54 Commits

Author SHA1 Message Date
Andreas Schildbach
e6372c5d7d Release 0.12.2 2014-11-15 11:27:36 +01:00
Andreas Schildbach
2d82de222e Fix just asking Wallet.doMaintenance() for if maintenance needs to be done wastes addresses. 2014-11-15 11:23:08 +01:00
Andreas Schildbach
5b62cc2fbb Tests for completing transactions with non-standard fees. 2014-11-14 17:31:34 +01:00
Andreas Schildbach
019c581447 Print fee with Transaction.toString(), if known. 2014-11-14 17:31:34 +01:00
Andreas Schildbach
463cda4965 Remove IOException from TestWithWallet. 2014-11-14 17:31:19 +01:00
Adam Mackler
72766c2b07 Clarify javadoc comments regarding whether HD derivations are hardened. 2014-11-14 17:15:17 +01:00
Mike Hearn
013d307f95 Orchid: switch out turtles for longclaw. Turtles has been defunct for some time. Longclaw is a new directory authority run out of Hong Kong by RiseUp:
https://lists.torproject.org/pipermail/tor-talk/2014-November/035650.html
2014-11-14 15:27:48 +01:00
Mike Hearn
f7c35df9f4 Fix a bug in the BIP 62 handling code that can cause a crash for any broadcast multisig transaction. 2014-11-14 14:13:18 +01:00
Mike Hearn
ce545065d8 Add an OP_RETURN convenience method on ScriptBuilder and convert unit tests to use it. 2014-11-14 14:13:12 +01:00
Mike Hearn
9225cf8c1f Fix a wrong lock, spotted by Andreas 2014-11-14 14:03:04 +01:00
Mike Hearn
8942680ec0 Wallet: give up on using read/write locks for the keychain, the re-entrancy rules are too hard to follow. Switch back to a regular lock. 2014-11-14 14:03:04 +01:00
Mike Hearn
cb78be8f4d Delete EncryptedPrivateKey, it's cruft left over from the HD wallets work. 2014-11-13 22:36:53 +01:00
Mike Hearn
6edfc89335 PeerGroup: add a removePeerFilterProvider to complement add. 2014-11-13 22:36:31 +01:00
Andreas Schildbach
2769bc32bb When reading wallets, catch another possible data inconsistency and throw UnreadableWalletException. 2014-11-13 22:35:56 +01:00
Andreas Schildbach
f89574c832 WalletTool: magic --output value ALL for easier emptying of wallets. 2014-11-13 22:35:29 +01:00
Andreas Schildbach
bd9e215a59 Fix question marks could not be used in payment protocol request URI. Removes the check for multiple question marks in the bitcoin URI.
Also see https://github.com/schildbach/bitcoin-wallet/issues/169
2014-11-13 22:35:12 +01:00
Wojciech Langiewicz
c5a2dc2551 Issue 586 fixed, 0BTC transaction with OP_RETURN will work. 2014-11-13 22:26:53 +01:00
Mike Hearn
d9ef834c62 Immediately advance current addresses instead of doing it lazily. This avoids a bug whereby an app might quit after using a change address, thus currentKey(CHANGE) == null and it gets reset to the last used address when the wallet is round-tripped.
Unit tests didn't catch this because they didn't simulate the app terminating after the send, and weren't explicitly checking that the change address was different, so improve tests to do those things.

Additionally implement marking as used for married wallets.
2014-11-04 23:46:31 +01:00
Mike Hearn
259e1604e9 Fix bug revealed by static analysis. 2014-11-04 23:40:15 +01:00
Wojciech Langiewicz
30089c9a72 Fixes issue 587: disables connecting to local bitcoin node during tests 2014-11-04 23:39:42 +01:00
Oscar Guindzberg
604b0131e7 Fix documentation 2014-11-04 23:38:59 +01:00
Andreas Schildbach
7fe37b49b4 Revert one of the occurences "Replace two occurences of pom versions with ${project.version}". On release-0.12, wallettemplate isn't part of the main project's versioning scheme.
This partly reverts commit ec306981e7.
2014-11-01 15:02:17 +01:00
Andreas Schildbach
5d12701259 Prepare 0.12.2-SNAPSHOT 2014-11-01 15:00:06 +01:00
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
44 changed files with 873 additions and 482 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.2</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,13 +44,16 @@ 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.*;
import java.util.concurrent.locks.ReentrantLock;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
@@ -123,6 +126,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 +259,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 +662,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 +766,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 +775,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 +874,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();
}
@@ -894,6 +907,20 @@ public class PeerGroup extends AbstractExecutionThreadService implements Transac
}
}
/**
* Opposite of {@link #addPeerFilterProvider(PeerFilterProvider)}. Again, don't use this for wallets. Does not
* trigger recalculation of the filter.
*/
public void removePeerFilterProvider(PeerFilterProvider provider) {
lock.lock();
try {
checkNotNull(provider);
checkArgument(peerFilterProviders.remove(provider));
} finally {
lock.unlock();
}
}
/**
* Unlinks the given wallet so it no longer receives broadcast transactions or has its transactions announced.
*/
@@ -902,6 +929,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 +1055,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 +1305,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 +1337,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;
@@ -379,7 +379,7 @@ public class Transaction extends ChildMessage implements Serializable {
}
/**
* Returns the difference of {@link Transaction#getValueSentFromMe(TransactionBag)} and {@link Transaction#getValueSentToMe(TransactionBag)}.
* Returns the difference of {@link Transaction#getValueSentToMe(TransactionBag)} and {@link Transaction#getValueSentFromMe(TransactionBag)}.
*/
public Coin getValue(TransactionBag wallet) throws ScriptException {
return getValueSentToMe(wallet).subtract(getValueSentFromMe(wallet));
@@ -704,6 +704,9 @@ public class Transaction extends ChildMessage implements Serializable {
}
s.append(String.format("%n"));
}
Coin fee = getFee();
if (fee != null)
s.append(" fee ").append(fee.toFriendlyString()).append(String.format("%n"));
return s.toString();
}

View File

@@ -253,7 +253,7 @@ public class TransactionOutput extends ChildMessage implements Serializable {
/**
* Returns the minimum value for this output to be considered "not dust", i.e. the transaction will be relayable
* and mined by default miners. For normal pay to address outputs, this is 5460 satoshis, the same as
* and mined by default miners. For normal pay to address outputs, this is 546 satoshis, the same as
* {@link Transaction#MIN_NONDUST_OUTPUT}.
*/
public Coin getMinNonDustValue() {

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.2";
/** 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:

File diff suppressed because it is too large Load Diff

View File

@@ -298,7 +298,9 @@ public class DeterministicKey extends ECKey {
}
/**
* Derives a child at the given index (note: not the "i" value).
* Derives a child at the given index using hardened derivation. Note: <code>index</code> is
* not the "i" value. If you want the softened derivation, then use instead
* <code>HDKeyDerivation.deriveChildKey(this, new ChildNumber(child, false))</code>.
*/
public DeterministicKey derive(int child) {
return HDKeyDerivation.deriveChildKey(this, new ChildNumber(child, true));

View File

@@ -1,129 +0,0 @@
/**
* Copyright 2013 Jim Burton.
*
* Licensed under the MIT license (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://opensource.org/licenses/mit-license.php
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.bitcoinj.crypto;
import com.google.common.base.Objects;
import java.util.Arrays;
/**
* <p>An EncryptedPrivateKey contains the information produced after encrypting the private key bytes of an ECKey.</p>
*
* <p>It contains two member variables - initialisationVector and encryptedPrivateBytes. The initialisationVector is
* a randomly chosen list of bytes that were used to initialise the AES block cipher when the private key bytes were encrypted.
* You need these for decryption. The encryptedPrivateBytes are the result of AES encrypting the private keys using
* an AES key that is derived from a user entered password. You need the password to recreate the AES key in order
* to decrypt these bytes.</p>
*/
public class EncryptedPrivateKey {
private byte[] initialisationVector = null;
private byte[] encryptedPrivateBytes = null;
/**
* Cloning constructor.
* @param encryptedPrivateKey EncryptedPrivateKey to clone.
*/
public EncryptedPrivateKey(EncryptedPrivateKey encryptedPrivateKey) {
setInitialisationVector(encryptedPrivateKey.getInitialisationVector());
setEncryptedPrivateBytes(encryptedPrivateKey.getEncryptedBytes());
}
/**
* @param initialisationVector
* @param encryptedPrivateKeys
*/
public EncryptedPrivateKey(byte[] initialisationVector, byte[] encryptedPrivateKeys) {
setInitialisationVector(initialisationVector);
setEncryptedPrivateBytes(encryptedPrivateKeys);
}
public byte[] getInitialisationVector() {
return initialisationVector;
}
/**
* Set the initialisationVector, cloning the bytes.
*
* @param initialisationVector
*/
public void setInitialisationVector(byte[] initialisationVector) {
if (initialisationVector == null) {
this.initialisationVector = null;
return;
}
byte[] cloneIV = new byte[initialisationVector.length];
System.arraycopy(initialisationVector, 0, cloneIV, 0, initialisationVector.length);
this.initialisationVector = cloneIV;
}
public byte[] getEncryptedBytes() {
return encryptedPrivateBytes;
}
/**
* Set the encrypted private key bytes, cloning them.
*
* @param encryptedPrivateBytes
*/
public void setEncryptedPrivateBytes(byte[] encryptedPrivateBytes) {
if (encryptedPrivateBytes == null) {
this.encryptedPrivateBytes = null;
return;
}
this.encryptedPrivateBytes = Arrays.copyOf(encryptedPrivateBytes, encryptedPrivateBytes.length);
}
@Override
public EncryptedPrivateKey clone() {
return new EncryptedPrivateKey(getInitialisationVector(), getEncryptedBytes());
}
@Override
public int hashCode() {
return com.google.common.base.Objects.hashCode(encryptedPrivateBytes, initialisationVector);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EncryptedPrivateKey other = (EncryptedPrivateKey) o;
return Objects.equal(initialisationVector, other.initialisationVector) &&
Objects.equal(encryptedPrivateBytes, other.encryptedPrivateBytes);
}
@Override
public String toString() {
return "EncryptedPrivateKey [initialisationVector=" + Arrays.toString(initialisationVector) + ", encryptedPrivateKey=" + Arrays.toString(encryptedPrivateBytes) + "]";
}
/**
* Clears all the EncryptedPrivateKey contents from memory (overwriting all data including PRIVATE KEYS).
* WARNING - this method irreversibly deletes the private key information.
*/
public void clear() {
if (encryptedPrivateBytes != null) {
Arrays.fill(encryptedPrivateBytes, (byte)0);
}
if (initialisationVector != null) {
Arrays.fill(initialisationVector, (byte)0);
}
}
}

View File

@@ -90,8 +90,12 @@ public final class HDKeyDerivation {
}
/**
* Derives a key given the "extended" child number, ie. with the 0x80000000 bit specifying whether to use hardened
* derivation or not.
* Derives a key given the "extended" child number, ie. the 0x80000000 bit of the value that you
* pass for <code>childNumber</code> will determine whether to use hardened derivation or not.
* Consider whether your code would benefit from the clarity of the equivalent, but explicit, form
* of this method that takes a <code>ChildNumber</code> rather than an <code>int</code>, for example:
* <code>deriveChildKey(parent, new ChildNumber(childNumber, true))</code>
* where the value of the hardened bit of <code>childNumber</code> is zero.
*/
public static DeterministicKey deriveChildKey(DeterministicKey parent, int childNumber) {
return deriveChildKey(parent, new ChildNumber(childNumber));

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

@@ -725,6 +725,10 @@ public class Script {
return Utils.decodeMPI(Utils.reverseBytes(chunk), false);
}
public boolean isOpReturn() {
return chunks.size() == 2 && chunks.get(0).equalsOpCode(OP_RETURN);
}
/**
* Exposes the script interpreter. Normally you should not use this directly, instead use
* {@link org.bitcoinj.core.TransactionInput#verify(org.bitcoinj.core.TransactionOutput)} or
@@ -1269,9 +1273,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

@@ -331,4 +331,14 @@ public class ScriptBuilder {
Collections.sort(pubkeys, ECKey.PUBKEY_COMPARATOR);
return ScriptBuilder.createMultiSigOutputScript(threshold, pubkeys);
}
/**
* Creates a script of the form OP_RETURN [data]. This feature allows you to attach a small piece of data (like
* a hash of something stored elsewhere) to a zero valued output which can never be spent and thus does not pollute
* the ledger.
*/
public static Script createOpReturnScript(byte[] data) {
checkArgument(data.length <= 40);
return new ScriptBuilder().op(OP_RETURN).data(data).build();
}
}

View File

@@ -81,13 +81,15 @@ public class ScriptChunk {
*/
public boolean isShortestPossiblePushData() {
checkState(isPushData());
if (data == null)
return true; // OP_N
if (data.length == 0)
return opcode == OP_0;
if (data.length == 1) {
byte b = data[0];
if (b >= 0x01 && b <= 0x10)
return opcode == OP_1 + b - 1;
if (b == 0x81)
if ((b & 0xFF) == 0x81)
return opcode == OP_1NEGATE;
}
if (data.length < OP_PUSHDATA1)
@@ -106,7 +108,6 @@ public class ScriptChunk {
checkState(data == null);
stream.write(opcode);
} else if (data != null) {
checkNotNull(data);
if (opcode < OP_PUSHDATA1) {
checkState(data.length == opcode);
stream.write(opcode);

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

@@ -389,6 +389,8 @@ public class WalletProtobufSerializer {
return readWallet(params, null, walletProto);
} catch (IOException e) {
throw new UnreadableWalletException("Could not parse input stream to protobuf", e);
} catch (IllegalStateException e) {
throw new UnreadableWalletException("Could not parse input stream to protobuf", e);
}
}

View File

@@ -80,6 +80,7 @@ public class TestWithPeerGroup extends TestWithNetworkConnections {
peerGroup = new PeerGroup(unitTestParams, blockChain, new BlockingClientManager());
peerGroup.setPingIntervalMsec(0); // Disable the pings as they just get in the way of most tests.
peerGroup.addWallet(wallet);
peerGroup.setUseLocalhostPeerWhenPossible(false); // Prevents from connecting to bitcoin nodes on localhost.
}
protected InboundMessageQueuer connectPeerWithoutVersionExchange(int id) throws Exception {

View File

@@ -23,7 +23,6 @@ import org.bitcoinj.store.MemoryBlockStore;
import org.bitcoinj.utils.BriefLogFormatter;
import javax.annotation.Nullable;
import java.io.IOException;
import static org.bitcoinj.testing.FakeTxBuilder.createFakeBlock;
import static org.bitcoinj.testing.FakeTxBuilder.createFakeTx;
@@ -62,7 +61,7 @@ public class TestWithWallet {
@Nullable
protected Transaction sendMoneyToWallet(Wallet wallet, Transaction tx, AbstractBlockChain.NewBlockType type)
throws IOException, VerificationException {
throws VerificationException {
if (type == null) {
// Pending/broadcast tx.
if (wallet.isPendingTransactionRelevant(tx))
@@ -77,22 +76,22 @@ public class TestWithWallet {
}
@Nullable
protected Transaction sendMoneyToWallet(Transaction tx, AbstractBlockChain.NewBlockType type) throws IOException, VerificationException {
protected Transaction sendMoneyToWallet(Transaction tx, AbstractBlockChain.NewBlockType type) throws VerificationException {
return sendMoneyToWallet(this.wallet, tx, type);
}
@Nullable
protected Transaction sendMoneyToWallet(Wallet wallet, Coin value, Address toAddress, AbstractBlockChain.NewBlockType type) throws IOException, VerificationException {
protected Transaction sendMoneyToWallet(Wallet wallet, Coin value, Address toAddress, AbstractBlockChain.NewBlockType type) throws VerificationException {
return sendMoneyToWallet(wallet, createFakeTx(params, value, toAddress), type);
}
@Nullable
protected Transaction sendMoneyToWallet(Wallet wallet, Coin value, ECKey toPubKey, AbstractBlockChain.NewBlockType type) throws IOException, VerificationException {
protected Transaction sendMoneyToWallet(Wallet wallet, Coin value, ECKey toPubKey, AbstractBlockChain.NewBlockType type) throws VerificationException {
return sendMoneyToWallet(wallet, createFakeTx(params, value, toPubKey), type);
}
@Nullable
protected Transaction sendMoneyToWallet(Coin value, AbstractBlockChain.NewBlockType type) throws IOException, VerificationException {
protected Transaction sendMoneyToWallet(Coin value, AbstractBlockChain.NewBlockType type) throws VerificationException {
return sendMoneyToWallet(this.wallet, createFakeTx(params, value, myAddress), type);
}
}

View File

@@ -153,7 +153,7 @@ public class BitcoinURI {
}
// Split off the address from the rest of the query parameters.
String[] addressSplitTokens = schemeSpecificPart.split("\\?");
String[] addressSplitTokens = schemeSpecificPart.split("\\?", 2);
if (addressSplitTokens.length == 0)
throw new BitcoinURIParseException("No data found after the bitcoin: prefix");
String addressToken = addressSplitTokens[0]; // may be empty!
@@ -163,12 +163,8 @@ public class BitcoinURI {
// Only an address is specified - use an empty '<name>=<value>' token array.
nameValuePairTokens = new String[] {};
} else {
if (addressSplitTokens.length == 2) {
// Split into '<name>=<value>' tokens.
nameValuePairTokens = addressSplitTokens[1].split("&");
} else {
throw new BitcoinURIParseException("Too many question marks in URI '" + uri + "'");
}
// Split into '<name>=<value>' tokens.
nameValuePairTokens = addressSplitTokens[1].split("&");
}
// Attempt to parse the rest of the URI parameters.

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,9 @@ public class KeyChainGroup implements KeyBag {
private BasicKeyChain basic;
private NetworkParameters params;
private final List<DeterministicKeyChain> chains;
protected final LinkedList<DeterministicKeyChain> chains;
// currentKeys is used for normal, non-multisig/married wallets. currentAddresses is used when we're handing out
// P2SH addresses. They're mutually exclusive.
private final EnumMap<KeyChain.KeyPurpose, DeterministicKey> currentKeys;
// The map keys are the watching keys of the followed chains and values are the following chains
@@ -81,7 +83,7 @@ public class KeyChainGroup implements KeyBag {
// mapped to redeem script hashes.
private LinkedHashMap<ByteString, RedeemData> marriedKeysRedeemData;
private EnumMap<KeyChain.KeyPurpose, Address> currentAddresses;
private final EnumMap<KeyChain.KeyPurpose, Address> currentAddresses;
@Nullable private KeyCrypter keyCrypter;
private int lookaheadSize = -1;
private int lookaheadThreshold = -1;
@@ -171,7 +173,7 @@ public class KeyChainGroup implements KeyBag {
DeterministicKeyChain> followingKeychains, int sigsRequiredToSpend, @Nullable KeyCrypter crypter) {
this.params = params;
this.basic = basicKeyChain == null ? new BasicKeyChain() : basicKeyChain;
this.chains = new ArrayList<DeterministicKeyChain>(checkNotNull(chains));
this.chains = new LinkedList<DeterministicKeyChain>(checkNotNull(chains));
this.keyCrypter = crypter;
this.currentKeys = currentKeys == null
? new EnumMap<KeyChain.KeyPurpose, DeterministicKey>(KeyChain.KeyPurpose.class)
@@ -459,6 +461,22 @@ public class KeyChainGroup implements KeyBag {
return marriedKeysRedeemData.get(ByteString.copyFrom(scriptHash));
}
public void markP2SHAddressAsUsed(Address address) {
checkState(isMarried());
checkArgument(address.isP2SHAddress());
RedeemData data = findRedeemDataFromScriptHash(address.getHash160());
if (data == null)
return; // Not our P2SH address.
for (ECKey key : data.keys) {
for (DeterministicKeyChain chain : chains) {
DeterministicKey k = chain.findKeyFromPubKey(key.getPubKey());
if (k == null) continue;
chain.markKeyAsUsed(k);
maybeMarkCurrentAddressAsUsed(address);
}
}
}
@Nullable
@Override
public ECKey findKeyFromPubHash(byte[] pubkeyHash) {
@@ -486,12 +504,27 @@ public class KeyChainGroup implements KeyBag {
}
}
/** If the given P2SH address is "current", advance it to a new one. */
private void maybeMarkCurrentAddressAsUsed(Address address) {
checkState(isMarried());
checkArgument(address.isP2SHAddress());
for (Map.Entry<KeyChain.KeyPurpose, Address> entry : currentAddresses.entrySet()) {
if (entry.getValue() != null && entry.getValue().equals(address)) {
log.info("Marking P2SH address as used: {}", address);
currentAddresses.put(entry.getKey(), freshAddress(entry.getKey()));
return;
}
}
}
/** If the given key is "current", advance the current key to a new one. */
private void maybeMarkCurrentKeyAsUsed(DeterministicKey key) {
// It's OK for currentKeys to be empty here: it means we're a married wallet and the key may be a part of a
// rotating chain.
for (Map.Entry<KeyChain.KeyPurpose, DeterministicKey> entry : currentKeys.entrySet()) {
if (entry.getValue() != null && entry.getValue().equals(key)) {
log.info("Marking key as used: {}", key);
currentKeys.put(entry.getKey(), null);
currentKeys.put(entry.getKey(), freshKey(entry.getKey()));
return;
}
}
@@ -773,12 +806,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 +836,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 +908,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

@@ -141,6 +141,8 @@ public class FilteredBlockAndPartialMerkleTreeTests extends TestWithPeerGroup {
super.setUp(blockStore);
peerGroup.addWallet(wallet);
peerGroup.setUseLocalhostPeerWhenPossible(false); // Prevents from connecting to bitcoin nodes on localhost.
blockChain.addWallet(wallet);
peerGroup.startAsync();

View File

@@ -36,6 +36,7 @@ import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.io.IOException;
import java.net.BindException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.util.*;
@@ -207,6 +208,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.
@@ -648,9 +687,24 @@ public class PeerGroupTest extends TestWithPeerGroup {
@Test
public void preferLocalPeer() throws IOException {
// Check that if we have a localhost port 8333 then it's used instead of the p2p network.
ServerSocket local = new ServerSocket(params.getPort(), 100, InetAddresses.forString("127.0.0.1"));
// Because we are using the same port (8333 or 18333) that is used by Satoshi client
// We have to consider 2 cases:
// 1. Test are executed on the same machine that is running full node / Satoshi client
// 2. Test are executed without any full node running locally
// We have to avoid to connecting to real and external services in unit tests
// So we skip this test in case we have already something running on port params.getPort()
// Check that if we have a localhost port 8333 or 18333 then it's used instead of the p2p network.
ServerSocket local = null;
try {
local = new ServerSocket(params.getPort(), 100, InetAddresses.forString("127.0.0.1"));
}
catch(BindException e) { // Port already in use, skipping this test.
return;
}
try {
peerGroup.setUseLocalhostPeerWhenPossible(true);
peerGroup.startAsync();
peerGroup.awaitRunning();
local.accept().close(); // Probe connect

View File

@@ -19,10 +19,14 @@ package org.bitcoinj.core;
import org.bitcoinj.core.Wallet.SendRequest;
import org.bitcoinj.crypto.*;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptBuilder;
import org.bitcoinj.script.ScriptOpCodes;
import org.bitcoinj.signers.StatelessTransactionSigner;
import org.bitcoinj.signers.TransactionSigner;
import org.bitcoinj.store.BlockStoreException;
import org.bitcoinj.store.MemoryBlockStore;
import org.bitcoinj.store.UnreadableWalletException;
import org.bitcoinj.store.WalletProtobufSerializer;
import org.bitcoinj.testing.*;
import org.bitcoinj.utils.ExchangeRate;
@@ -43,7 +47,6 @@ import org.slf4j.LoggerFactory;
import org.spongycastle.crypto.params.KeyParameter;
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.net.InetAddress;
import java.security.SecureRandom;
@@ -52,6 +55,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 +82,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 +101,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);
@@ -343,7 +347,6 @@ public class WalletTest extends TestWithWallet {
assertEquals("Wrong number of ALL.3", 1, wallet.getTransactions(true).size());
assertEquals(TransactionConfidence.Source.SELF, t2.getConfidence().getSource());
assertEquals(Transaction.Purpose.USER_PAYMENT, t2.getPurpose());
assertEquals(wallet.getChangeAddress(), t2.getOutput(1).getScriptPubKey().getToAddress(params));
// Do some basic sanity checks.
basicSanityChecks(wallet, t2, destination);
@@ -357,28 +360,31 @@ public class WalletTest extends TestWithWallet {
}
private void receiveATransaction(Wallet wallet, Address toAddress) throws Exception {
Coin v1 = COIN;
final ListenableFuture<Coin> availFuture = wallet.getBalanceFuture(v1, Wallet.BalanceType.AVAILABLE);
final ListenableFuture<Coin> estimatedFuture = wallet.getBalanceFuture(v1, Wallet.BalanceType.ESTIMATED);
receiveATransactionAmount(wallet, toAddress, COIN);
}
private void receiveATransactionAmount(Wallet wallet, Address toAddress, Coin amount) {
final ListenableFuture<Coin> availFuture = wallet.getBalanceFuture(amount, Wallet.BalanceType.AVAILABLE);
final ListenableFuture<Coin> estimatedFuture = wallet.getBalanceFuture(amount, Wallet.BalanceType.ESTIMATED);
assertFalse(availFuture.isDone());
assertFalse(estimatedFuture.isDone());
// Send some pending coins to the wallet.
Transaction t1 = sendMoneyToWallet(wallet, v1, toAddress, null);
Transaction t1 = sendMoneyToWallet(wallet, amount, toAddress, null);
Threading.waitForUserCode();
final ListenableFuture<Transaction> depthFuture = t1.getConfidence().getDepthFuture(1);
assertFalse(depthFuture.isDone());
assertEquals(ZERO, wallet.getBalance());
assertEquals(v1, wallet.getBalance(Wallet.BalanceType.ESTIMATED));
assertEquals(amount, wallet.getBalance(Wallet.BalanceType.ESTIMATED));
assertFalse(availFuture.isDone());
// Our estimated balance has reached the requested level.
assertTrue(estimatedFuture.isDone());
assertEquals(1, wallet.getPoolSize(Pool.PENDING));
assertEquals(0, wallet.getPoolSize(WalletTransaction.Pool.UNSPENT));
assertEquals(0, wallet.getPoolSize(Pool.UNSPENT));
// Confirm the coins.
sendMoneyToWallet(wallet, t1, AbstractBlockChain.NewBlockType.BEST_CHAIN);
assertEquals("Incorrect confirmed tx balance", v1, wallet.getBalance());
assertEquals("Incorrect confirmed tx PENDING pool size", 0, wallet.getPoolSize(WalletTransaction.Pool.PENDING));
assertEquals("Incorrect confirmed tx UNSPENT pool size", 1, wallet.getPoolSize(WalletTransaction.Pool.UNSPENT));
assertEquals("Incorrect confirmed tx balance", amount, wallet.getBalance());
assertEquals("Incorrect confirmed tx PENDING pool size", 0, wallet.getPoolSize(Pool.PENDING));
assertEquals("Incorrect confirmed tx UNSPENT pool size", 1, wallet.getPoolSize(Pool.UNSPENT));
assertEquals("Incorrect confirmed tx ALL pool size", 1, wallet.getTransactions(true).size());
Threading.waitForUserCode();
assertTrue(availFuture.isDone());
@@ -417,16 +423,18 @@ public class WalletTest extends TestWithWallet {
}
private void spendUnconfirmedChange(Wallet wallet, Transaction t2, KeyParameter aesKey) throws Exception {
if (wallet.getTransactionSigners().size() == 1) // don't bother reconfiguring the p2sh wallet
wallet = roundTrip(wallet);
Coin v3 = valueOf(0, 49);
assertEquals(v3, wallet.getBalance());
Wallet.SendRequest req = Wallet.SendRequest.to(new ECKey().toAddress(params), valueOf(0, 48));
req.aesKey = aesKey;
Address a = req.changeAddress = new ECKey().toAddress(params);
req.ensureMinRequiredFee = false;
req.shuffleOutputs = false;
wallet.completeTx(req);
Transaction t3 = req.tx;
assertEquals(a, t3.getOutput(1).getScriptPubKey().getToAddress(params));
assertNotEquals(t2.getOutput(1).getScriptPubKey().getToAddress(params),
t3.getOutput(1).getScriptPubKey().getToAddress(params));
assertNotNull(t3);
wallet.commitTx(t3);
assertTrue(wallet.isConsistent());
@@ -1500,7 +1508,7 @@ public class WalletTest extends TestWithWallet {
}
@Test
public void importAndEncrypt() throws IOException, InsufficientMoneyException {
public void importAndEncrypt() throws InsufficientMoneyException {
final ECKey key = new ECKey();
encryptedWallet.importKeysAndEncrypt(ImmutableList.of(key), PASSWORD1);
assertEquals(1, encryptedWallet.getImportedKeys().size());
@@ -1546,6 +1554,107 @@ public class WalletTest extends TestWithWallet {
wallet.completeTx(req);
}
@Test
public void opReturnOneOutputTest() throws Exception {
// Tests basic send of transaction with one output that doesn't transfer any value but just writes OP_RETURN.
receiveATransaction(wallet, myAddress);
Transaction tx = new Transaction(params);
Coin messagePrice = Coin.ZERO;
Script script = ScriptBuilder.createOpReturnScript("hello world!".getBytes());
tx.addOutput(messagePrice, script);
SendRequest request = Wallet.SendRequest.forTx(tx);
wallet.completeTx(request);
}
@Test
public void opReturnOneOutputWithValueTest() throws Exception {
// Tests basic send of transaction with one output that destroys coins and has an OP_RETURN.
receiveATransaction(wallet, myAddress);
Transaction tx = new Transaction(params);
Coin messagePrice = CENT;
Script script = ScriptBuilder.createOpReturnScript("hello world!".getBytes());
tx.addOutput(messagePrice, script);
SendRequest request = Wallet.SendRequest.forTx(tx);
wallet.completeTx(request);
}
@Test
public void opReturnTwoOutputsTest() throws Exception {
// Tests sending transaction where one output transfers BTC, the other one writes OP_RETURN.
receiveATransaction(wallet, myAddress);
Address notMyAddr = new ECKey().toAddress(params);
Transaction tx = new Transaction(params);
Coin messagePrice = Coin.ZERO;
Script script = ScriptBuilder.createOpReturnScript("hello world!".getBytes());
tx.addOutput(CENT, notMyAddr);
tx.addOutput(messagePrice, script);
SendRequest request = Wallet.SendRequest.forTx(tx);
wallet.completeTx(request);
}
@Test(expected = Wallet.MultipleOpReturnRequested.class)
public void twoOpReturnsPerTransactionTest() throws Exception {
// Tests sending transaction where there are 2 attempts to write OP_RETURN scripts - this should fail and throw MultipleOpReturnRequested.
receiveATransaction(wallet, myAddress);
Transaction tx = new Transaction(params);
Coin messagePrice = Coin.ZERO;
Script script1 = ScriptBuilder.createOpReturnScript("hello world 1!".getBytes());
Script script2 = ScriptBuilder.createOpReturnScript("hello world 2!".getBytes());
tx.addOutput(messagePrice, script1);
tx.addOutput(messagePrice, script2);
SendRequest request = Wallet.SendRequest.forTx(tx);
wallet.completeTx(request);
}
@Test(expected = Wallet.DustySendRequested.class)
public void sendDustTest() throws InsufficientMoneyException {
// Tests sending dust, should throw DustySendRequested.
Transaction tx = new Transaction(params);
Address notMyAddr = new ECKey().toAddress(params);
tx.addOutput(Transaction.MIN_NONDUST_OUTPUT.subtract(SATOSHI), notMyAddr);
SendRequest request = Wallet.SendRequest.forTx(tx);
wallet.completeTx(request);
}
@Test
public void sendMultipleCentsTest() throws Exception {
receiveATransactionAmount(wallet, myAddress, Coin.COIN);
Transaction tx = new Transaction(params);
Address notMyAddr = new ECKey().toAddress(params);
tx.addOutput(COIN.CENT.subtract(SATOSHI), notMyAddr);
tx.addOutput(COIN.CENT.subtract(SATOSHI), notMyAddr);
tx.addOutput(COIN.CENT.subtract(SATOSHI), notMyAddr);
tx.addOutput(COIN.CENT.subtract(SATOSHI), notMyAddr);
SendRequest request = Wallet.SendRequest.forTx(tx);
wallet.completeTx(request);
}
@Test(expected = Wallet.DustySendRequested.class)
public void sendDustAndOpReturnWithoutValueTest() throws Exception {
// Tests sending dust and OP_RETURN without value, should throw DustySendRequested because sending sending dust is not allowed in any case.
receiveATransactionAmount(wallet, myAddress, Coin.COIN);
Transaction tx = new Transaction(params);
Address notMyAddr = new ECKey().toAddress(params);
Script script = new ScriptBuilder().op(ScriptOpCodes.OP_RETURN).data("hello world!".getBytes()).build();
tx.addOutput(Coin.ZERO, script);
tx.addOutput(Coin.SATOSHI, notMyAddr);
SendRequest request = Wallet.SendRequest.forTx(tx);
wallet.completeTx(request);
}
@Test(expected = Wallet.DustySendRequested.class)
public void sendDustAndMessageWithValueTest() throws Exception {
//Tests sending dust and OP_RETURN with value, should throw DustySendRequested
receiveATransaction(wallet, myAddress);
Transaction tx = new Transaction(params);
Address notMyAddr = new ECKey().toAddress(params);
Script script = new ScriptBuilder().op(ScriptOpCodes.OP_RETURN).data("hello world!".getBytes()).build();
tx.addOutput(Coin.CENT, script);
tx.addOutput(Transaction.MIN_NONDUST_OUTPUT.subtract(SATOSHI), notMyAddr);
SendRequest request = Wallet.SendRequest.forTx(tx);
wallet.completeTx(request);
}
@Test
public void feeSolverAndCoinSelectionTest() throws Exception {
// Tests basic fee solving works
@@ -1952,43 +2061,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 +2107,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
@@ -2063,6 +2174,42 @@ public class WalletTest extends TestWithWallet {
assertEquals(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE, request.tx.getFee());
}
@Test
public void lowerThanDefaultFee() throws InsufficientMoneyException {
Coin fee = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.divide(10);
receiveATransactionAmount(wallet, myAddress, Coin.COIN);
SendRequest req = SendRequest.to(myAddress, Coin.CENT);
req.feePerKb = fee;
wallet.completeTx(req);
assertEquals(fee, req.tx.getFee());
wallet.commitTx(req.tx);
SendRequest emptyReq = SendRequest.emptyWallet(myAddress);
emptyReq.feePerKb = fee;
emptyReq.emptyWallet = true;
emptyReq.coinSelector = AllowUnconfirmedCoinSelector.get();
wallet.completeTx(emptyReq);
assertEquals(fee, emptyReq.tx.getFee());
wallet.commitTx(emptyReq.tx);
}
@Test
public void higherThanDefaultFee() throws InsufficientMoneyException {
Coin fee = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.multiply(10);
receiveATransactionAmount(wallet, myAddress, Coin.COIN);
SendRequest req = SendRequest.to(myAddress, Coin.CENT);
req.feePerKb = fee;
wallet.completeTx(req);
assertEquals(fee, req.tx.getFee());
wallet.commitTx(req.tx);
SendRequest emptyReq = SendRequest.emptyWallet(myAddress);
emptyReq.feePerKb = fee;
emptyReq.emptyWallet = true;
emptyReq.coinSelector = AllowUnconfirmedCoinSelector.get();
wallet.completeTx(emptyReq);
assertEquals(fee, emptyReq.tx.getFee());
wallet.commitTx(emptyReq.tx);
}
@Test
public void feePerKbCategoryJumpTest() throws Exception {
// Simple test of boundary condition on fee per kb in category fee solver
@@ -2284,7 +2431,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 +2445,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 +2466,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);
@@ -2341,8 +2484,7 @@ public class WalletTest extends TestWithWallet {
// We don't attempt to race an attacker against unconfirmed transactions.
// Now round-trip the wallet and check the protobufs are storing the data correctly.
Protos.Wallet protos = new WalletProtobufSerializer().walletToProto(wallet);
wallet = new WalletProtobufSerializer().readWallet(params, null, protos);
wallet = roundTrip(wallet);
tx = wallet.getTransaction(tx.getHash());
checkNotNull(tx);
@@ -2357,6 +2499,11 @@ public class WalletTest extends TestWithWallet {
assertArrayEquals(address.getHash160(), tx.getOutput(0).getScriptPubKey().getPubKeyHash());
}
private Wallet roundTrip(Wallet wallet) throws UnreadableWalletException {
Protos.Wallet protos = new WalletProtobufSerializer().walletToProto(wallet);
return new WalletProtobufSerializer().readWallet(params, null, protos);
}
@Test
public void keyRotationHD() throws Exception {
// Test that if we rotate an HD chain, a new one is created and all arrivals on the old keys are moved.
@@ -2371,14 +2518,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 +2597,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);
@@ -2614,8 +2814,7 @@ public class WalletTest extends TestWithWallet {
TransactionSigner signer = new NopTransactionSigner(true);
wallet.addTransactionSigner(signer);
assertEquals(2, wallet.getTransactionSigners().size());
Protos.Wallet protos = new WalletProtobufSerializer().walletToProto(wallet);
wallet = new WalletProtobufSerializer().readWallet(params, null, protos);
wallet = roundTrip(wallet);
assertEquals(2, wallet.getTransactionSigners().size());
assertTrue(wallet.getTransactionSigners().get(1).isReady());
}

View File

@@ -305,23 +305,6 @@ public class BitcoinURITest {
assertEquals("aardvark=zebra", new BitcoinURI(MainNetParams.get(), BitcoinURI.BITCOIN_SCHEME + ":"
+ MAINNET_GOOD_ADDRESS + "?label=aardvark=zebra").getLabel());
}
/**
* Handles case when there are too many question marks
*
* @throws BitcoinURIParseException
* If something goes wrong
*/
@Test
public void testBad_TooManyQuestionMarks() throws BitcoinURIParseException {
try {
testObject = new BitcoinURI(MainNetParams.get(), BitcoinURI.BITCOIN_SCHEME + ":" + MAINNET_GOOD_ADDRESS
+ "?label=aardvark?message=zebra");
fail("Expecting BitcoinURIParseException");
} catch (BitcoinURIParseException e) {
assertTrue(e.getMessage().contains("Too many question marks"));
}
}
/**
* Handles unknown fields (required and not required)
@@ -409,4 +392,13 @@ public class BitcoinURITest {
assertEquals(ImmutableList.of(), uri.getPaymentRequestUrls());
assertNotNull(uri.getAddress());
}
@Test
public void testUnescapedPaymentProtocolReq() throws Exception {
BitcoinURI uri = new BitcoinURI(TestNet3Params.get(),
"bitcoin:?r=https://merchant.com/pay.php?h%3D2a8628fc2fbe");
assertEquals("https://merchant.com/pay.php?h=2a8628fc2fbe", uri.getPaymentRequestUrl());
assertEquals(ImmutableList.of("https://merchant.com/pay.php?h=2a8628fc2fbe"), uri.getPaymentRequestUrls());
assertNull(uri.getAddress());
}
}

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

@@ -17,6 +17,7 @@
package org.bitcoinj.wallet;
import com.google.common.collect.Lists;
import org.bitcoinj.core.*;
import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.script.ScriptBuilder;
@@ -38,7 +39,7 @@ public class DefaultRiskAnalysisTest {
private static final NetworkParameters params = MainNetParams.get();
private Wallet wallet;
private final int TIMESTAMP = 1384190189;
private ECKey key1;
private static final ECKey key1 = new ECKey();
private final ImmutableList<Transaction> NO_DEPS = ImmutableList.of();
@Before
@@ -54,7 +55,6 @@ public class DefaultRiskAnalysisTest {
return TIMESTAMP;
}
};
key1 = new ECKey();
}
@Test
@@ -161,4 +161,25 @@ public class DefaultRiskAnalysisTest {
tx.addOutput(new TransactionOutput(params, null, COIN, nonStandardScript));
assertEquals(DefaultRiskAnalysis.RuleViolation.SHORTEST_POSSIBLE_PUSHDATA, DefaultRiskAnalysis.isStandard(tx));
}
@Test
public void standardOutputs() throws Exception {
Transaction tx = new Transaction(params);
tx.addInput(params.getGenesisBlock().getTransactions().get(0).getOutput(0));
// A pay to address output
tx.addOutput(Coin.CENT, ScriptBuilder.createOutputScript(key1.toAddress(params)));
// A pay to pubkey output
tx.addOutput(Coin.CENT, ScriptBuilder.createOutputScript(key1));
tx.addOutput(Coin.CENT, ScriptBuilder.createOutputScript(key1));
// 1-of-2 multisig output.
ImmutableList<ECKey> keys = ImmutableList.of(key1, new ECKey());
tx.addOutput(Coin.CENT, ScriptBuilder.createMultiSigOutputScript(1, keys));
// 2-of-2 multisig output.
tx.addOutput(Coin.CENT, ScriptBuilder.createMultiSigOutputScript(2, keys));
// P2SH
tx.addOutput(Coin.CENT, ScriptBuilder.createP2SHOutputScript(1, keys));
// OP_RETURN
tx.addOutput(Coin.CENT, ScriptBuilder.createOpReturnScript("hi there".getBytes()));
assertEquals(RiskAnalysis.Result.OK, DefaultRiskAnalysis.FACTORY.create(wallet, tx, NO_DEPS).analyze());
}
}

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.2</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.2</version>
</parent>
<artifactId>orchid</artifactId>

View File

@@ -22,7 +22,7 @@ public class TrustedAuthorities {
"authority tor26 v1 orport=443 v3ident=14C131DFC5C6F93646BE72FA1401C02A8DF2E8B4 86.59.21.38:80 847B 1F85 0344 D787 6491 A548 92F9 0493 4E4E B85D",
"authority dizum orport=443 v3ident=E8A9C45EDE6D711294FADF8E7951F4DE6CA56B58 194.109.206.212:80 7EA6 EAD6 FD83 083C 538F 4403 8BBF A077 587D D755",
"authority Tonga orport=443 bridge no-v2 82.94.251.203:80 4A0C CD2D DC79 9508 3D73 F5D6 6710 0C8A 5831 F16D",
"authority turtles orport=9090 no-v2 v3ident=27B6B5996C426270A5C95488AA5BCEB6BCC86956 76.73.17.194:9030 F397 038A DC51 3361 35E7 B80B D99C A384 4360 292B",
"authority longclaw orport=9090 no-v2 v3ident=23D15D965BC35114467363C165C4F724B64B4F66 202.85.227.202:80 74A9 1064 6BCE EFBC D2E8 74FC 1DC9 9743 0F96 8145",
"authority dannenberg orport=443 no-v2 v3ident=585769C78764D58426B8B52B6651A5A71137189A 193.23.244.244:80 7BE6 83E6 5D48 1413 21C5 ED92 F075 C553 64AC 7123",
"authority urras orport=80 no-v2 v3ident=80550987E1D626E3EBA5E5E75A458DE0626D088C 208.83.223.34:443 0AD3 FA88 4D18 F89E EA2D 89C0 1937 9E0E 7FD9 4417",
"authority maatuska orport=80 no-v2 v3ident=49015F787433103580E3B66A1707A00E60F2D15B 171.25.193.9:443 BD6A 8292 55CB 08E6 6FBE 7D37 4836 3586 E46B 3810",

View File

@@ -4,7 +4,7 @@
<groupId>org.bitcoinj</groupId>
<artifactId>bitcoinj-parent</artifactId>
<version>0.12</version>
<version>0.12.2</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.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -18,6 +18,7 @@
package org.bitcoinj.tools;
import org.bitcoinj.core.*;
import org.bitcoinj.core.Wallet.BalanceType;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.crypto.KeyCrypterException;
import org.bitcoinj.crypto.MnemonicCode;
@@ -441,7 +442,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 +449,7 @@ public class WalletTool {
if (aesKey == null)
return;
}
Futures.getUnchecked(wallet.maybeDoMaintenance(aesKey, true));
Futures.getUnchecked(wallet.doMaintenance(aesKey, true));
}
private static void encrypt() {
@@ -507,7 +507,11 @@ public class WalletTool {
}
String destination = parts[0];
try {
Coin value = parseCoin(parts[1]);
Coin value;
if ("ALL".equalsIgnoreCase(parts[1]))
value = wallet.getBalance(BalanceType.ESTIMATED);
else
value = parseCoin(parts[1]);
if (destination.startsWith("0")) {
// Treat as a raw public key.
byte[] pubKey = new BigInteger(destination, 16).toByteArray();

View File

@@ -27,9 +27,11 @@ Usage: wallet-tool --flags action-name
send Creates and broadcasts a transaction from the given wallet.
Requires either --output or --payment-request to be specified.
If --output is specified, a transaction is created from the provided output
from this wallet and broadcasted, eg:
from this wallet and broadcasted, e.g.:
--output=1GthXFQMktFLWdh5EPNGqbq3H6WdG8zsWj:1.245
You can repeat --output=address:value multiple times.
There is a magic value ALL which empties the wallet to that address, e.g.:
--output=1GthXFQMktFLWdh5EPNGqbq3H6WdG8zsWj:ALL
If the output destination starts with 04 and is 65 or 33 bytes long it will be
treated as a public key instead of an address and the send will use
<key> CHECKSIG as the script.
@@ -78,4 +80,4 @@ will be printed. Waiting occurs after the --action is performed, if any is speci
--waitfor=EVER Never quit.
--waitfor=WALLET_TX Any transaction that sends coins to or from the wallet.
--waitfor=BLOCK A new block that builds on the best chain.
--waitfor=BALANCE Waits until the wallets balance meets the --condition.\n";
--waitfor=BALANCE Waits until the wallets balance meets the --condition.\n";

View File

@@ -56,7 +56,7 @@
<dependency>
<groupId>org.bitcoinj</groupId>
<artifactId>bitcoinj-core</artifactId>
<version>0.12</version>
<version>0.12.2</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>