mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-07-31 12:01:24 +00:00
Compare commits
31 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
9eab6977bd | ||
|
0fd3752dcf | ||
|
955c60180b | ||
|
f9c4e2153c | ||
|
146f082dfb | ||
|
23b3a637ab | ||
|
d2865f93e6 | ||
|
dd204fdbf5 | ||
|
2c44957b6d | ||
|
61626447f2 | ||
|
72f3900ca4 | ||
|
235478adb2 | ||
|
4a97212240 | ||
|
d99a4f887b | ||
|
b333f19983 | ||
|
b879606d8c | ||
|
81eff264cf | ||
|
ec306981e7 | ||
|
301f6c750d | ||
|
680221d577 | ||
|
32f6c5f330 | ||
|
a23b018b22 | ||
|
f83c93b627 | ||
|
aed2816bad | ||
|
9bfcb80516 | ||
|
f7a0979091 | ||
|
e0467da35c | ||
|
a5582461d7 | ||
|
6a3eaf31fe | ||
|
b5ac1d8a76 | ||
|
bf52cc86bb |
2
AUTHORS
2
AUTHORS
@@ -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>
|
@@ -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>
|
||||
|
@@ -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 {
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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) {
|
||||
|
@@ -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();
|
||||
|
@@ -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;
|
||||
|
@@ -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 + "/";
|
||||
|
||||
|
@@ -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:
|
||||
|
@@ -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();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
@@ -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)
|
||||
|
@@ -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();
|
||||
|
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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 {}
|
@@ -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())
|
||||
|
@@ -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;
|
||||
|
@@ -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();
|
||||
|
@@ -62,6 +62,11 @@ public class CoinTest {
|
||||
} catch (IllegalArgumentException e) {
|
||||
}
|
||||
|
||||
try {
|
||||
valueOf(Long.MIN_VALUE);
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {}
|
||||
|
||||
try {
|
||||
valueOf(1, -1);
|
||||
fail();
|
||||
|
@@ -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.
|
||||
|
@@ -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);
|
||||
|
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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>
|
||||
|
||||
|
@@ -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>
|
||||
|
2
pom.xml
2
pom.xml
@@ -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>
|
||||
|
@@ -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>
|
||||
|
||||
|
@@ -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() {
|
||||
|
@@ -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>
|
||||
|
Reference in New Issue
Block a user