mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-08-02 00:31:24 +00:00
Compare commits
54 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
e6372c5d7d | ||
|
2d82de222e | ||
|
5b62cc2fbb | ||
|
019c581447 | ||
|
463cda4965 | ||
|
72766c2b07 | ||
|
013d307f95 | ||
|
f7c35df9f4 | ||
|
ce545065d8 | ||
|
9225cf8c1f | ||
|
8942680ec0 | ||
|
cb78be8f4d | ||
|
6edfc89335 | ||
|
2769bc32bb | ||
|
f89574c832 | ||
|
bd9e215a59 | ||
|
c5a2dc2551 | ||
|
d9ef834c62 | ||
|
259e1604e9 | ||
|
30089c9a72 | ||
|
604b0131e7 | ||
|
7fe37b49b4 | ||
|
5d12701259 | ||
|
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.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>
|
||||
|
@@ -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,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();
|
||||
|
@@ -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();
|
||||
}
|
||||
|
||||
|
@@ -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() {
|
||||
|
@@ -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 + "/";
|
||||
|
||||
|
@@ -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
@@ -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));
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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));
|
||||
|
@@ -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 {
|
||||
|
@@ -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)
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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();
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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 {
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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.
|
||||
|
@@ -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,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();
|
||||
|
@@ -62,6 +62,11 @@ public class CoinTest {
|
||||
} catch (IllegalArgumentException e) {
|
||||
}
|
||||
|
||||
try {
|
||||
valueOf(Long.MIN_VALUE);
|
||||
fail();
|
||||
} catch (IllegalArgumentException e) {}
|
||||
|
||||
try {
|
||||
valueOf(1, -1);
|
||||
fail();
|
||||
|
@@ -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();
|
||||
|
@@ -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
|
||||
|
@@ -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());
|
||||
}
|
||||
|
@@ -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());
|
||||
}
|
||||
}
|
||||
|
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
@@ -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());
|
||||
}
|
||||
}
|
||||
|
@@ -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.2</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.2</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>orchid</artifactId>
|
||||
|
@@ -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",
|
||||
|
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.2</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.2</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
@@ -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();
|
||||
|
@@ -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";
|
||||
|
@@ -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>
|
||||
|
Reference in New Issue
Block a user