31 Commits

Author SHA1 Message Date
Andreas Schildbach
9eab6977bd Release 0.12.1 2014-10-26 15:11:28 +01:00
Mike Hearn
0fd3752dcf Use finer grained locking around the wallet keychain, to allow for fast reading of keys/the current receive address even if the wallet is busy auto saving or processing large transactions. This helps reduce UI hangs/lag on Android. 2014-10-24 18:18:55 +02:00
Mike Hearn
955c60180b Log full tx when considered risky. 2014-10-23 17:21:45 +02:00
Mike Hearn
f9c4e2153c Default risk analysis: fix an off-by-one error in dust output comparisons. 2014-10-23 17:21:45 +02:00
Mike Hearn
146f082dfb Key rotation: fix bug that could cause multiple identical key chains to be created over and over if the key rotation time was equal to the time of the oldest best key, with test coverage. 2014-10-23 15:58:52 +02:00
Mike Hearn
23b3a637ab Key rotation: add saveNow call after new HD chains might have been added. 2014-10-23 00:10:48 +02:00
Mike Hearn
d2865f93e6 doMaintenance: don't trigger signing if bool param is false, as an optimisation. 2014-10-22 22:24:24 +02:00
Mike Hearn
dd204fdbf5 Fix off by one in DKC.getKeys(false). Resolves #253 2014-10-22 22:24:14 +02:00
Mike Hearn
2c44957b6d 10x fee drop, now most miners seem to have upgraded to 0.9+ 2014-10-22 22:23:38 +02:00
Mike Hearn
61626447f2 Make basicCategoryStepTest independent of actual min fee level. 2014-10-22 22:23:25 +02:00
Mike Hearn
72f3900ca4 Key rotation: also unit test the creation time of a fresh key. 2014-10-22 22:19:59 +02:00
Mike Hearn
235478adb2 Add maybeDoMaintenance back as a deprecated alias. 2014-10-22 22:19:49 +02:00
Mike Hearn
4a97212240 Rename maybeDoMaintenance to doMaintenance and add a bit more docs. 2014-10-22 22:19:43 +02:00
Mike Hearn
d99a4f887b Key rotation: construct new HD chain based on the oldest possible key, a la upgrade, with a fresh random HD chain only being created if all random keys are rotating. 2014-10-22 22:19:36 +02:00
Mike Hearn
b333f19983 Key rotation: remove the enabled setting. It's no longer useful and defaulted to off, which is dangerous and can lead to bugs. 2014-10-22 22:19:25 +02:00
Jarl Fransson
b879606d8c When deserializing client payment channel state, if there was an existing close transaction, it was deserialized from wrong data. 2014-10-22 22:19:05 +02:00
Peter Dettman
81eff264cf Improve Coin range check to cope with Long.MIN_VALUE correctly 2014-10-21 18:39:58 +02:00
Andreas Schildbach
ec306981e7 Replace two occurences of pom versions with ${project.version}. It makes incrementing the version a little bit less painful. 2014-10-21 18:38:57 +02:00
Andreas Schildbach
301f6c750d Sort keys by age when printing them. Also fix a generics warning. 2014-10-16 12:32:09 +02:00
eleetas
680221d577 Added check to determine if the DB connection has been closed. If yes, then get a new connection. 2014-10-16 12:30:57 +02:00
Andreas Schildbach
32f6c5f330 Fix one unexpected case of fiatToCoin overflow and add tests. 2014-10-16 12:30:49 +02:00
Andreas Schildbach
a23b018b22 Add missing JavaDocs for ExchangeRate. 2014-10-16 12:30:37 +02:00
Adam Mackler
f83c93b627 Add javadoc comment for VersionedChecksummedBytes.toString(). 2014-10-16 12:30:27 +02:00
Oscar Guindzberg
aed2816bad Add/Remove wallets to the connected peers as they are added/removed
to/from the PeerGroup
2014-10-16 12:30:18 +02:00
Andreas Schildbach
9bfcb80516 PeerGroup improvements:
1) Don't hold the PeerGroup lock across DNS discovery, otherwise the API is high latency in this period of startup. Fixes issue in Lighthouse where the UI would not appear until DNS resolution had completed.

2) Don't backoff peers that failed due to a first-time connection error.

3) If an IPv6 peer fails to connect due to a NoRouteToHostException, don't try any more IPv6 peers in future.
2014-10-16 12:25:53 +02:00
Andreas Schildbach
f7a0979091 Fix duplicate 'BTC' in log message. 2014-10-16 11:54:50 +02:00
Mike Hearn
e0467da35c Suppress noisy log line that is duplicated by LocalTransactionSigner. 2014-10-16 11:54:18 +02:00
Mike Hearn
a5582461d7 Extend exception message to help track down flaky test. 2014-10-16 11:50:45 +02:00
Mike Hearn
6a3eaf31fe Suppress noisy log line during signing. 2014-10-16 11:50:10 +02:00
Mike Hearn
b5ac1d8a76 AUTHORS: Fix Martin's name. 2014-10-16 11:49:33 +02:00
Andreas Schildbach
bf52cc86bb Prepare 0.12.1-SNAPSHOT 2014-10-16 11:39:25 +02:00
29 changed files with 519 additions and 276 deletions

View File

@@ -36,5 +36,5 @@ Kalpesh Parmar <kparmar1@hotmail.com>
Giannis Dzegoutanis <erasmospunk@gmail.com>
Oscar Guindzberg <oscar.guindzberg@gmail.com>
Richard Green <richardagreen@gmail.com>
Martin Zachrinson <zac@cyberzac.se>
Martin Zachrison <zac@cyberzac.se>
Michael Bumann <michael@railslove.com>

View File

@@ -22,7 +22,7 @@
<parent>
<groupId>org.bitcoinj</groupId>
<artifactId>bitcoinj-parent</artifactId>
<version>0.12</version>
<version>0.12.1</version>
</parent>
<artifactId>bitcoinj-core</artifactId>
@@ -270,7 +270,7 @@
<outputDirectory>target/test-classes/</outputDirectory>
<groupId>org.bitcoinj</groupId>
<artifactId>bitcoinj-core</artifactId>
<version>0.12</version>
<version>${project.version}</version>
</artifactItem>
</artifactItems>
</configuration>

View File

@@ -669,7 +669,7 @@ public class Block extends Message {
// Allow injection of a fake clock to allow unit testing.
long currentTime = Utils.currentTimeSeconds();
if (time > currentTime + ALLOWED_TIME_DRIFT)
throw new VerificationException("Block too far in future");
throw new VerificationException(String.format("Block too far in future: %d vs %d", time, currentTime + ALLOWED_TIME_DRIFT));
}
private void checkSigOps() throws VerificationException {

View File

@@ -82,9 +82,11 @@ public final class Coin implements Monetary, Comparable<Coin>, Serializable {
*/
public final long value;
private final long MAX_SATOSHIS = COIN_VALUE * NetworkParameters.MAX_COINS;
private Coin(final long satoshis) {
checkArgument(Math.abs(satoshis) <= COIN_VALUE * NetworkParameters.MAX_COINS,
"%s satoshis exceeds maximum possible quantity of Bitcoin.", satoshis);
checkArgument(-MAX_SATOSHIS <= satoshis && satoshis <= MAX_SATOSHIS,
"%s satoshis exceeds maximum possible quantity of Bitcoin.", satoshis);
this.value = satoshis;
}

View File

@@ -90,9 +90,21 @@ import static com.google.common.base.Preconditions.*;
public class ECKey implements EncryptableItem, Serializable {
private static final Logger log = LoggerFactory.getLogger(ECKey.class);
/** Compares pub key bytes using {@link com.google.common.primitives.UnsignedBytes#lexicographicalComparator()} **/
/** Sorts oldest keys first, newest last. */
public static final Comparator<ECKey> AGE_COMPARATOR = new Comparator<ECKey>() {
@Override
public int compare(ECKey k1, ECKey k2) {
if (k1.creationTimeSeconds == k2.creationTimeSeconds)
return 0;
else
return k1.creationTimeSeconds > k2.creationTimeSeconds ? 1 : -1;
}
};
/** Compares pub key bytes using {@link com.google.common.primitives.UnsignedBytes#lexicographicalComparator()} */
public static final Comparator<ECKey> PUBKEY_COMPARATOR = new Comparator<ECKey>() {
private Comparator comparator = UnsignedBytes.lexicographicalComparator();
private Comparator<byte[]> comparator = UnsignedBytes.lexicographicalComparator();
@Override
public int compare(ECKey k1, ECKey k2) {

View File

@@ -32,7 +32,7 @@ import org.bitcoinj.utils.Threading;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import com.google.common.collect.Lists;
import com.google.common.net.InetAddresses;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
@@ -44,8 +44,10 @@ import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.io.IOException;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NoRouteToHostException;
import java.net.Socket;
import java.util.*;
import java.util.concurrent.*;
@@ -123,6 +125,7 @@ public class PeerGroup extends AbstractExecutionThreadService implements Transac
private long pingIntervalMsec = DEFAULT_PING_INTERVAL_MSEC;
@GuardedBy("lock") private boolean useLocalhostPeerWhenPossible = true;
@GuardedBy("lock") private boolean ipv6Unreachable = false;
private final NetworkParameters params;
private final AbstractBlockChain chain;
@@ -255,7 +258,7 @@ public class PeerGroup extends AbstractExecutionThreadService implements Transac
@Override
public void onPeerDisconnected(Peer peer, int peerCount) {
// The channel will be automatically removed from channels.
handlePeerDeath(peer);
handlePeerDeath(peer, null);
}
}
@@ -658,26 +661,30 @@ public class PeerGroup extends AbstractExecutionThreadService implements Transac
}
protected void discoverPeers() throws PeerDiscoveryException {
checkState(lock.isHeldByCurrentThread());
if (peerDiscoverers.isEmpty())
throw new PeerDiscoveryException("No peer discoverers registered");
long start = System.currentTimeMillis();
Set<PeerAddress> addressSet = Sets.newHashSet();
final List<PeerAddress> addressList = Lists.newLinkedList();
for (PeerDiscovery peerDiscovery : peerDiscoverers) {
InetSocketAddress[] addresses;
addresses = peerDiscovery.getPeers(5, TimeUnit.SECONDS);
for (InetSocketAddress address : addresses) addressSet.add(new PeerAddress(address));
if (addressSet.size() > 0) break;
}
lock.lock();
try {
for (PeerAddress address : addressSet) {
addInactive(address);
}
} finally {
// Don't hold the peergroup lock across peer discovery as it's likely to be very slow and would make the
// peergroup API high latency.
lock.unlock();
try {
addresses = peerDiscovery.getPeers(5, TimeUnit.SECONDS);
} finally {
lock.lock();
}
for (InetSocketAddress address : addresses) addressList.add(new PeerAddress(address));
if (addressList.size() > 0) break;
}
for (PeerAddress address : addressList) {
addInactive(address);
}
log.info("Peer discovery took {}msec and returned {} items",
System.currentTimeMillis() - start, addressSet.size());
System.currentTimeMillis() - start, addressList.size());
}
@Override
@@ -758,6 +765,7 @@ public class PeerGroup extends AbstractExecutionThreadService implements Transac
return;
}
if (!haveReadyInactivePeer(nowMillis)) {
// Release the lock here because we'll probably do slow things like DNS lookups below,
discoverPeers();
groupBackoff.trackSuccess();
nowMillis = Utils.currentTimeMillis();
@@ -766,7 +774,8 @@ public class PeerGroup extends AbstractExecutionThreadService implements Transac
log.debug("Peer discovery didn't provide us any more peers, not trying to build new connection.");
return;
}
addr = inactives.poll();
while (addr == null || (ipv6Unreachable && addr.getAddr() instanceof Inet6Address))
addr = inactives.poll();
retryTime = backoffMap.get(addr).getRetryTime();
} finally {
// discoverPeers might throw an exception if something goes wrong: we then hit this path with addr == null.
@@ -864,6 +873,9 @@ public class PeerGroup extends AbstractExecutionThreadService implements Transac
wallet.setTransactionBroadcaster(this);
wallet.addEventListener(walletEventListener, Threading.SAME_THREAD);
addPeerFilterProvider(wallet);
for (Peer peer : peers) {
peer.addWallet(wallet);
}
} finally {
lock.unlock();
}
@@ -902,6 +914,9 @@ public class PeerGroup extends AbstractExecutionThreadService implements Transac
peerFilterProviders.remove(wallet);
wallet.removeEventListener(walletEventListener);
wallet.setTransactionBroadcaster(null);
for (Peer peer : peers) {
peer.removeWallet(wallet);
}
}
public static enum FilterRecalculateMode {
@@ -1025,7 +1040,7 @@ public class PeerGroup extends AbstractExecutionThreadService implements Transac
channels.openConnection(address.toSocketAddress(), peer);
} catch (Exception e) {
log.warn("Failed to connect to " + address + ": " + e.getMessage());
handlePeerDeath(peer);
handlePeerDeath(peer, e);
return null;
}
peer.setSocketTimeout(connectTimeoutMillis);
@@ -1275,12 +1290,12 @@ public class PeerGroup extends AbstractExecutionThreadService implements Transac
}
}
protected void handlePeerDeath(final Peer peer) {
protected void handlePeerDeath(final Peer peer, @Nullable Exception exception) {
// Peer deaths can occur during startup if a connect attempt after peer discovery aborts immediately.
final State state = state();
if (state != State.RUNNING && state != State.STARTING) return;
int numPeers = 0;
int numPeers;
int numConnectedPeers = 0;
lock.lock();
try {
@@ -1307,10 +1322,15 @@ public class PeerGroup extends AbstractExecutionThreadService implements Transac
groupBackoff.trackFailure();
//TODO: if network failure is suspected, do not backoff peer
backoffMap.get(address).trackFailure();
// Put back on inactive list
inactives.offer(address);
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");
}
backoffMap.get(address).trackFailure();
// Put back on inactive list
inactives.offer(address);
}
if (numPeers < getMaxConnections()) {
triggerConnections();

View File

@@ -90,16 +90,16 @@ public class Transaction extends ChildMessage implements Serializable {
/**
* If fee is lower than this value (in satoshis), a default reference client will treat it as if there were no fee.
* Currently this is 10000 satoshis.
* Currently this is 1000 satoshis.
*/
public static final Coin REFERENCE_DEFAULT_MIN_TX_FEE = Coin.valueOf(10000);
public static final Coin REFERENCE_DEFAULT_MIN_TX_FEE = Coin.valueOf(1000);
/**
* Any standard (ie pay-to-address) output smaller than this value (in satoshis) will most likely be rejected by the network.
* This is calculated by assuming a standard output will be 34 bytes, and then using the formula used in
* {@link TransactionOutput#getMinNonDustValue(Coin)}. Currently it's 5460 satoshis.
* {@link TransactionOutput#getMinNonDustValue(Coin)}. Currently it's 546 satoshis.
*/
public static final Coin MIN_NONDUST_OUTPUT = Coin.valueOf(5460);
public static final Coin MIN_NONDUST_OUTPUT = Coin.valueOf(546);
// These are serialized in both bitcoin and java serialization.
private long version;

View File

@@ -75,7 +75,7 @@ public class VersionMessage extends Message {
public boolean relayTxesBeforeFilter;
/** The version of this library release, as a string. */
public static final String BITCOINJ_VERSION = "0.12";
public static final String BITCOINJ_VERSION = "0.12.1";
/** The value that is prepended to the subVer field of this application. */
public static final String LIBRARY_SUBVER = "/bitcoinj:" + BITCOINJ_VERSION + "/";

View File

@@ -49,6 +49,10 @@ public class VersionedChecksummedBytes implements Serializable {
this.bytes = bytes;
}
/**
* Returns the base-58 encoded String representation of this
* object, including version and checksum bytes.
*/
@Override
public String toString() {
// A stringified buffer is:

View File

@@ -17,23 +17,6 @@
package org.bitcoinj.core;
import org.bitcoinj.core.TransactionConfidence.ConfidenceType;
import org.bitcoinj.crypto.*;
import org.bitcoinj.params.UnitTestParams;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptBuilder;
import org.bitcoinj.script.ScriptChunk;
import org.bitcoinj.signers.MissingSigResolutionSigner;
import org.bitcoinj.signers.LocalTransactionSigner;
import org.bitcoinj.signers.TransactionSigner;
import org.bitcoinj.store.UnreadableWalletException;
import org.bitcoinj.store.WalletProtobufSerializer;
import org.bitcoinj.utils.BaseTaggableObject;
import org.bitcoinj.utils.ExchangeRate;
import org.bitcoinj.utils.ListenerRegistration;
import org.bitcoinj.utils.Threading;
import org.bitcoinj.wallet.*;
import org.bitcoinj.wallet.WalletTransaction.Pool;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects;
import com.google.common.base.Objects.ToStringHelper;
@@ -44,21 +27,40 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import com.google.protobuf.ByteString;
import net.jcip.annotations.GuardedBy;
import org.bitcoin.protocols.payments.Protos.PaymentDetails;
import org.bitcoinj.core.TransactionConfidence.ConfidenceType;
import org.bitcoinj.crypto.*;
import org.bitcoinj.params.UnitTestParams;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptBuilder;
import org.bitcoinj.script.ScriptChunk;
import org.bitcoinj.signers.LocalTransactionSigner;
import org.bitcoinj.signers.MissingSigResolutionSigner;
import org.bitcoinj.signers.TransactionSigner;
import org.bitcoinj.store.UnreadableWalletException;
import org.bitcoinj.store.WalletProtobufSerializer;
import org.bitcoinj.utils.BaseTaggableObject;
import org.bitcoinj.utils.ExchangeRate;
import org.bitcoinj.utils.ListenerRegistration;
import org.bitcoinj.utils.Threading;
import org.bitcoinj.wallet.*;
import org.bitcoinj.wallet.Protos.Wallet.EncryptionType;
import org.bitcoinj.wallet.WalletTransaction.Pool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.crypto.params.KeyParameter;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import java.io.*;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import static com.google.common.base.Preconditions.*;
@@ -106,7 +108,10 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
private static final long serialVersionUID = 2L;
private static final int MINIMUM_BLOOM_DATA_LENGTH = 8;
// Ordering: lock > keychainLock. Keychain is protected separately to allow fast querying of current receive address
// even if the wallet itself is busy e.g. saving or processing a big reorg. Useful for reducing UI latency.
protected final ReentrantLock lock = Threading.lock("wallet");
protected final ReentrantReadWriteLock keychainLock = Threading.factory.newReentrantReadWriteLock("wallet-keychain");
// The various pools below give quick access to wallet-relevant transactions by the state they're in:
//
@@ -151,7 +156,7 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
// The key chain group is not thread safe, and generally the whole hierarchy of objects should not be mutated
// outside the wallet lock. So don't expose this object directly via any accessors!
@GuardedBy("lock") protected KeyChainGroup keychain;
@GuardedBy("keychainLock") protected KeyChainGroup keychain;
// A list of scripts watched by this wallet.
private Set<Script> watchedScripts;
@@ -187,7 +192,6 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
// UNIX time in seconds. Money controlled by keys created before this time will be automatically respent to a key
// that was created after it. Useful when you believe some keys have been compromised.
private volatile long vKeyRotationTimestamp;
private volatile boolean vKeyRotationEnabled;
protected transient CoinSelector coinSelector = new DefaultCoinSelector();
@@ -346,12 +350,12 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* a different key (for each purpose independently).
*/
public DeterministicKey currentKey(KeyChain.KeyPurpose purpose) {
lock.lock();
keychainLock.readLock().lock();
try {
maybeUpgradeToHD();
return keychain.currentKey(purpose);
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -367,12 +371,12 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* Returns address for a {@link #currentKey(org.bitcoinj.wallet.KeyChain.KeyPurpose)}
*/
public Address currentAddress(KeyChain.KeyPurpose purpose) {
lock.lock();
keychainLock.readLock().lock();
try {
maybeUpgradeToHD();
return keychain.currentAddress(purpose);
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -405,17 +409,18 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* to someone who wishes to send money.
*/
public List<DeterministicKey> freshKeys(KeyChain.KeyPurpose purpose, int numberOfKeys) {
lock.lock();
List<DeterministicKey> keys;
keychainLock.writeLock().lock();
try {
maybeUpgradeToHD();
List<DeterministicKey> keys = keychain.freshKeys(purpose, numberOfKeys);
// Do we really need an immediate hard save? Arguably all this is doing is saving the 'current' key
// and that's not quite so important, so we could coalesce for more performance.
saveNow();
return keys;
keys = keychain.freshKeys(purpose, numberOfKeys);
} finally {
lock.unlock();
keychainLock.writeLock().unlock();
}
// Do we really need an immediate hard save? Arguably all this is doing is saving the 'current' key
// and that's not quite so important, so we could coalesce for more performance.
saveNow();
return keys;
}
/**
@@ -430,14 +435,15 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* Returns address for a {@link #freshKey(org.bitcoinj.wallet.KeyChain.KeyPurpose)}
*/
public Address freshAddress(KeyChain.KeyPurpose purpose) {
lock.lock();
Address key;
keychainLock.writeLock().lock();
try {
Address key = keychain.freshAddress(purpose);
saveNow();
return key;
key = keychain.freshAddress(purpose);
} finally {
lock.unlock();
keychainLock.writeLock().unlock();
}
saveNow();
return key;
}
/**
@@ -457,11 +463,11 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* you automatically the first time a new key is requested (this happens when spending due to the change address).
*/
public void upgradeToDeterministic(@Nullable KeyParameter aesKey) throws DeterministicUpgradeRequiresPassword {
lock.lock();
keychainLock.writeLock().lock();
try {
keychain.upgradeToDeterministic(vKeyRotationEnabled ? vKeyRotationTimestamp : 0, aesKey);
keychain.upgradeToDeterministic(vKeyRotationTimestamp, aesKey);
} finally {
lock.unlock();
keychainLock.writeLock().unlock();
}
}
@@ -471,11 +477,11 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* that would require a new address or key.
*/
public boolean isDeterministicUpgradeRequired() {
lock.lock();
keychainLock.readLock().lock();
try {
return keychain.isDeterministicUpgradeRequired();
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -484,7 +490,6 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
}
private void maybeUpgradeToHD(@Nullable KeyParameter aesKey) throws DeterministicUpgradeRequiresPassword {
checkState(lock.isHeldByCurrentThread());
if (keychain.isDeterministicUpgradeRequired()) {
log.info("Upgrade to HD wallets is required, attempting to do so.");
try {
@@ -501,11 +506,11 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* Returns a snapshot of the watched scripts. This view is not live.
*/
public List<Script> getWatchedScripts() {
lock.lock();
keychainLock.readLock().lock();
try {
return new ArrayList<Script>(watchedScripts);
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -515,11 +520,11 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* @return Whether the key was removed or not.
*/
public boolean removeKey(ECKey key) {
lock.lock();
keychainLock.writeLock().lock();
try {
return keychain.removeImportedKey(key);
} finally {
lock.unlock();
keychainLock.writeLock().unlock();
}
}
@@ -527,11 +532,11 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* Returns the number of keys in the key chain, including lookahead keys.
*/
public int getKeychainSize() {
lock.lock();
keychainLock.readLock().lock();
try {
return keychain.numKeys();
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -539,11 +544,11 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* Returns a list of the non-deterministic keys that have been imported into the wallet, or the empty list if none.
*/
public List<ECKey> getImportedKeys() {
lock.lock();
keychainLock.readLock().lock();
try {
return keychain.getImportedKeys();
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -587,16 +592,17 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* in the list that was not already present.
*/
public int importKeys(final List<ECKey> keys) {
lock.lock();
// API usage check.
checkNoDeterministicKeys(keys);
int result;
keychainLock.writeLock().lock();
try {
// API usage check.
checkNoDeterministicKeys(keys);
int result = keychain.importKeys(keys);
saveNow();
return result;
result = keychain.importKeys(keys);
} finally {
lock.unlock();
keychainLock.writeLock().unlock();
}
saveNow();
return result;
}
private void checkNoDeterministicKeys(List<ECKey> keys) {
@@ -608,23 +614,23 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
/** Takes a list of keys and a password, then encrypts and imports them in one step using the current keycrypter. */
public int importKeysAndEncrypt(final List<ECKey> keys, CharSequence password) {
lock.lock();
keychainLock.writeLock().lock();
try {
checkNotNull(getKeyCrypter(), "Wallet is not encrypted");
return importKeysAndEncrypt(keys, getKeyCrypter().deriveKey(password));
} finally {
lock.unlock();
keychainLock.writeLock().unlock();
}
}
/** Takes a list of keys and an AES key, then encrypts and imports them in one step using the current keycrypter. */
public int importKeysAndEncrypt(final List<ECKey> keys, KeyParameter aesKey) {
lock.lock();
keychainLock.writeLock().lock();
try {
checkNoDeterministicKeys(keys);
return keychain.importKeysAndEncrypt(keys, aesKey);
} finally {
lock.unlock();
keychainLock.writeLock().unlock();
}
}
@@ -675,28 +681,33 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
/** See {@link org.bitcoinj.wallet.DeterministicKeyChain#setLookaheadSize(int)} for more info on this. */
public int getKeychainLookaheadSize() {
return keychain.getLookaheadSize();
keychainLock.readLock().lock();
try {
return keychain.getLookaheadSize();
} finally {
keychainLock.readLock().unlock();
}
}
/** See {@link org.bitcoinj.wallet.DeterministicKeyChain#setLookaheadThreshold(int)} for more info on this. */
public void setKeychainLookaheadThreshold(int num) {
lock.lock();
keychainLock.writeLock().lock();
try {
maybeUpgradeToHD();
keychain.setLookaheadThreshold(num);
} finally {
lock.unlock();
keychainLock.writeLock().unlock();
}
}
/** See {@link org.bitcoinj.wallet.DeterministicKeyChain#setLookaheadThreshold(int)} for more info on this. */
public int getKeychainLookaheadThreshold() {
lock.lock();
keychainLock.readLock().lock();
try {
maybeUpgradeToHD();
return keychain.getLookaheadThreshold();
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -707,12 +718,12 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* zero key in the recommended BIP32 hierarchy.
*/
public DeterministicKey getWatchingKey() {
lock.lock();
keychainLock.readLock().lock();
try {
maybeUpgradeToHD();
return keychain.getActiveKeyChain().getWatchingKey();
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -767,29 +778,27 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* @return how many scripts were added successfully
*/
public int addWatchedScripts(final List<Script> scripts) {
lock.lock();
int added = 0;
keychainLock.writeLock().lock();
try {
int added = 0;
for (final Script script : scripts) {
if (watchedScripts.contains(script)) continue;
watchedScripts.add(script);
added++;
}
queueOnScriptsAdded(scripts);
saveNow();
return added;
} finally {
lock.unlock();
keychainLock.writeLock().unlock();
}
queueOnScriptsAdded(scripts);
saveNow();
return added;
}
/**
* Returns all addresses watched by this wallet.
*/
public List<Address> getWatchedAddresses() {
lock.lock();
keychainLock.readLock().lock();
try {
List<Address> addresses = new LinkedList<Address>();
for (Script script : watchedScripts)
@@ -797,7 +806,7 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
addresses.add(script.getToAddress(params));
return addresses;
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -810,21 +819,21 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
@Override
@Nullable
public ECKey findKeyFromPubHash(byte[] pubkeyHash) {
lock.lock();
keychainLock.readLock().lock();
try {
return keychain.findKeyFromPubHash(pubkeyHash);
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
/** Returns true if the given key is in the wallet, false otherwise. Currently an O(N) operation. */
public boolean hasKey(ECKey key) {
lock.lock();
keychainLock.readLock().lock();
try {
return keychain.hasKey(key);
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -837,11 +846,11 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
/** {@inheritDoc} */
@Override
public boolean isWatchedScript(Script script) {
lock.lock();
keychainLock.readLock().lock();
try {
return watchedScripts.contains(script);
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -852,11 +861,11 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
@Override
@Nullable
public ECKey findKeyFromPubKey(byte[] pubkey) {
lock.lock();
keychainLock.readLock().lock();
try {
return keychain.findKeyFromPubKey(pubkey);
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -873,11 +882,11 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
@Nullable
@Override
public RedeemData findRedeemDataFromScriptHash(byte[] payToScriptHash) {
lock.lock();
keychainLock.readLock().lock();
try {
return keychain.findRedeemDataFromScriptHash(payToScriptHash);
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -892,7 +901,7 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* See {@link org.bitcoinj.wallet.DeterministicKeyChain#markKeyAsUsed(DeterministicKey)} for more info on this.
*/
private void markKeysAsUsed(Transaction tx) {
lock.lock();
keychainLock.writeLock().lock();
try {
for (TransactionOutput o : tx.getOutputs()) {
try {
@@ -910,7 +919,7 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
}
}
} finally {
lock.unlock();
keychainLock.writeLock().unlock();
}
}
@@ -919,14 +928,14 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* @throws org.bitcoinj.core.ECKey.MissingPrivateKeyException if the seed is unavailable (watching wallet)
*/
public DeterministicSeed getKeyChainSeed() {
lock.lock();
keychainLock.readLock().lock();
try {
DeterministicSeed seed = keychain.getActiveKeyChain().getSeed();
if (seed == null)
throw new ECKey.MissingPrivateKeyException();
return seed;
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -935,12 +944,12 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* use currentReceiveKey/freshReceiveKey instead.
*/
public DeterministicKey getKeyByPath(List<ChildNumber> path) {
lock.lock();
keychainLock.readLock().lock();
try {
maybeUpgradeToHD();
return keychain.getActiveKeyChain().getKeyByPath(path, false);
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -950,14 +959,14 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* parameters to derive a key from the given password.
*/
public void encrypt(CharSequence password) {
lock.lock();
keychainLock.writeLock().lock();
try {
final KeyCrypterScrypt scrypt = new KeyCrypterScrypt();
keychain.encrypt(scrypt, scrypt.deriveKey(password));
saveNow();
} finally {
lock.unlock();
keychainLock.writeLock().unlock();
}
saveNow();
}
/**
@@ -969,13 +978,13 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* @throws KeyCrypterException Thrown if the wallet encryption fails. If so, the wallet state is unchanged.
*/
public void encrypt(KeyCrypter keyCrypter, KeyParameter aesKey) {
lock.lock();
keychainLock.writeLock().lock();
try {
keychain.encrypt(keyCrypter, aesKey);
saveNow();
} finally {
lock.unlock();
keychainLock.writeLock().unlock();
}
saveNow();
}
/**
@@ -983,15 +992,15 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* @throws KeyCrypterException Thrown if the wallet decryption fails. If so, the wallet state is unchanged.
*/
public void decrypt(CharSequence password) {
lock.lock();
keychainLock.writeLock().lock();
try {
final KeyCrypter crypter = keychain.getKeyCrypter();
checkState(crypter != null, "Not encrypted");
keychain.decrypt(crypter.deriveKey(password));
saveNow();
} finally {
lock.unlock();
keychainLock.writeLock().unlock();
}
saveNow();
}
/**
@@ -1001,13 +1010,13 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* @throws KeyCrypterException Thrown if the wallet decryption fails. If so, the wallet state is unchanged.
*/
public void decrypt(KeyParameter aesKey) {
lock.lock();
keychainLock.writeLock().lock();
try {
keychain.decrypt(aesKey);
saveNow();
} finally {
lock.unlock();
keychainLock.writeLock().unlock();
}
saveNow();
}
/**
@@ -1018,11 +1027,11 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* @throws IllegalStateException if the wallet is not encrypted.
*/
public boolean checkPassword(CharSequence password) {
lock.lock();
keychainLock.readLock().lock();
try {
return keychain.checkPassword(password);
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -1032,11 +1041,11 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* @return boolean true if AES key supplied can decrypt the first encrypted private key in the wallet, false otherwise.
*/
public boolean checkAESKey(KeyParameter aesKey) {
lock.lock();
keychainLock.readLock().lock();
try {
return keychain.checkAESKey(aesKey);
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -1046,11 +1055,11 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
*/
@Nullable
public KeyCrypter getKeyCrypter() {
lock.lock();
keychainLock.readLock().lock();
try {
return keychain.getKeyCrypter();
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -1060,7 +1069,7 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* (This is a convenience method - the encryption type is actually stored in the keyCrypter).
*/
public EncryptionType getEncryptionType() {
lock.lock();
keychainLock.readLock().lock();
try {
KeyCrypter crypter = keychain.getKeyCrypter();
if (crypter != null)
@@ -1068,7 +1077,7 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
else
return EncryptionType.UNENCRYPTED;
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -1086,11 +1095,11 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
// TODO: Make this package private once the classes finish moving around.
/** Internal use only. */
public List<Protos.Key> serializeKeychainToProtobuf() {
lock.lock();
keychainLock.readLock().lock();
try {
return keychain.serializeToProtobuf();
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -1502,7 +1511,7 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
RiskAnalysis analysis = riskAnalyzer.create(this, tx, dependencies);
RiskAnalysis.Result result = analysis.analyze();
if (result != RiskAnalysis.Result.OK) {
log.warn("Pending transaction {} was considered risky: {}", tx.getHashAsString(), analysis);
log.warn("Pending transaction was considered risky: {}\n{}", analysis, tx);
return true;
}
return false;
@@ -2106,13 +2115,9 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* like receiving money. The listener is executed by the given executor.
*/
public void addEventListener(WalletEventListener listener, Executor executor) {
lock.lock();
try {
eventListeners.add(new ListenerRegistration<WalletEventListener>(listener, executor));
keychain.addEventListener(listener, executor);
} finally {
lock.unlock();
}
// This is thread safe, so we don't need to take the lock.
eventListeners.add(new ListenerRegistration<WalletEventListener>(listener, executor));
keychain.addEventListener(listener, executor);
}
/**
@@ -2120,13 +2125,8 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* was never added.
*/
public boolean removeEventListener(WalletEventListener listener) {
lock.lock();
try {
keychain.removeEventListener(listener);
return ListenerRegistration.removeFromList(listener, eventListeners);
} finally {
lock.unlock();
}
keychain.removeEventListener(listener);
return ListenerRegistration.removeFromList(listener, eventListeners);
}
private void queueOnTransactionConfidenceChanged(final Transaction tx) {
@@ -2199,7 +2199,6 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
}
protected void queueOnScriptsAdded(final List<Script> scripts) {
checkState(lock.isHeldByCurrentThread());
for (final ListenerRegistration<WalletEventListener> registration : eventListeners) {
registration.executor.execute(new Runnable() {
@Override
@@ -2635,7 +2634,7 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
*/
@Override
public long getEarliestKeyCreationTime() {
lock.lock();
keychainLock.readLock().lock();
try {
long earliestTime = keychain.getEarliestKeyCreationTime();
for (Script script : watchedScripts)
@@ -2644,7 +2643,7 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
return Utils.currentTimeSeconds();
return earliestTime;
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -3455,7 +3454,7 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
final Coin calculatedFee = req.tx.getFee();
if (calculatedFee != null) {
log.info(" with a fee of {} BTC", calculatedFee.toFriendlyString());
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
@@ -3498,7 +3497,7 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
for (int i = 0; i < numInputs; i++) {
TransactionInput txIn = tx.getInput(i);
if (txIn.getConnectedOutput() == null) {
log.warn("Missing connected output, assuming input {} is already signed.", i);
// Missing connected output, assuming already signed.
continue;
}
@@ -3800,29 +3799,27 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
*/
@Override
public int getBloomFilterElementCount() {
// This is typically called by the PeerGroup, in which case it will have already explicitly taken the lock
// before calling, but because this is public API we must still lock again regardless.
lock.lock();
try {
int size = keychain.getBloomFilterElementCount();
for (Transaction tx : getTransactions(false)) {
for (TransactionOutput out : tx.getOutputs()) {
try {
if (isTxOutputBloomFilterable(out))
size++;
} catch (ScriptException e) {
throw new RuntimeException(e); // If it is ours, we parsed the script correctly, so this shouldn't happen
}
int size = 0;
for (Transaction tx : getTransactions(false)) {
for (TransactionOutput out : tx.getOutputs()) {
try {
if (isTxOutputBloomFilterable(out))
size++;
} catch (ScriptException e) {
// If it is ours, we parsed the script correctly, so this shouldn't happen.
throw new RuntimeException(e);
}
}
// Some scripts may have more than one bloom element. That should normally be okay,
// because under-counting just increases false-positive rate.
}
keychainLock.readLock().lock();
try {
size += keychain.getBloomFilterElementCount();
// Some scripts may have more than one bloom element. That should normally be okay, because under-counting
// just increases false-positive rate.
size += watchedScripts.size();
return size;
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -3835,11 +3832,11 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
public boolean isRequiringUpdateAllBloomFilter() {
// This is typically called by the PeerGroup, in which case it will have already explicitly taken the lock
// before calling, but because this is public API we must still lock again regardless.
lock.lock();
keychainLock.readLock().lock();
try {
return !watchedScripts.isEmpty();
} finally {
lock.unlock();
keychainLock.readLock().unlock();
}
}
@@ -3867,6 +3864,7 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
// This is typically called by the PeerGroup, in which case it will have already explicitly taken the lock
// before calling, but because this is public API we must still lock again regardless.
lock.lock();
keychainLock.readLock().lock();
try {
BloomFilter filter = keychain.getBloomFilter(size, falsePositiveRate, nTweak);
@@ -3895,12 +3893,12 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
}
return filter;
} finally {
keychainLock.readLock().unlock();
lock.unlock();
}
}
private boolean isTxOutputBloomFilterable(TransactionOutput out) {
checkState(lock.isHeldByCurrentThread());
boolean isScriptTypeSupported = out.getScriptPubKey().isSentToRawPubKey() || out.getScriptPubKey().isPayToScriptHash();
return (out.isMine(this) && isScriptTypeSupported) ||
out.isWatched(this);
@@ -3912,7 +3910,7 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* sequence within it to reliably find relevant transactions.
*/
public boolean checkForFilterExhaustion(FilteredBlock block) {
lock.lock();
keychainLock.writeLock().lock();
try {
int epoch = keychain.getCombinedKeyLookaheadEpochs();
for (Transaction tx : block.getAssociatedTransactions().values()) {
@@ -3925,7 +3923,7 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
// block at this point and await a new filter before restarting the download.
return newEpoch > epoch;
} finally {
lock.unlock();
keychainLock.writeLock().unlock();
}
}
@@ -4302,15 +4300,12 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
}
/**
* <p>When a key rotation time is set, and money controlled by keys created before the given timestamp T will be
* <p>When a key rotation time is set, any money controlled by keys created before the given timestamp T will be
* automatically respent to any key that was created after T. This can be used to recover from a situation where
* a set of keys is believed to be compromised. You can stop key rotation by calling this method again with zero
* as the argument, or by using {@link #setKeyRotationEnabled(boolean)}. Once set up, calling
* {@link #maybeDoMaintenance(org.spongycastle.crypto.params.KeyParameter, boolean)} will create and possibly
* send rotation transactions: but it won't be done automatically (because you might have to ask for the users
* password).</p>
*
* <p>Note that this method won't do anything unless you call {@link #setKeyRotationEnabled(boolean)} first.</p>
* as the argument. Once set up, calling {@link #doMaintenance(org.spongycastle.crypto.params.KeyParameter, boolean)}
* will create and possibly send rotation transactions: but it won't be done automatically (because you might have
* to ask for the users password).</p>
*
* <p>The given time cannot be in the future.</p>
*/
@@ -4323,36 +4318,42 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
saveNow();
}
/** Toggles key rotation on and off. Note that this state is not serialized. */
public void setKeyRotationEnabled(boolean enabled) {
vKeyRotationEnabled = enabled;
}
/** Returns whether the keys creation time is before the key rotation time, if one was set. */
public boolean isKeyRotating(ECKey key) {
long time = vKeyRotationTimestamp;
return time != 0 && key.getCreationTimeSeconds() < time;
}
/** @deprecated Renamed to doMaintenance */
@Deprecated
public ListenableFuture<List<Transaction>> maybeDoMaintenance(@Nullable KeyParameter aesKey, boolean andSend) throws DeterministicUpgradeRequiresPassword {
return doMaintenance(aesKey, andSend);
}
/**
* A wallet app should call this from time to time if key rotation is enabled in order to let the wallet craft and
* send transactions needed to re-organise coins internally. A good time to call this would be after receiving coins
* for an unencrypted wallet, or after sending money for an encrypted wallet. If you have an encrypted wallet and
* just want to know if some maintenance needs doing, call this method with doSend set to false and look at the
* returned list of transactions.
* A wallet app should call this from time to time in order to let the wallet craft and send transactions needed
* to re-organise coins internally. A good time to call this would be after receiving coins for an unencrypted
* wallet, or after sending money for an encrypted wallet. If you have an encrypted wallet and just want to know
* if some maintenance needs doing, call this method with andSend set to false and look at the returned list of
* transactions. Maintenance might also include internal changes that involve some processing or work but
* which don't require making transactions - these will happen automatically unless the password is required
* in which case an exception will be thrown.
*
* @param aesKey the users password, if any.
* @param andSend if true, send the transactions via the tx broadcaster and return them, if false just return them.
* @param signAndSend if true, send the transactions via the tx broadcaster and return them, if false just return them.
* @return A list of transactions that the wallet just made/will make for internal maintenance. Might be empty.
* @throws org.bitcoinj.wallet.DeterministicUpgradeRequiresPassword if key rotation requires the users password.
*/
public ListenableFuture<List<Transaction>> maybeDoMaintenance(@Nullable KeyParameter aesKey, boolean andSend) {
public ListenableFuture<List<Transaction>> doMaintenance(@Nullable KeyParameter aesKey, boolean signAndSend) throws DeterministicUpgradeRequiresPassword {
List<Transaction> txns;
lock.lock();
keychainLock.writeLock().lock();
try {
txns = maybeRotateKeys(aesKey);
if (!andSend)
txns = maybeRotateKeys(aesKey, signAndSend);
if (!signAndSend)
return Futures.immediateFuture(txns);
} finally {
keychainLock.writeLock().unlock();
lock.unlock();
}
checkState(!lock.isHeldByCurrentThread());
@@ -4381,26 +4382,37 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
}
// Checks to see if any coins are controlled by rotating keys and if so, spends them.
private List<Transaction> maybeRotateKeys(@Nullable KeyParameter aesKey) {
private List<Transaction> maybeRotateKeys(@Nullable KeyParameter aesKey, boolean sign) throws DeterministicUpgradeRequiresPassword {
checkState(lock.isHeldByCurrentThread());
checkState(keychainLock.isWriteLockedByCurrentThread());
List<Transaction> results = Lists.newLinkedList();
// TODO: Handle chain replays here.
if (!vKeyRotationEnabled) return results;
// Snapshot volatiles so this method has an atomic view.
long keyRotationTimestamp = vKeyRotationTimestamp;
if (keyRotationTimestamp == 0) return results; // Nothing to do.
// We might have to create a new HD hierarchy if the previous ones are now rotating.
boolean allChainsRotating = true;
for (DeterministicKeyChain chain : keychain.getDeterministicKeyChains()) {
if (chain.getEarliestKeyCreationTime() > vKeyRotationTimestamp) {
if (chain.getEarliestKeyCreationTime() >= vKeyRotationTimestamp) {
allChainsRotating = false;
break;
}
}
if (allChainsRotating) {
log.info("All HD chains are currently rotating, creating a new one");
keychain.createAndActivateNewHDChain();
try {
if (keychain.getImportedKeys().isEmpty()) {
log.info("All HD chains are currently rotating and we have no random keys, creating fresh HD chain ...");
keychain.createAndActivateNewHDChain();
} else {
log.info("All HD chains are currently rotating, attempting to create a new one from the next oldest non-rotating key material ...");
keychain.upgradeToDeterministic(keyRotationTimestamp, aesKey);
log.info(" ... upgraded to HD again, based on next best oldest key.");
}
} catch (AllRandomKeysRotating rotating) {
log.info(" ... no non-rotating random keys available, generating entirely new HD tree: backup required after this.");
keychain.createAndActivateNewHDChain();
}
saveNow();
}
// Because transactions are size limited, we might not be able to re-key the entire wallet in one go. So
@@ -4408,14 +4420,14 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
// fully done, at least for now (we may still get more transactions later and this method will be reinvoked).
Transaction tx;
do {
tx = rekeyOneBatch(keyRotationTimestamp, aesKey, results);
tx = rekeyOneBatch(keyRotationTimestamp, aesKey, results, sign);
if (tx != null) results.add(tx);
} while (tx != null && tx.getInputs().size() == KeyTimeCoinSelector.MAX_SIMULTANEOUS_INPUTS);
return results;
}
@Nullable
private Transaction rekeyOneBatch(long timeSecs, @Nullable KeyParameter aesKey, List<Transaction> others) {
private Transaction rekeyOneBatch(long timeSecs, @Nullable KeyParameter aesKey, List<Transaction> others, boolean sign) {
lock.lock();
try {
// Build the transaction using some custom logic for our special needs. Last parameter to
@@ -4447,7 +4459,8 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
rekeyTx.setPurpose(Transaction.Purpose.KEY_ROTATION);
SendRequest req = SendRequest.forTx(rekeyTx);
req.aesKey = aesKey;
signTransaction(req);
if (sign)
signTransaction(req);
// KeyTimeCoinSelector should never select enough inputs to push us oversize.
checkState(rekeyTx.bitcoinSerialize().length < Transaction.MAX_STANDARD_TX_SIZE);
return rekeyTx;
@@ -4469,7 +4482,39 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha
* If not, redisplay the confirm window and try again.
*/
@Override
public ReentrantLock getLock() {
return lock;
public Lock getLock() {
return new Lock() {
@Override
public void lock() {
lock.lock();
keychainLock.readLock().lock();
}
@Override
public void lockInterruptibly() throws InterruptedException {
throw new UnsupportedOperationException();
}
@Override
public boolean tryLock() {
throw new UnsupportedOperationException();
}
@Override
public boolean tryLock(long l, TimeUnit unit) throws InterruptedException {
throw new UnsupportedOperationException();
}
@Override
public void unlock() {
keychainLock.readLock().unlock();
lock.unlock();
}
@Override
public Condition newCondition() {
throw new UnsupportedOperationException();
}
};
}
}

View File

@@ -300,8 +300,10 @@ public class StoredPaymentChannelClientStates implements WalletExtension {
ECKey.fromPrivate(storedState.getMyKey().toByteArray()),
Coin.valueOf(storedState.getValueToMe()),
Coin.valueOf(storedState.getRefundFees()), false);
if (storedState.hasCloseTransactionHash())
channel.close = containingWallet.getTransaction(new Sha256Hash(storedState.toByteArray()));
if (storedState.hasCloseTransactionHash()) {
Sha256Hash closeTxHash = new Sha256Hash(storedState.getCloseTransactionHash().toByteArray());
channel.close = containingWallet.getTransaction(closeTxHash);
}
putChannel(channel, false);
}
} finally {

View File

@@ -1269,9 +1269,11 @@ public class Script {
} catch (Exception e1) {
// There is (at least) one exception that could be hit here (EOFException, if the sig is too short)
// Because I can't verify there aren't more, we use a very generic Exception catch
log.warn("Signature checking failed! {}", e1.toString());
// Don't dump a stack trace here because we sometimes expect this to fail inside
// LocalTransactionSigner.signInputs().
// This RuntimeException occurs when signing as we run partial/invalid scripts to see if they need more
// signing work to be done inside LocalTransactionSigner.signInputs.
if (!e1.getMessage().contains("Reached past end of ASN.1 stream"))
log.warn("Signature checking failed! {}", e1.toString());
}
if (opcode == OP_CHECKSIG)

View File

@@ -195,7 +195,7 @@ public class PostgresFullPrunedBlockStore implements FullPrunedBlockStore {
private synchronized void maybeConnect() throws BlockStoreException {
try {
if (conn.get() != null)
if (conn.get() != null && !conn.get().isClosed())
return;
Properties props = new Properties();

View File

@@ -31,16 +31,22 @@ public class ExchangeRate implements Serializable {
public final Coin coin;
public final Fiat fiat;
/** Construct exchange rate. This amount of coin is worth that amount of fiat. */
public ExchangeRate(Coin coin, Fiat fiat) {
this.coin = coin;
this.fiat = fiat;
}
/** Construct exchange rate. One coin is worth this amount of fiat. */
public ExchangeRate(Fiat fiat) {
this.coin = Coin.COIN;
this.fiat = fiat;
}
/**
* Convert a coin amount to a fiat amount using this exchange rate.
* @throws ArithmeticException if the converted fiat amount is too high or too low.
*/
public Fiat coinToFiat(Coin convertCoin) {
// Use BigInteger because it's much easier to maintain full precision without overflowing.
final BigInteger converted = BigInteger.valueOf(convertCoin.value).multiply(BigInteger.valueOf(fiat.value))
@@ -51,6 +57,10 @@ public class ExchangeRate implements Serializable {
return Fiat.valueOf(fiat.currencyCode, converted.longValue());
}
/**
* Convert a fiat amount to a coin amount using this exchange rate.
* @throws ArithmeticException if the converted coin amount is too high or too low.
*/
public Coin fiatToCoin(Fiat convertFiat) {
checkArgument(convertFiat.currencyCode.equals(fiat.currencyCode), "Currency mismatch: %s vs %s",
convertFiat.currencyCode, fiat.currencyCode);
@@ -60,6 +70,10 @@ public class ExchangeRate implements Serializable {
if (converted.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0
|| converted.compareTo(BigInteger.valueOf(Long.MIN_VALUE)) < 0)
throw new ArithmeticException("Overflow");
return Coin.valueOf(converted.longValue());
try {
return Coin.valueOf(converted.longValue());
} catch (IllegalArgumentException x) {
throw new ArithmeticException("Overflow: " + x.getMessage());
}
}
}

View File

@@ -0,0 +1,7 @@
package org.bitcoinj.wallet;
/**
* Indicates that an attempt was made to upgrade a random wallet to deterministic, but there were no non-rotating
* random keys to use as source material for the seed. Add a non-compromised key first!
*/
public class AllRandomKeysRotating extends RuntimeException {}

View File

@@ -151,7 +151,7 @@ public class DefaultRiskAnalysis implements RiskAnalysis {
* Checks the output to see if the script violates a standardness rule. Not complete.
*/
public static RuleViolation isOutputStandard(TransactionOutput output) {
if (MIN_ANALYSIS_NONDUST_OUTPUT.compareTo(output.getValue()) > 0)
if (output.getValue().compareTo(MIN_ANALYSIS_NONDUST_OUTPUT) < 0)
return RuleViolation.DUST;
for (ScriptChunk chunk : output.getScriptPubKey().getChunks()) {
if (chunk.isPushData() && !chunk.isShortestPossiblePushData())

View File

@@ -1061,8 +1061,8 @@ public class DeterministicKeyChain implements EncryptableKeyChain {
DeterministicKey parent = detkey.getParent();
if (parent == null) continue;
if (detkey.getPath().size() <= treeSize) continue;
if (parent.equals(internalKey) && detkey.getChildNumber().i() > issuedInternalKeys) continue;
if (parent.equals(externalKey) && detkey.getChildNumber().i() > issuedExternalKeys) continue;
if (parent.equals(internalKey) && detkey.getChildNumber().i() >= issuedInternalKeys) continue;
if (parent.equals(externalKey) && detkey.getChildNumber().i() >= issuedExternalKeys) continue;
issuedKeys.add(detkey);
}
return issuedKeys;

View File

@@ -67,7 +67,7 @@ public class KeyChainGroup implements KeyBag {
private BasicKeyChain basic;
private NetworkParameters params;
private final List<DeterministicKeyChain> chains;
protected final List<DeterministicKeyChain> chains;
private final EnumMap<KeyChain.KeyPurpose, DeterministicKey> currentKeys;
// The map keys are the watching keys of the followed chains and values are the following chains
@@ -773,12 +773,14 @@ public class KeyChainGroup implements KeyBag {
* and you should provide the users encryption key.
* @return the DeterministicKeyChain that was created by the upgrade.
*/
public DeterministicKeyChain upgradeToDeterministic(long keyRotationTimeSecs, @Nullable KeyParameter aesKey) throws DeterministicUpgradeRequiresPassword {
checkState(chains.isEmpty());
public DeterministicKeyChain upgradeToDeterministic(long keyRotationTimeSecs, @Nullable KeyParameter aesKey) throws DeterministicUpgradeRequiresPassword, AllRandomKeysRotating {
checkState(basic.numKeys() > 0);
checkArgument(keyRotationTimeSecs >= 0);
ECKey keyToUse = basic.findOldestKeyAfter(keyRotationTimeSecs);
checkArgument(keyToUse != null, "All keys are considered rotating, so we cannot upgrade deterministically.");
// Subtract one because the key rotation time might have been set to the creation time of the first known good
// key, in which case, that's the one we want to find.
ECKey keyToUse = basic.findOldestKeyAfter(keyRotationTimeSecs - 1);
if (keyToUse == null)
throw new AllRandomKeysRotating();
if (keyToUse.isEncrypted()) {
if (aesKey == null) {
@@ -801,7 +803,12 @@ public class KeyChainGroup implements KeyBag {
throw new IllegalStateException("AES Key was provided but wallet is not encrypted.");
}
log.info("Auto-upgrading pre-HD wallet using oldest non-rotating private key");
if (chains.isEmpty()) {
log.info("Auto-upgrading pre-HD wallet to HD!");
} else {
log.info("Wallet with existing HD chain is being re-upgraded due to change in key rotation time.");
}
log.info("Instantiating new HD chain using oldest non-rotating private key (address: {})", keyToUse.toAddress(params));
byte[] entropy = checkNotNull(keyToUse.getSecretBytes());
// Private keys should be at least 128 bits long.
checkState(entropy.length >= DeterministicSeed.DEFAULT_SEED_ENTROPY_BITS / 8);
@@ -868,7 +875,9 @@ public class KeyChainGroup implements KeyBag {
public String toString(boolean includePrivateKeys) {
final StringBuilder builder = new StringBuilder();
if (basic != null) {
for (ECKey key : basic.getKeys())
List<ECKey> keys = basic.getKeys();
Collections.sort(keys, ECKey.AGE_COMPARATOR);
for (ECKey key : keys)
formatKeyWithAddress(includePrivateKeys, key, builder);
}
List<String> chainStrs = Lists.newLinkedList();

View File

@@ -62,6 +62,11 @@ public class CoinTest {
} catch (IllegalArgumentException e) {
}
try {
valueOf(Long.MIN_VALUE);
fail();
} catch (IllegalArgumentException e) {}
try {
valueOf(1, -1);
fail();

View File

@@ -207,6 +207,44 @@ public class PeerGroupTest extends TestWithPeerGroup {
peerGroup.awaitTerminated();
}
@Test
public void receiveTxBroadcastOnAddedWallet() throws Exception {
// Check that when we receive transactions on all our peers, we do the right thing.
peerGroup.startAsync();
peerGroup.awaitRunning();
// Create a peer.
InboundMessageQueuer p1 = connectPeer(1);
Wallet wallet2 = new Wallet(unitTestParams);
ECKey key2 = wallet2.freshReceiveKey();
Address address2 = key2.toAddress(unitTestParams);
peerGroup.addWallet(wallet2);
blockChain.addWallet(wallet2);
assertTrue(outbound(p1) instanceof BloomFilter);
assertTrue(outbound(p1) instanceof MemoryPoolMessage);
Coin value = COIN;
Transaction t1 = FakeTxBuilder.createFakeTx(unitTestParams, value, address2);
InventoryMessage inv = new InventoryMessage(unitTestParams);
inv.addTransaction(t1);
inbound(p1, inv);
assertTrue(outbound(p1) instanceof GetDataMessage);
inbound(p1, t1);
// Asks for dependency.
GetDataMessage getdata = (GetDataMessage) outbound(p1);
assertNotNull(getdata);
inbound(p1, new NotFoundMessage(unitTestParams, getdata.getItems()));
pingAndWait(p1);
assertEquals(value, wallet2.getBalance(Wallet.BalanceType.ESTIMATED));
peerGroup.stopAsync();
peerGroup.awaitTerminated();
}
@Test
public void singleDownloadPeer1() throws Exception {
// Check that we don't attempt to retrieve blocks on multiple peers.

View File

@@ -52,6 +52,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import static org.bitcoinj.core.Coin.*;
import static org.bitcoinj.core.Utils.HEX;
@@ -78,6 +79,7 @@ public class WalletTest extends TestWithWallet {
@Override
public void setUp() throws Exception {
super.setUp();
// TODO: Move these fields into the right tests so we don't create two wallets for every test case.
encryptedWallet = new Wallet(params);
myEncryptedAddress = encryptedWallet.freshReceiveKey().toAddress(params);
encryptedWallet.encrypt(PASSWORD1);
@@ -96,7 +98,6 @@ public class WalletTest extends TestWithWallet {
createMarriedWallet(threshold, numKeys, true);
}
private void createMarriedWallet(int threshold, int numKeys, boolean addSigners) throws BlockStoreException {
wallet = new Wallet(params);
blockStore = new MemoryBlockStore(params);
@@ -1952,43 +1953,45 @@ public class WalletTest extends TestWithWallet {
// Generate a ton of small outputs
StoredBlock block = new StoredBlock(makeSolvedTestBlock(blockStore, notMyAddr), BigInteger.ONE, 1);
int i = 0;
while (i <= CENT.divide(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE)) {
Transaction tx = createFakeTxWithChangeAddress(params, Transaction.REFERENCE_DEFAULT_MIN_TX_FEE, myAddress, notMyAddr);
Coin tenThousand = Coin.valueOf(10000);
while (i <= 100) {
Transaction tx = createFakeTxWithChangeAddress(params, tenThousand, myAddress, notMyAddr);
tx.getInput(0).setSequenceNumber(i++); // Keep every transaction unique
wallet.receiveFromBlock(tx, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, i);
}
Coin balance = wallet.getBalance();
// Create a spend that will throw away change (category 3 type 2 in which the change causes fee which is worth more than change)
SendRequest request1 = SendRequest.to(notMyAddr, CENT.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE).subtract(SATOSHI));
SendRequest request1 = SendRequest.to(notMyAddr, balance.subtract(SATOSHI));
wallet.completeTx(request1);
assertEquals(SATOSHI, request1.tx.getFee());
assertEquals(request1.tx.getInputs().size(), i); // We should have spent all inputs
// Give us one more input...
Transaction tx1 = createFakeTxWithChangeAddress(params, Transaction.REFERENCE_DEFAULT_MIN_TX_FEE, myAddress, notMyAddr);
Transaction tx1 = createFakeTxWithChangeAddress(params, tenThousand, myAddress, notMyAddr);
tx1.getInput(0).setSequenceNumber(i++); // Keep every transaction unique
wallet.receiveFromBlock(tx1, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, i);
// ... and create a spend that will throw away change (category 3 type 1 in which the change causes dust output)
SendRequest request2 = SendRequest.to(notMyAddr, CENT.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE).subtract(SATOSHI));
SendRequest request2 = SendRequest.to(notMyAddr, balance.subtract(SATOSHI));
wallet.completeTx(request2);
assertEquals(SATOSHI, request2.tx.getFee());
assertEquals(request2.tx.getInputs().size(), i - 1); // We should have spent all inputs - 1
// Give us one more input...
Transaction tx2 = createFakeTxWithChangeAddress(params, Transaction.REFERENCE_DEFAULT_MIN_TX_FEE, myAddress, notMyAddr);
Transaction tx2 = createFakeTxWithChangeAddress(params, tenThousand, myAddress, notMyAddr);
tx2.getInput(0).setSequenceNumber(i++); // Keep every transaction unique
wallet.receiveFromBlock(tx2, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, i);
// ... and create a spend that will throw away change (category 3 type 1 in which the change causes dust output)
// but that also could have been category 2 if it wanted
SendRequest request3 = SendRequest.to(notMyAddr, CENT.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE).subtract(SATOSHI));
SendRequest request3 = SendRequest.to(notMyAddr, CENT.add(tenThousand).subtract(SATOSHI));
wallet.completeTx(request3);
assertEquals(SATOSHI, request3.tx.getFee());
assertEquals(request3.tx.getInputs().size(), i - 2); // We should have spent all inputs - 2
//
SendRequest request4 = SendRequest.to(notMyAddr, CENT.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE).subtract(SATOSHI));
SendRequest request4 = SendRequest.to(notMyAddr, balance.subtract(SATOSHI));
request4.feePerKb = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.divide(request3.tx.bitcoinSerialize().length);
wallet.completeTx(request4);
assertEquals(SATOSHI, request4.tx.getFee());
@@ -1996,24 +1999,24 @@ public class WalletTest extends TestWithWallet {
// Give us a few more inputs...
while (wallet.getBalance().compareTo(CENT.multiply(2)) < 0) {
Transaction tx3 = createFakeTxWithChangeAddress(params, Transaction.REFERENCE_DEFAULT_MIN_TX_FEE, myAddress, notMyAddr);
Transaction tx3 = createFakeTxWithChangeAddress(params, tenThousand, myAddress, notMyAddr);
tx3.getInput(0).setSequenceNumber(i++); // Keep every transaction unique
wallet.receiveFromBlock(tx3, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, i);
}
// ...that is just slightly less than is needed for category 1
SendRequest request5 = SendRequest.to(notMyAddr, CENT.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE).subtract(SATOSHI));
SendRequest request5 = SendRequest.to(notMyAddr, CENT.add(tenThousand).subtract(SATOSHI));
wallet.completeTx(request5);
assertEquals(SATOSHI, request5.tx.getFee());
assertEquals(1, request5.tx.getOutputs().size()); // We should have no change output
// Give us one more input...
Transaction tx4 = createFakeTxWithChangeAddress(params, Transaction.REFERENCE_DEFAULT_MIN_TX_FEE, myAddress, notMyAddr);
Transaction tx4 = createFakeTxWithChangeAddress(params, tenThousand, myAddress, notMyAddr);
tx4.getInput(0).setSequenceNumber(i); // Keep every transaction unique
wallet.receiveFromBlock(tx4, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, i);
// ... that puts us in category 1 (no fee!)
SendRequest request6 = SendRequest.to(notMyAddr, CENT.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE).subtract(SATOSHI));
SendRequest request6 = SendRequest.to(notMyAddr, CENT.add(tenThousand).subtract(SATOSHI));
wallet.completeTx(request6);
assertEquals(ZERO, request6.tx.getFee());
assertEquals(2, request6.tx.getOutputs().size()); // We should have a change output
@@ -2284,7 +2287,6 @@ public class WalletTest extends TestWithWallet {
wallet = new Wallet(params);
// Watch out for wallet-initiated broadcasts.
MockTransactionBroadcaster broadcaster = new MockTransactionBroadcaster(wallet);
wallet.setKeyRotationEnabled(true);
// Send three cents to two different random keys, then add a key and mark the initial keys as compromised.
ECKey key1 = new ECKey();
key1.setCreationTimeSeconds(Utils.currentTimeSeconds() - (86400 * 2));
@@ -2299,14 +2301,11 @@ public class WalletTest extends TestWithWallet {
assertEquals(0, broadcaster.size());
assertFalse(wallet.isKeyRotating(key1));
// We got compromised! We have an old style random-only wallet. So let's upgrade to HD: for that we need a fresh
// random key that's not rotating as the wallet won't create a new seed for us, it'll just refuse to upgrade.
// We got compromised!
Utils.rollMockClock(1);
ECKey key3 = new ECKey();
wallet.importKey(key3);
wallet.setKeyRotationTime(compromiseTime);
assertTrue(wallet.isKeyRotating(key1));
wallet.maybeDoMaintenance(null, true);
wallet.doMaintenance(null, true);
Transaction tx = broadcaster.waitForTransactionAndSucceed();
final Coin THREE_CENTS = CENT.add(CENT).add(CENT);
@@ -2323,12 +2322,12 @@ public class WalletTest extends TestWithWallet {
// Now receive some more money to the newly derived address via a new block and check that nothing happens.
sendMoneyToWallet(wallet, CENT, toAddress, AbstractBlockChain.NewBlockType.BEST_CHAIN);
assertTrue(wallet.maybeDoMaintenance(null, true).get().isEmpty());
assertTrue(wallet.doMaintenance(null, true).get().isEmpty());
assertEquals(0, broadcaster.size());
// Receive money via a new block on key1 and ensure it shows up as a maintenance task.
sendMoneyToWallet(wallet, CENT, key1.toAddress(params), AbstractBlockChain.NewBlockType.BEST_CHAIN);
wallet.maybeDoMaintenance(null, true);
wallet.doMaintenance(null, true);
tx = broadcaster.waitForTransactionAndSucceed();
assertNotNull(wallet.findKeyFromPubHash(tx.getOutput(0).getScriptPubKey().getPubKeyHash()));
log.info("Unexpected thing: {}", tx);
@@ -2371,14 +2370,68 @@ public class WalletTest extends TestWithWallet {
// A day later, we get compromised.
Utils.rollMockClock(86400);
wallet.setKeyRotationTime(Utils.currentTimeSeconds());
wallet.setKeyRotationEnabled(true);
List<Transaction> txns = wallet.maybeDoMaintenance(null, false).get();
List<Transaction> txns = wallet.doMaintenance(null, false).get();
assertEquals(1, txns.size());
DeterministicKey watchKey2 = wallet.getWatchingKey();
assertNotEquals(watchKey1, watchKey2);
}
@SuppressWarnings("ConstantConditions")
@Test
public void keyRotationHD2() throws Exception {
// Check we handle the following scenario: a weak random key is created, then some good random keys are created
// but the weakness of the first isn't known yet. The wallet is upgraded to HD based on the weak key. Later, we
// find out about the weakness and set the rotation time to after the bad key's creation date. A new HD chain
// should be created based on the oldest known good key and the old chain + bad random key should rotate to it.
// We fix the private keys just to make the test deterministic (last byte differs).
Utils.setMockClock();
ECKey badKey = ECKey.fromPrivate(Utils.HEX.decode("00905b93f990267f4104f316261fc10f9f983551f9ef160854f40102eb71cffdbb"));
badKey.setCreationTimeSeconds(Utils.currentTimeSeconds());
Utils.rollMockClock(86400);
ECKey goodKey = ECKey.fromPrivate(Utils.HEX.decode("00905b93f990267f4104f316261fc10f9f983551f9ef160854f40102eb71cffdcc"));
goodKey.setCreationTimeSeconds(Utils.currentTimeSeconds());
// Do an upgrade based on the bad key.
final AtomicReference<List<DeterministicKeyChain>> fChains = new AtomicReference<List<DeterministicKeyChain>>();
KeyChainGroup kcg = new KeyChainGroup(params) {
{
fChains.set(chains);
}
};
kcg.importKeys(badKey, goodKey);
Utils.rollMockClock(86400);
wallet = new Wallet(params, kcg); // This avoids the automatic HD initialisation
assertTrue(fChains.get().isEmpty());
wallet.upgradeToDeterministic(null);
DeterministicKey badWatchingKey = wallet.getWatchingKey();
assertEquals(badKey.getCreationTimeSeconds(), badWatchingKey.getCreationTimeSeconds());
sendMoneyToWallet(wallet, CENT, badWatchingKey.toAddress(params), AbstractBlockChain.NewBlockType.BEST_CHAIN);
// Now we set the rotation time to the time we started making good keys. This should create a new HD chain.
wallet.setKeyRotationTime(goodKey.getCreationTimeSeconds());
List<Transaction> txns = wallet.doMaintenance(null, false).get();
assertEquals(1, txns.size());
Address output = txns.get(0).getOutput(0).getAddressFromP2PKHScript(params);
ECKey usedKey = wallet.findKeyFromPubHash(output.getHash160());
assertEquals(goodKey.getCreationTimeSeconds(), usedKey.getCreationTimeSeconds());
assertEquals(goodKey.getCreationTimeSeconds(), wallet.freshReceiveKey().getCreationTimeSeconds());
assertEquals("mrM3TpCnav5YQuVA1xLercCGJH4DXujMtv", usedKey.toAddress(params).toString());
DeterministicKeyChain c = fChains.get().get(1);
assertEquals(c.getEarliestKeyCreationTime(), goodKey.getCreationTimeSeconds());
assertEquals(2, fChains.get().size());
// Commit the maint txns.
wallet.commitTx(txns.get(0));
// Check next maintenance does nothing.
assertTrue(wallet.doMaintenance(null, false).get().isEmpty());
assertEquals(c, fChains.get().get(1));
assertEquals(2, fChains.get().size());
}
@Test(expected = IllegalArgumentException.class)
public void importOfHDKeyForbidden() throws Exception {
wallet.importKey(wallet.freshReceiveKey());
@@ -2396,13 +2449,12 @@ public class WalletTest extends TestWithWallet {
}
MockTransactionBroadcaster broadcaster = new MockTransactionBroadcaster(wallet);
wallet.setKeyRotationEnabled(true);
Date compromise = Utils.now();
Utils.rollMockClock(86400);
wallet.freshReceiveKey();
wallet.setKeyRotationTime(compromise);
wallet.maybeDoMaintenance(null, true);
wallet.doMaintenance(null, true);
Transaction tx = broadcaster.waitForTransactionAndSucceed();
final Coin valueSentToMe = tx.getValueSentToMe(wallet);

View File

@@ -50,4 +50,28 @@ public class ExchangeRateTest {
ExchangeRate rate = new ExchangeRate(Fiat.parseFiat("EUR", "500"));
rate.fiatToCoin(Fiat.parseFiat("USD", "1"));
}
@Test(expected = ArithmeticException.class)
public void fiatToCoinTooLarge() throws Exception {
ExchangeRate rate = new ExchangeRate(Fiat.parseFiat("XXX", "1"));
rate.fiatToCoin(Fiat.parseFiat("XXX", "21000001"));
}
@Test(expected = ArithmeticException.class)
public void fiatToCoinTooSmall() throws Exception {
ExchangeRate rate = new ExchangeRate(Fiat.parseFiat("XXX", "1"));
rate.fiatToCoin(Fiat.parseFiat("XXX", "-21000001"));
}
@Test(expected = ArithmeticException.class)
public void coinToFiatTooLarge() throws Exception {
ExchangeRate rate = new ExchangeRate(Fiat.parseFiat("XXX", "1000000000"));
rate.coinToFiat(Coin.parseCoin("1000000"));
}
@Test(expected = ArithmeticException.class)
public void coinToFiatTooSmall() throws Exception {
ExchangeRate rate = new ExchangeRate(Fiat.parseFiat("XXX", "1000000000"));
rate.coinToFiat(Coin.parseCoin("-1000000"));
}
}

View File

@@ -72,6 +72,14 @@ public class DeterministicKeyChainTest {
key3.sign(Sha256Hash.ZERO_HASH);
}
@Test
public void getKeys() throws Exception {
chain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
chain.getKey(KeyChain.KeyPurpose.CHANGE);
chain.maybeLookAhead();
assertEquals(2, chain.getKeys(false).size());
}
@Test
public void signMessage() throws Exception {
ECKey key = chain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);

View File

@@ -21,7 +21,7 @@
<parent>
<groupId>org.bitcoinj</groupId>
<artifactId>bitcoinj-parent</artifactId>
<version>0.12</version>
<version>0.12.1</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -6,7 +6,7 @@
<parent>
<groupId>org.bitcoinj</groupId>
<artifactId>bitcoinj-parent</artifactId>
<version>0.12</version>
<version>0.12.1</version>
</parent>
<artifactId>orchid</artifactId>

View File

@@ -4,7 +4,7 @@
<groupId>org.bitcoinj</groupId>
<artifactId>bitcoinj-parent</artifactId>
<version>0.12</version>
<version>0.12.1</version>
<packaging>pom</packaging>
<modules>

View File

@@ -21,7 +21,7 @@
<parent>
<groupId>org.bitcoinj</groupId>
<artifactId>bitcoinj-parent</artifactId>
<version>0.12</version>
<version>0.12.1</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -441,7 +441,6 @@ public class WalletTool {
rotationTimeSecs = options.valueOf(dateFlag).getTime() / 1000;
}
log.info("Setting wallet key rotation time to {}", rotationTimeSecs);
wallet.setKeyRotationEnabled(true);
wallet.setKeyRotationTime(rotationTimeSecs);
KeyParameter aesKey = null;
if (wallet.isEncrypted()) {
@@ -449,7 +448,7 @@ public class WalletTool {
if (aesKey == null)
return;
}
Futures.getUnchecked(wallet.maybeDoMaintenance(aesKey, true));
Futures.getUnchecked(wallet.doMaintenance(aesKey, true));
}
private static void encrypt() {

View File

@@ -56,7 +56,7 @@
<dependency>
<groupId>org.bitcoinj</groupId>
<artifactId>bitcoinj-core</artifactId>
<version>0.12</version>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>