45 Commits

Author SHA1 Message Date
Andreas Schildbach
2733004e71 Release 0.11.2 2014-04-22 15:49:51 +02:00
Andreas Schildbach
34dcef91a9 Fix size of encrypted bytes when encrypting private keys. Also change decrypting to use similar code. 2014-04-22 15:12:39 +02:00
Andreas Schildbach
b8708c4680 Fix parsing of empty labels and messages, and parsing of labels and messages with an unescaped equals sign in their value. 2014-04-22 15:09:49 +02:00
Mike Hearn
eceae231e8 Fix a crash that can occur if a peer reports a chain height of zero (this is a protocol violation but such crashes were seen in the wild). 2014-04-22 15:07:01 +02:00
Andreas Schildbach
30cc5c6a12 Adjust MIN_NONDUST_OUTPUT down to 546 only for risk analysis. This is required because we start seeing more and more transactions using the new fee rules introduced with Bitcoin Core 0.9. 2014-04-18 15:44:25 +02:00
Mike Hearn
4faa8b486c TransactionInput: verify(): don't crash if the given output has no parent. Clears a static analysis warning. 2014-04-18 15:37:38 +02:00
Andreas Schildbach
ea8e122cef Cheap test to see if an input stream is a wallet. 2014-04-18 15:36:18 +02:00
Mike Hearn
1bc77f843b More mock clock conversions, to avoid failures when running test cases independently.
Probably we should be injecting a mock Clock class so it goes away at the end of each test, but this would complicate the API somewhat.
2014-04-18 15:36:18 +02:00
Mike Hearn
60b0d917e7 Fix unit test broken by less aggressive backoff time. 2014-04-18 15:28:15 +02:00
Mike Hearn
95009f0310 PeerGroup: tweak global backoff to be faster. 2014-04-18 15:19:55 +02:00
Mike Hearn
86e63fb048 PeerGroup: bugfixes to backoff.
1) Do the wait even on the exception path so if discoverPeers throws, we don't bypass the sleep.
2) Move some field accesses inside the lock.

Resolves issue 527.
2014-04-18 15:18:59 +02:00
Andreas Schildbach
f399dafefc Fix race of mock clock with current time if tests are all run sequentially. This commit requires you to use one of the setMockClock() variants before being able to roll it. 2014-04-18 15:18:58 +02:00
Andreas Schildbach
d5ffe88cff In Transaction.toString(): For outpoints, show the pubkey-hash of the connected output (if available). This makes it easier to debug wallets. 2014-04-18 15:14:41 +02:00
Mike Hearn
635002ce11 BloomFilter: javadoc updates 2014-04-18 15:14:14 +02:00
Mike Rosseel
1e58cdc4cf mark didn't really make a mark 2014-04-18 15:13:05 +02:00
Andreas Schildbach
4eb792c74d Allow shutting down wallet auto-saving. 2014-04-18 15:09:27 +02:00
monk
cae096295f Initiliaze/assign sendResult. Prevents NullPointerException and app from crashing when sending money out. 2014-04-18 15:09:11 +02:00
Mike Hearn
de58cb0444 PaymentSession: extract names from S/MIME certificates as well as SSL certs. 2014-04-18 15:06:54 +02:00
Mike Hearn
1a15ae2f52 ECKey: fix bug where creation time was lost when encrypting/decrypting. 2014-04-18 15:06:14 +02:00
Andreas Schildbach
25c332a71b Include the hash160 of addresses in Wallet.toString(). This makes it easier to debug wallets just from the dump. 2014-04-18 15:04:11 +02:00
Mike Hearn
e5677c2f53 Wallet: disallow adding of keys that don't match the wallet's encryption state. 2014-04-18 15:02:03 +02:00
Andreas Schildbach
9010b9a411 Remove incorrect execution of OP_0. That code was never reached, because OP_0 is not an opcode in terms of chunk.isOpCode()). However, it lead to believe that OP_0 pushes the vector [0], rather than correctly the empty vector to the stack.
Because the code was never executed, this bug could never trigger a test. Afaict, script.cpp does not have the corresponding case in its switch block.
2014-04-18 14:59:18 +02:00
Andreas Schildbach
74cf34db82 Add script test to prove that OP_0 evaluates as the empty vector, rather than [0]. Also adds debug output in case an script_invalid.json test fails. 2014-04-18 14:56:24 +02:00
Mike Hearn
16aeea9104 Correct maven instruction in the README file and make ForwardingService work on mainnet again by fixing command line arg parsing.
Resolves issue 523.
2014-04-18 14:53:26 +02:00
Mike Hearn
8fc63e482e Hex dump pending transactions that double spend each other. 2014-04-18 14:50:22 +02:00
Andreas Schildbach
9592f25ba1 Fix method name in SendRequest.aesKey javadoc. 2014-04-18 14:48:35 +02:00
Diego Basch
afff0f701d added path to files on the Mac 2014-04-18 14:46:36 +02:00
Andreas Schildbach
38e103feea Print available balance in Wallet.toString(), rather than the redundant number of Satoshis. 2014-04-18 14:41:51 +02:00
Peter Todd
b3e6a23f0e Update BIP URLs to new github repo 2014-04-18 14:41:23 +02:00
Andreas Schildbach
fd6ceac30c Fix crash in case wallet is so inconsistent that even .toString() fails. 2014-04-18 14:37:33 +02:00
Andreas Schildbach
112f6ab624 Prepare 0.11.2-SNAPSHOT 2014-04-18 14:26:09 +02:00
Mike Hearn
60199f810a Build a bundled JAR for the core in parallel to the normal unbundled JAR. Helps people who don't use Maven for some reason, like Jython/JRuby users. 2014-03-05 17:32:06 +01:00
Andreas Schildbach
95abd140f0 Release 0.11.1 2014-02-23 13:34:40 +01:00
Mike Hearn
e783895dd3 PeerGroup: fix a regression that stopped Bloom filters being sent when a key is added, and add a unit test covering that behaviour.
Resolves issue 524.
2014-02-23 13:21:55 +01:00
Andreas Schildbach
cc89cd0d16 Don't throw just because the name of the CA cannot be determined. 2014-02-22 16:11:54 +01:00
Andreas Schildbach
28a5d8abbc Fix handling of defaults when parsing the payment message. Bitpay is leaving out the payment details version which was handled incorrectly. Adds a testcase for the defaults. 2014-02-21 13:54:20 +01:00
Mike Hearn
3ffcce49f9 PaymentSession: some bug fixes and new accessors. 2014-02-21 13:53:50 +01:00
Andreas Schildbach
f581a53aa0 Add method to clean up the wallet.
Currently, it just removes risky pending transaction from the wallet and only if their outputs have not been spent. Includes unit-tests by Miron Cuperman.
2014-02-20 22:37:42 +01:00
Matt Corallo
a5bc2f3794 Add isStandard risk analysis.
This is currently only to deal with recent spam, especially dust sybil spam. Includes an unit-test by Andreas Schildbach.
2014-02-20 22:37:31 +01:00
Mike Hearn
5543b166cc Payment protocol: Expose a friendly/display name for validating CA 2014-02-19 09:52:16 +01:00
Andreas Schildbach
3755835d11 When printing the wallet, apply a sensible order to transaction pools. Again, it helps debugging of wallet problems. 2014-02-19 09:50:40 +01:00
Andreas Schildbach
0ee3dca7a0 Move pending transactions to the top of a wallet printout. It helps debugging, as pending transactions happen to attract most problems. 2014-02-19 09:50:03 +01:00
Andreas Schildbach
1250aa8e3d Prepare 0.11.1-SNAPSHOT 2014-02-06 10:33:56 +01:00
Mike Hearn
410d4547a7 Release 0.11 2014-02-04 11:30:55 +01:00
Mike Hearn
71391c1271 Fix Maven enforcer hashes. 2014-02-04 11:30:55 +01:00
52 changed files with 807 additions and 332 deletions

7
README
View File

@@ -11,10 +11,13 @@ find your unzipped Maven install directory.
Now try running one of the example apps:
cd examples
mvn exec:java -Dexec.mainClass=com.google.bitcoin.examples.ForwardingService <insert a bitcoin address here>
mvn exec:java -Dexec.mainClass=com.google.bitcoin.examples.ForwardingService -Dexec.args="<insert a bitcoin address here>"
It will download the block chain and eventually print a Bitcoin address. If you send coins to it,
it will forward them on to the address you specified.
it will forward them on to the address you specified. Note that this example app does not use
checkpointing, so the initial chain sync will be pretty slow. You can make an app that starts up and
does the initial sync much faster by including a checkpoints file; see the documentation for
more info on this.
Now you are ready to follow the tutorial:

View File

@@ -22,7 +22,7 @@
<parent>
<groupId>com.google</groupId>
<artifactId>bitcoinj-parent</artifactId>
<version>0.11-SNAPSHOT</version>
<version>0.11.2</version>
</parent>
<artifactId>bitcoinj</artifactId>
@@ -152,12 +152,11 @@
<urns>
<urn>com.google.code.findbugs:jsr305:1.3.9:jar:null:compile:40719ea6961c0cb6afaeb6a921eaa1f6afd4cfdf</urn>
<urn>com.google.guava:guava:13.0.1:jar:null:compile:0d6f22b1e60a2f1ef99e22c9f5fde270b2088365</urn>
<urn>com.google.protobuf:protobuf-java:2.4.1:jar:null:compile:0c589509ec6fd86d5d2fda37e07c08538235d3b9</urn>
<urn>com.google.protobuf:protobuf-java:2.5.0:jar:null:compile:a10732c76bfacdbd633a7eb0f7968b1059a65dfa</urn>
<urn>com.h2database:h2:1.3.167:jar:null:compile:d3867d586f087e53eb12fc65e5693d8ee9a5da17</urn>
<urn>com.lambdaworks:scrypt:1.3.3:jar:null:compile:06d6813de41e177189e1722717979b4fb5454b1d</urn>
<urn>com.madgag:sc-light-jdk15on:1.47.0.2:jar:null:compile:d5c98671cc97fa0d928be1c7eb5edd3fb95d3234</urn>
<urn>net.jcip:jcip-annotations:1.0:jar:null:compile:afba4942caaeaf46aab0b976afd57cc7c181467e</urn>
<urn>net.sf.jopt-simple:jopt-simple:4.3:jar:null:compile:88ffca34311a6564a98f14820431e17b4382a069</urn>
<urn>org.slf4j:slf4j-api:1.7.5:jar:null:compile:6b262da268f8ad9eff941b25503a9198f0a0ac93</urn>
<urn>org.slf4j:slf4j-jdk14:1.7.5:jar:null:runtime:33cf4abac351aa45dd130d31a1e7e33fbbba4762</urn>
<!-- A check for the rules themselves -->

View File

@@ -106,7 +106,7 @@ public class Address extends VersionedChecksummedBytes {
/*
* Returns true if this address is a Pay-To-Script-Hash (P2SH) address.
* See also https://en.bitcoin.it/wiki/BIP_0013: Address Format for pay-to-script-hash
* See also https://github.com/bitcoin/bips/blob/master/bip-0013.mediawiki: Address Format for pay-to-script-hash
*/
public boolean isP2SHAddress() {
final NetworkParameters parameters = getParameters();

View File

@@ -293,16 +293,15 @@ public class BitcoinSerializer {
// The command is a NULL terminated string, unless the command fills all twelve bytes
// in which case the termination is implicit.
int mark = cursor;
for (; header[cursor] != 0 && cursor - mark < COMMAND_LEN; cursor++) ;
byte[] commandBytes = new byte[cursor - mark];
System.arraycopy(header, mark, commandBytes, 0, cursor - mark);
for (; header[cursor] != 0 && cursor < COMMAND_LEN; cursor++) ;
byte[] commandBytes = new byte[cursor];
System.arraycopy(header, 0, commandBytes, 0, cursor);
try {
command = new String(commandBytes, "US-ASCII");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e); // Cannot happen.
}
cursor = mark + COMMAND_LEN;
cursor = COMMAND_LEN;
size = (int) readUint32(header, cursor);
cursor += 4;

View File

@@ -70,25 +70,20 @@ public class BloomFilter extends Message {
}
/**
* <p>Constructs a new Bloom Filter which will provide approximately the given false positive
* rate when the given number of elements have been inserted.</p>
* <p>Constructs a new Bloom Filter which will provide approximately the given false positive rate when the given
* number of elements have been inserted. If the filter would otherwise be larger than the maximum allowed size,
* it will be automatically downsized to the maximum size.</p>
*
* <p>If the filter would otherwise be larger than the maximum allowed size, it will be
* automatically downsized to the maximum size.</p>
* <p>To check the theoretical false positive rate of a given filter, use
* {@link BloomFilter#getFalsePositiveRate(int)}.</p>
*
* <p>To check the theoretical false positive rate of a given filter, use {@link BloomFilter#getFalsePositiveRate(int)}</p>
*
* <p>The anonymity of which coins are yours to any peer which you send a BloomFilter to is
* controlled by the false positive rate.</p>
*
* <p>For reference, as of block 187,000, the total number of addresses used in the chain was roughly 4.5 million.</p>
*
* <p>Thus, if you use a false positive rate of 0.001 (0.1%), there will be, on average, 4,500 distinct public
* keys/addresses which will be thought to be yours by nodes which have your bloom filter, but which are not
* actually yours.</p>
*
* <p>Keep in mind that a remote node can do a pretty good job estimating the order of magnitude of the false positive
* rate of a given filter you provide it when considering the anonymity of a given filter.</p>
* <p>The anonymity of which coins are yours to any peer which you send a BloomFilter to is controlled by the
* false positive rate. For reference, as of block 187,000, the total number of addresses used in the chain was
* roughly 4.5 million. Thus, if you use a false positive rate of 0.001 (0.1%), there will be, on average, 4,500
* distinct public keys/addresses which will be thought to be yours by nodes which have your bloom filter, but
* which are not actually yours. Keep in mind that a remote node can do a pretty good job estimating the order of
* magnitude of the false positive rate of a given filter you provide it when considering the anonymity of a given
* filter.</p>
*
* <p>In order for filtered block download to function efficiently, the number of matched transactions in any given
* block should be less than (with some headroom) the maximum size of the MemoryPool used by the Peer
@@ -98,7 +93,10 @@ public class BloomFilter extends Message {
* <p>randomNonce is a tweak for the hash function used to prevent some theoretical DoS attacks.
* It should be a random value, however secureness of the random value is of no great consequence.</p>
*
* <p>updateFlag is used to control filter behavior</p>
* <p>updateFlag is used to control filter behaviour on the server (remote node) side when it encounters a hit.
* See {@link com.google.bitcoin.core.BloomFilter.BloomUpdate} for a brief description of each mode. The purpose
* of this flag is to reduce network round-tripping and avoid over-dirtying the filter for the most common
* wallet configurations.</p>
*/
public BloomFilter(int elements, double falsePositiveRate, long randomNonce, BloomUpdate updateFlag) {
// The following formulas were stolen from Wikipedia's page on Bloom Filters (with the addition of min(..., MAX_...))
@@ -210,8 +208,8 @@ public class BloomFilter extends Message {
}
/**
* Returns true if the given object matches the filter
* (either because it was inserted, or because we have a false-positive)
* Returns true if the given object matches the filter either because it was inserted, or because we have a
* false-positive.
*/
public boolean contains(byte[] object) {
for (int i = 0; i < hashFuncs; i++) {
@@ -221,9 +219,7 @@ public class BloomFilter extends Message {
return true;
}
/**
* Insert the given arbitrary data into the filter
*/
/** Insert the given arbitrary data into the filter */
public void insert(byte[] object) {
for (int i = 0; i < hashFuncs; i++)
Utils.setBitLE(data, hash(i, object));

View File

@@ -838,7 +838,9 @@ public class ECKey implements Serializable {
final byte[] privKeyBytes = getPrivKeyBytes();
checkState(privKeyBytes != null, "Private key is not available");
EncryptedPrivateKey encryptedPrivateKey = keyCrypter.encrypt(privKeyBytes, aesKey);
return new ECKey(encryptedPrivateKey, getPubKey(), keyCrypter);
ECKey result = new ECKey(encryptedPrivateKey, getPubKey(), keyCrypter);
result.setCreationTimeSeconds(creationTimeSeconds);
return result;
}
/**
@@ -860,6 +862,7 @@ public class ECKey implements Serializable {
ECKey key = new ECKey(new BigInteger(1, unencryptedPrivateKey), null, isCompressed());
if (!Arrays.equals(key.getPubKey(), getPubKey()))
throw new KeyCrypterException("Provided AES key is wrong");
key.setCreationTimeSeconds(creationTimeSeconds);
return key;
}

View File

@@ -164,7 +164,7 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
if (!params.isCheckpoint(height)) {
// BIP30 violator blocks are ones that contain a duplicated transaction. They are all in the
// checkpoints list and we therefore only check non-checkpoints for duplicated transactions here. See the
// BIP30 document for more details on this: https://en.bitcoin.it/wiki/BIP_0030
// BIP30 document for more details on this: https://github.com/bitcoin/bips/blob/master/bip-0030.mediawiki
for (Transaction tx : block.transactions) {
Sha256Hash hash = tx.getHash();
// If we already have unspent outputs for this hash, we saw the tx already. Either the block is

View File

@@ -200,8 +200,8 @@ public class PeerGroup extends AbstractExecutionThreadService implements Transac
// Exponential backoff for peers starts at 1 second and maxes at 10 minutes.
private ExponentialBackoff.Params peerBackoffParams = new ExponentialBackoff.Params(1000, 1.5f, 10 * 60 * 1000);
// Tracks failures globally in case of a network failure
private ExponentialBackoff groupBackoff = new ExponentialBackoff(new ExponentialBackoff.Params(100, 1.1f, 30 * 1000));
// Tracks failures globally in case of a network failure.
private ExponentialBackoff groupBackoff = new ExponentialBackoff(new ExponentialBackoff.Params(1000, 1.5f, 10 * 1000));
// Things for the dedicated PeerGroup management thread to do.
private LinkedBlockingQueue<Runnable> jobQueue = new LinkedBlockingQueue<Runnable>();
@@ -342,7 +342,7 @@ public class PeerGroup extends AbstractExecutionThreadService implements Transac
do {
try {
connectToAnyPeer();
} catch(PeerDiscoveryException e) {
} catch (PeerDiscoveryException e) {
groupBackoff.trackFailure();
}
} while (isRunning() && countConnectedAndPendingPeers() < getMaxConnections());
@@ -400,7 +400,7 @@ public class PeerGroup extends AbstractExecutionThreadService implements Transac
* Sets the {@link VersionMessage} that will be announced on newly created connections. A version message is
* primarily interesting because it lets you customize the "subVer" field which is used a bit like the User-Agent
* field from HTTP. It means your client tells the other side what it is, see
* <a href="https://en.bitcoin.it/wiki/BIP_0014">BIP 14</a>.
* <a href="https://github.com/bitcoin/bips/blob/master/bip-0014.mediawiki">BIP 14</a>.
*
* The VersionMessage you provide is copied and the best chain height/time filled in for each new connection,
* therefore you don't have to worry about setting that. The provided object is really more of a template.
@@ -575,6 +575,8 @@ public class PeerGroup extends AbstractExecutionThreadService implements Transac
}
protected void discoverPeers() throws PeerDiscoveryException {
if (peerDiscoverers.isEmpty())
throw new PeerDiscoveryException("No peer discoverers registered");
long start = System.currentTimeMillis();
Set<PeerAddress> addressSet = Sets.newHashSet();
for (PeerDiscovery peerDiscovery : peerDiscoverers) {
@@ -630,10 +632,10 @@ public class PeerGroup extends AbstractExecutionThreadService implements Transac
final State state = state();
if (!(state == State.STARTING || state == State.RUNNING)) return;
final PeerAddress addr;
PeerAddress addr = null;
long nowMillis = Utils.currentTimeMillis();
long retryTime = 0;
lock.lock();
try {
if (!haveReadyInactivePeer(nowMillis)) {
@@ -646,18 +648,21 @@ public class PeerGroup extends AbstractExecutionThreadService implements Transac
return;
}
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.
retryTime = Math.max(retryTime, groupBackoff.getRetryTime());
lock.unlock();
}
// Delay if any backoff is required
long retryTime = Math.max(backoffMap.get(addr).getRetryTime(), groupBackoff.getRetryTime());
if (retryTime > nowMillis) {
// Sleep until retry time
Utils.sleep(retryTime - nowMillis);
if (retryTime > nowMillis) {
// Sleep until retry time
final long millis = retryTime - nowMillis;
log.info("Waiting {} msec before next connect attempt {}", millis, addr == null ? "" : " to " + addr);
Utils.sleep(millis);
}
}
// This method constructs a Peer and puts it into pendingPeers.
checkNotNull(addr); // Help static analysis which can't see that addr is always set if we didn't throw above.
connectTo(addr, false);
}
@@ -803,11 +808,12 @@ public class PeerGroup extends AbstractExecutionThreadService implements Transac
BloomFilter filter = new BloomFilter(lastBloomFilterElementCount, bloomFilterFPRate, bloomFilterTweak, bloomFlags);
for (PeerFilterProvider p : peerFilterProviders)
filter.merge(p.getBloomFilter(lastBloomFilterElementCount, bloomFilterFPRate, bloomFilterTweak));
bloomFilter = filter;
boolean changed = !filter.equals(bloomFilter);
boolean send = false;
bloomFilter = filter;
switch (mode) {
case SEND_IF_CHANGED: send = changed; break;
case DONT_SEND: send = false; break;
@@ -1407,39 +1413,11 @@ public class PeerGroup extends AbstractExecutionThreadService implements Transac
* If multiple heights are tied, the highest is returned. If no peers are connected, returns zero.
*/
public static int getMostCommonChainHeight(final List<Peer> peers) {
int s = peers.size();
int[] heights = new int[s];
int[] counts = new int[s];
int maxCount = 0;
// Calculate the frequencies of each reported height.
for (Peer peer : peers) {
int h = (int) peer.getBestHeight();
// Find the index of the peers height in the heights array.
for (int cursor = 0; cursor < s; cursor++) {
if (heights[cursor] == h) {
maxCount = Math.max(++counts[cursor], maxCount);
break;
} else if (heights[cursor] == 0) {
// A new height we didn't see before.
checkState(counts[cursor] == 0);
heights[cursor] = h;
counts[cursor] = 1;
maxCount = Math.max(maxCount, 1);
break;
}
}
}
// Find the heights that have the highest frequencies.
int[] freqHeights = new int[s];
int cursor = 0;
for (int i = 0; i < s; i++) {
if (counts[i] == maxCount) {
freqHeights[cursor++] = heights[i];
}
}
// Return the highest of the most common heights.
Arrays.sort(freqHeights);
return freqHeights[s - 1];
if (peers.isEmpty())
return 0;
List<Integer> heights = new ArrayList<Integer>(peers.size());
for (Peer peer : peers) heights.add((int) peer.getBestHeight());
return Utils.maxOfMostFreq(heights);
}
private static class PeerAndPing {

View File

@@ -375,6 +375,18 @@ public class Transaction extends ChildMessage implements Serializable {
return true;
}
/**
* Returns true if any of the outputs is marked as spent.
*/
public boolean isAnyOutputSpent() {
maybeParse();
for (TransactionOutput output : outputs) {
if (!output.isAvailableForSpending())
return true;
}
return false;
}
/**
* Returns false if this transaction has at least one output that is owned by the given wallet and unspent, true
* otherwise.
@@ -602,8 +614,15 @@ public class Transaction extends ChildMessage implements Serializable {
try {
Script scriptSig = in.getScriptSig();
s.append(scriptSig);
s.append(" / ");
s.append(in.getOutpoint().toString());
s.append("\n ");
s.append("outpoint:");
final TransactionOutPoint outpoint = in.getOutpoint();
s.append(outpoint.toString());
final TransactionOutput connectedOutput = outpoint.getConnectedOutput();
if (connectedOutput != null) {
s.append(" hash160:");
s.append(Utils.bytesToHexString(connectedOutput.getScriptPubKey().getPubKeyHash()));
}
} catch (Exception e) {
s.append("[exception: ").append(e.getMessage()).append("]");
}

View File

@@ -402,10 +402,12 @@ public class TransactionInput extends ChildMessage implements Serializable {
* @throws VerificationException If the outpoint doesn't match the given output.
*/
public void verify(TransactionOutput output) throws VerificationException {
if (!getOutpoint().getHash().equals(output.parentTransaction.getHash()))
throw new VerificationException("This input does not refer to the tx containing the output.");
if (getOutpoint().getIndex() != output.getIndex())
throw new VerificationException("This input refers to a different output on the given tx.");
if (output.parentTransaction != null) {
if (!getOutpoint().getHash().equals(output.parentTransaction.getHash()))
throw new VerificationException("This input does not refer to the tx containing the output.");
if (getOutpoint().getIndex() != output.getIndex())
throw new VerificationException("This input refers to a different output on the given tx.");
}
Script pubKey = output.getScriptPubKey();
int myIndex = parentTransaction.getInputs().indexOf(this);
getScriptSig().correctlySpends(parentTransaction, myIndex, pubKey, true);

View File

@@ -174,7 +174,7 @@ public class TransactionOutput extends ChildMessage implements Serializable {
}
int getIndex() {
checkNotNull(parentTransaction);
checkNotNull(parentTransaction, "This output is not attached to a parent transaction.");
for (int i = 0; i < parentTransaction.getOutputs().size(); i++) {
if (parentTransaction.getOutputs().get(i) == this)
return i;

View File

@@ -1,5 +1,6 @@
/**
* Copyright 2011 Google Inc.
* Copyright 2014 Andreas Schildbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,6 +18,9 @@
package com.google.bitcoin.core;
import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import com.google.common.primitives.Ints;
import com.google.common.primitives.UnsignedLongs;
import org.spongycastle.crypto.digests.RIPEMD160Digest;
import org.spongycastle.util.encoders.Hex;
@@ -28,8 +32,7 @@ import java.math.BigDecimal;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Date;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
@@ -460,13 +463,20 @@ public class Utils {
*/
public static Date rollMockClockMillis(long millis) {
if (mockTime == null)
mockTime = new Date();
throw new IllegalStateException("You need to use setMockClock() first.");
mockTime = new Date(mockTime.getTime() + millis);
return mockTime;
}
/**
* Sets the mock clock to the given time (in seconds)
* Sets the mock clock to the current time.
*/
public static void setMockClock() {
mockTime = new Date();
}
/**
* Sets the mock clock to the given time (in seconds).
*/
public static void setMockClock(long mockClock) {
mockTime = new Date(mockClock * 1000);
@@ -596,4 +606,43 @@ public class Utils {
mockSleepQueue.offer(true);
}
}
private static class Pair implements Comparable<Pair> {
int item, count;
public Pair(int item, int count) { this.count = count; this.item = item; }
@Override public int compareTo(Pair o) { return -Ints.compare(count, o.count); }
}
public static int maxOfMostFreq(int... items) {
// Java 6 sucks.
ArrayList<Integer> list = new ArrayList<Integer>(items.length);
for (int item : items) list.add(item);
return maxOfMostFreq(list);
}
public static int maxOfMostFreq(List<Integer> items) {
if (items.isEmpty())
return 0;
// This would be much easier in a functional language (or in Java 8).
items = Ordering.natural().reverse().sortedCopy(items);
LinkedList<Pair> pairs = Lists.newLinkedList();
pairs.add(new Pair(items.get(0), 0));
for (int item : items) {
Pair pair = pairs.getLast();
if (pair.item != item)
pairs.add((pair = new Pair(item, 0)));
pair.count++;
}
// pairs now contains a uniqified list of the sorted inputs, with counts for how often that item appeared.
// Now sort by how frequently they occur, and pick the max of the most frequent.
Collections.sort(pairs);
int maxCount = pairs.getFirst().count;
int maxItem = pairs.getFirst().item;
for (Pair pair : pairs) {
if (pair.count != maxCount)
break;
maxItem = Math.max(maxItem, pair.item);
}
return maxItem;
}
}

View File

@@ -74,7 +74,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.11-SNAPSHOT";
public static final String BITCOINJ_VERSION = "0.11.2";
/** The value that is prepended to the subVer field of this application. */
public static final String LIBRARY_SUBVER = "/BitCoinJ:" + BITCOINJ_VERSION + "/";
@@ -275,7 +275,7 @@ public class VersionMessage extends Message {
* set of "/BitCoinJ:1.0/MultiBit:1.0(Windows)/. Therefore the / ( and ) characters are reserved in all these
* components. If you don't want to add a comment (recommended), pass null.<p>
*
* See <a href="https://en.bitcoin.it/wiki/BIP_0014">BIP 14</a> for more information.
* See <a href="https://github.com/bitcoin/bips/blob/master/bip-0014.mediawiki">BIP 14</a> for more information.
*
* @param comments Optional (can be null) platform or other node specific information.
* @throws IllegalArgumentException if name, version or comments contains invalid characters.

View File

@@ -1,5 +1,6 @@
/**
* Copyright 2013 Google Inc.
* Copyright 2014 Andreas Schildbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -31,6 +32,7 @@ import com.google.bitcoin.wallet.*;
import com.google.bitcoin.wallet.WalletTransaction.Pool;
import com.google.common.collect.*;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
@@ -39,6 +41,7 @@ import org.bitcoinj.wallet.Protos.Wallet.EncryptionType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.crypto.params.KeyParameter;
import org.spongycastle.util.encoders.Hex;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -434,6 +437,25 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
}
}
/**
* <p>
* Disables auto-saving, after it had been enabled with
* {@link Wallet#autosaveToFile(java.io.File, long, java.util.concurrent.TimeUnit, com.google.bitcoin.wallet.WalletFiles.Listener)}
* before. This method blocks until finished.
* </p>
*/
public void shutdownAutosaveAndWait() {
lock.lock();
try {
WalletFiles files = vFileManager;
vFileManager = null;
checkState(files != null, "Auto saving not enabled.");
files.shutdownAndWait();
} finally {
lock.unlock();
}
}
private void saveLater() {
WalletFiles files = vFileManager;
if (files != null)
@@ -529,7 +551,13 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
}
}
if (!success) log.error(toString());
if (!success) {
try {
log.error(toString());
} catch (RuntimeException x) {
log.error("Printing inconsistent wallet failed", x);
}
}
return success;
} finally {
lock.unlock();
@@ -1065,10 +1093,13 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
log.warn("updateForSpends: saw double spend from chain, handling later.");
} else {
// We saw two pending transactions that double spend each other. We don't know which will win.
// This should not happen.
log.warn("Saw two pending transactions double spend each other: {} vs {}",
tx.getHash(), input.getConnectedOutput().getSpentBy().getParentTransaction().getHash());
// This can happen in the case of bad network nodes that mutate transactions. Do a hex dump
// so the exact nature of the mutation can be examined.
log.warn("Saw two pending transactions double spend each other");
log.warn(" offending input is input {}", tx.getInputs().indexOf(input));
log.warn("{}: {}", tx.getHash(), new String(Hex.encode(tx.unsafeBitcoinSerialize())));
Transaction other = input.getConnectedOutput().getSpentBy().getParentTransaction();
log.warn("{}: {}", other.getHash(), new String(Hex.encode(tx.unsafeBitcoinSerialize())));
}
} else if (result == TransactionInput.ConnectionResult.SUCCESS) {
// Otherwise we saw a transaction spend our coins, but we didn't try and spend them ourselves yet.
@@ -1424,6 +1455,40 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
}
}
/**
* Clean up the wallet. Currently, it only removes risky pending transaction from the wallet and only if their
* outputs have not been spent.
*/
public void cleanup() {
lock.lock();
try {
boolean dirty = false;
for (Iterator<Transaction> i = pending.values().iterator(); i.hasNext();) {
Transaction tx = i.next();
if (isTransactionRisky(tx, null) && !acceptRiskyTransactions) {
log.debug("Found risky transaction {} in wallet during cleanup.", tx.getHashAsString());
if (!tx.isAnyOutputSpent()) {
tx.disconnectInputs();
i.remove();
transactions.remove(tx.getHash());
dirty = true;
log.info("Removed transaction {} from pending pool during cleanup.", tx.getHashAsString());
} else {
log.info(
"Cannot remove transaction {} from pending pool during cleanup, as it's already spent partially.",
tx.getHashAsString());
}
}
}
if (dirty) {
checkState(isConsistent());
saveLater();
}
} finally {
lock.unlock();
}
}
EnumSet<Pool> getContainingPools(Transaction tx) {
lock.lock();
try {
@@ -1572,7 +1637,7 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
/**
* The AES key to use to decrypt the private keys before signing.
* If null then no decryption will be performed and if decryption is required an exception will be thrown.
* You can get this from a password by doing wallet.getKeyCrypter().derivePassword(password).
* You can get this from a password by doing wallet.getKeyCrypter().deriveKey(password).
*/
public KeyParameter aesKey = null;
@@ -2013,10 +2078,10 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
// If the key has a keyCrypter that does not match the Wallet's then a KeyCrypterException is thrown.
// This is done because only one keyCrypter is persisted per Wallet and hence all the keys must be homogenous.
if (keyCrypter != null && keyCrypter.getUnderstoodEncryptionType() != EncryptionType.UNENCRYPTED) {
if (key.isEncrypted() && !keyCrypter.equals(key.getKeyCrypter())) {
throw new KeyCrypterException("Cannot add key " + key.toString() + " because the keyCrypter does not match the wallets. Keys must be homogenous.");
}
if (isEncrypted() && (!key.isEncrypted() || !keyCrypter.equals(key.getKeyCrypter()))) {
throw new KeyCrypterException("Cannot add key " + key.toString() + " because the keyCrypter does not match the wallets. Keys must be homogenous.");
} else if (key.isEncrypted() && !isEncrypted()) {
throw new KeyCrypterException("Cannot add key because it's encrypted and this wallet is not.");
}
keychain.add(key);
added++;
@@ -2264,6 +2329,30 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
return toString(false, true, true, null);
}
private static final Comparator<Transaction> SORT_ORDER_BY_UPDATE_TIME = new Comparator<Transaction>() {
@Override
public int compare(final Transaction tx1, final Transaction tx2) {
final long time1 = tx1.getUpdateTime().getTime();
final long time2 = tx2.getUpdateTime().getTime();
return -(Longs.compare(time1, time2));
}
};
private static final Comparator<Transaction> SORT_ORDER_BY_HEIGHT = new Comparator<Transaction>() {
@Override
public int compare(final Transaction tx1, final Transaction tx2) {
final int height1 = tx1.getConfidence().getAppearedAtChainHeight();
final int height2 = tx2.getConfidence().getAppearedAtChainHeight();
return -(Ints.compare(height1, height2));
}
};
/**
* Formats the wallet as a human readable piece of text. Intended for debugging, the format is not meant to be
* stable or human readable.
@@ -2277,12 +2366,13 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
lock.lock();
try {
StringBuilder builder = new StringBuilder();
BigInteger balance = getBalance(BalanceType.ESTIMATED);
builder.append(String.format("Wallet containing %s BTC (%d satoshis) in:%n",
bitcoinValueToPlainString(balance), balance.longValue()));
BigInteger estimatedBalance = getBalance(BalanceType.ESTIMATED);
BigInteger availableBalance = getBalance(BalanceType.AVAILABLE);
builder.append(String.format("Wallet containing %s BTC (available: %s BTC) in:%n",
bitcoinValueToPlainString(estimatedBalance), bitcoinValueToPlainString(availableBalance)));
builder.append(String.format(" %d pending transactions%n", pending.size()));
builder.append(String.format(" %d unspent transactions%n", unspent.size()));
builder.append(String.format(" %d spent transactions%n", spent.size()));
builder.append(String.format(" %d pending transactions%n", pending.size()));
builder.append(String.format(" %d dead transactions%n", dead.size()));
final Date lastBlockSeenTime = getLastBlockSeenTime();
final String lastBlockSeenTimeStr = lastBlockSeenTime == null ? "time unknown" : lastBlockSeenTime.toString();
@@ -2294,8 +2384,11 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
// Do the keys.
builder.append("\nKeys:\n");
for (ECKey key : keychain) {
final Address address = key.toAddress(params);
builder.append(" addr:");
builder.append(key.toAddress(params));
builder.append(address.toString());
builder.append(" hash160:");
builder.append(Utils.bytesToHexString(address.getHash160()));
builder.append(" ");
builder.append(includePrivateKeys ? key.toStringWithPrivate() : key.toString());
builder.append("\n");
@@ -2312,21 +2405,21 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
if (includeTransactions) {
// Print the transactions themselves
if (pending.size() > 0) {
builder.append("\n>>> PENDING:\n");
toStringHelper(builder, pending, chain, SORT_ORDER_BY_UPDATE_TIME);
}
if (unspent.size() > 0) {
builder.append("\n>>> UNSPENT:\n");
toStringHelper(builder, unspent, chain);
toStringHelper(builder, unspent, chain, SORT_ORDER_BY_HEIGHT);
}
if (spent.size() > 0) {
builder.append("\n>>> SPENT:\n");
toStringHelper(builder, spent, chain);
}
if (pending.size() > 0) {
builder.append("\n>>> PENDING:\n");
toStringHelper(builder, pending, chain);
toStringHelper(builder, spent, chain, SORT_ORDER_BY_HEIGHT);
}
if (dead.size() > 0) {
builder.append("\n>>> DEAD:\n");
toStringHelper(builder, dead, chain);
toStringHelper(builder, dead, chain, SORT_ORDER_BY_HEIGHT);
}
}
if (includeExtensions && extensions.size() > 0) {
@@ -2342,9 +2435,18 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
}
private void toStringHelper(StringBuilder builder, Map<Sha256Hash, Transaction> transactionMap,
@Nullable AbstractBlockChain chain) {
@Nullable AbstractBlockChain chain, @Nullable Comparator<Transaction> sortOrder) {
checkState(lock.isHeldByCurrentThread());
for (Transaction tx : transactionMap.values()) {
final Collection<Transaction> txns;
if (sortOrder != null) {
txns = new TreeSet<Transaction>(sortOrder);
txns.addAll(transactionMap.values());
} else {
txns = transactionMap.values();
}
for (Transaction tx : txns) {
try {
builder.append("Sends ");
builder.append(Utils.bitcoinValueToFriendlyString(tx.getValueSentFromMe(this)));

View File

@@ -39,7 +39,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
/**
* A deterministic key is a node in a {@link DeterministicHierarchy}. As per
* <a href="https://en.bitcoin.it/wiki/BIP_0032">the BIP 32 specification</a> it is a pair (key, chaincode). If you
* <a href="https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki">the BIP 32 specification</a> it is a pair (key, chaincode). If you
* know its path in the tree you can derive more keys from this.
*/
public class DeterministicKey implements Serializable {

View File

@@ -31,6 +31,7 @@ import org.spongycastle.crypto.params.ParametersWithIV;
import java.io.Serializable;
import java.security.SecureRandom;
import java.util.Arrays;
import static com.google.common.base.Preconditions.checkNotNull;
@@ -154,11 +155,10 @@ public class KeyCrypterScrypt implements KeyCrypter, Serializable {
BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESFastEngine()));
cipher.init(true, keyWithIv);
byte[] encryptedBytes = new byte[cipher.getOutputSize(plainBytes.length)];
int length = cipher.processBytes(plainBytes, 0, plainBytes.length, encryptedBytes, 0);
final int length1 = cipher.processBytes(plainBytes, 0, plainBytes.length, encryptedBytes, 0);
final int length2 = cipher.doFinal(encryptedBytes, length1);
cipher.doFinal(encryptedBytes, length);
return new EncryptedPrivateKey(iv, encryptedBytes);
return new EncryptedPrivateKey(iv, Arrays.copyOf(encryptedBytes, length1 + length2));
} catch (Exception e) {
throw new KeyCrypterException("Could not encrypt bytes.", e);
}
@@ -185,16 +185,11 @@ public class KeyCrypterScrypt implements KeyCrypter, Serializable {
cipher.init(false, keyWithIv);
byte[] cipherBytes = privateKeyToDecode.getEncryptedBytes();
int minimumSize = cipher.getOutputSize(cipherBytes.length);
byte[] outputBuffer = new byte[minimumSize];
int length1 = cipher.processBytes(cipherBytes, 0, cipherBytes.length, outputBuffer, 0);
int length2 = cipher.doFinal(outputBuffer, length1);
int actualLength = length1 + length2;
byte[] decryptedBytes = new byte[cipher.getOutputSize(cipherBytes.length)];
final int length1 = cipher.processBytes(cipherBytes, 0, cipherBytes.length, decryptedBytes, 0);
final int length2 = cipher.doFinal(decryptedBytes, length1);
byte[] decryptedBytes = new byte[actualLength];
System.arraycopy(outputBuffer, 0, decryptedBytes, 0, actualLength);
return decryptedBytes;
return Arrays.copyOf(decryptedBytes, length1 + length2);
} catch (Exception e) {
throw new KeyCrypterException("Could not decrypt bytes", e);
}

View File

@@ -32,7 +32,7 @@ import java.util.List;
/**
* A MnemonicCode object may be used to convert between binary seed values and
* lists of words per <a href="https://en.bitcoin.it/wiki/BIP_0039">the BIP 39
* lists of words per <a href="https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki">the BIP 39
* specification</a>
*/

View File

@@ -1,5 +1,6 @@
/**
* Copyright 2013 Google Inc.
* Copyright 2014 Andreas Schildbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,6 +23,7 @@ import com.google.bitcoin.script.ScriptBuilder;
import com.google.bitcoin.uri.BitcoinURI;
import com.google.bitcoin.utils.Threading;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
@@ -42,6 +44,7 @@ import java.net.*;
import java.security.*;
import java.security.cert.*;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;
@@ -72,6 +75,7 @@ import java.util.concurrent.Callable;
* "processing" or that an error occurred.
*
* @author Kevin Greene
* @author Andreas Schildbach
* @see <a href="https://github.com/bitcoin/bips/blob/master/bip-0070.mediawiki">BIP 0070</a>
*/
public class PaymentSession {
@@ -227,26 +231,30 @@ public class PaymentSession {
* Message returned by the merchant in response to a Payment message.
*/
public class Ack {
private String memo = "";
@Nullable private String memo;
Ack(String memo) {
Ack(@Nullable String memo) {
this.memo = memo;
}
/**
* Returns the memo included by the merchant in the payment ack. This message is typically displayed to the user
* as a notification (e.g. "Your payment was received and is being processed").
* as a notification (e.g. "Your payment was received and is being processed"). If none was provided, returns
* null.
*/
public String getMemo() {
@Nullable public String getMemo() {
return memo;
}
}
/**
* Returns the memo included by the merchant in the payment request.
* Returns the memo included by the merchant in the payment request, or null if not found.
*/
public String getMemo() {
return paymentDetails.getMemo();
@Nullable public String getMemo() {
if (paymentDetails.hasMemo())
return paymentDetails.getMemo();
else
return null;
}
/**
@@ -374,7 +382,7 @@ public class PaymentSession {
InputStream inStream = connection.getInputStream();
Protos.PaymentACK.Builder paymentAckBuilder = Protos.PaymentACK.newBuilder().mergeFrom(inStream);
Protos.PaymentACK paymentAck = paymentAckBuilder.build();
String memo = "";
String memo = null;
if (paymentAck.hasMemo())
memo = paymentAck.getMemo();
return new Ack(memo);
@@ -386,10 +394,47 @@ public class PaymentSession {
* Information about the X509 signature's issuer and subject.
*/
public static class PkiVerificationData {
public String name;
public PublicKey merchantSigningKey;
public TrustAnchor rootAuthority;
public String orgName;
/** Display name of the payment requestor, could be a domain name, email address, legal name, etc */
public final String name;
/** The "org" part of the payment requestors ID. */
public final String orgName;
/** SSL public key that was used to sign. */
public final PublicKey merchantSigningKey;
/** Object representing the CA that verified the merchant's ID */
public final TrustAnchor rootAuthority;
/** String representing the display name of the CA that verified the merchant's ID */
public final String rootAuthorityName;
private PkiVerificationData(@Nullable String name, @Nullable String orgName, PublicKey merchantSigningKey,
TrustAnchor rootAuthority) throws PaymentRequestException.PkiVerificationException {
this.name = name;
this.orgName = orgName;
this.merchantSigningKey = merchantSigningKey;
this.rootAuthority = rootAuthority;
this.rootAuthorityName = getNameFromCert(rootAuthority);
}
private @Nullable String getNameFromCert(TrustAnchor rootAuthority) throws PaymentRequestException.PkiVerificationException {
org.spongycastle.asn1.x500.X500Name name = new X500Name(rootAuthority.getTrustedCert().getSubjectX500Principal().getName());
String commonName = null, org = null, location = null, country = null;
for (RDN rdn : name.getRDNs()) {
AttributeTypeAndValue pair = rdn.getFirst();
String val = ((ASN1String)pair.getValue()).getString();
if (pair.getType().equals(RFC4519Style.cn))
commonName = val;
else if (pair.getType().equals(RFC4519Style.o))
org = val;
else if (pair.getType().equals(RFC4519Style.l))
location = val;
else if (pair.getType().equals(RFC4519Style.c))
country = val;
}
if (org != null) {
return Joiner.on(", ").skipNulls().join(org, location, country);
} else {
return commonName;
}
}
}
/**
@@ -447,7 +492,8 @@ public class PaymentSession {
throw new PaymentRequestException.PkiVerificationException("Invalid signature, this payment request is not valid.");
// Signature verifies, get the names from the identity we just verified for presentation to the user.
X500Principal principal = certs.get(0).getSubjectX500Principal();
final X509Certificate cert = certs.get(0);
X500Principal principal = cert.getSubjectX500Principal();
// At this point the Java crypto API falls flat on its face and dies - there's no clean way to get the
// different parts of the certificate name except for parsing the string. That's hard because of various
// custom escaping rules and the usual crap. So, use Bouncy Castle to re-parse the string into binary form
@@ -461,13 +507,19 @@ public class PaymentSession {
else if (pair.getType().equals(RFC4519Style.o))
orgName = ((ASN1String)pair.getValue()).getString();
}
if (entityName == null && orgName == null) {
// This cert might not be an SSL cert. Just grab the first "subject alt name" if present, e.g. for
// S/MIME certs.
final Iterator<List<?>> it = cert.getSubjectAlternativeNames().iterator();
List<?> list;
// email addresses have a type code of one.
if (it.hasNext() && (list = it.next()) != null && (Integer) list.get(0) == 1)
entityName = (String) list.get(1);
if (entityName == null)
throw new PaymentRequestException.PkiVerificationException("Could not extract name from certificate");
}
// Everything is peachy. Return some useful data to the caller.
PkiVerificationData data = new PkiVerificationData();
data.name = entityName;
data.orgName = orgName;
data.merchantSigningKey = publicKey;
data.rootAuthority = result.getTrustAnchor();
PkiVerificationData data = new PkiVerificationData(entityName, orgName, publicKey, result.getTrustAnchor());
// Cache the result so we don't have to re-verify if this method is called again.
pkiVerificationData = data;
return data;
@@ -557,8 +609,6 @@ public class PaymentSession {
try {
if (request == null)
throw new PaymentRequestException("request cannot be null");
if (!request.hasPaymentDetailsVersion())
throw new PaymentRequestException.InvalidVersion("No version");
if (request.getPaymentDetailsVersion() != 1)
throw new PaymentRequestException.InvalidVersion("Version 1 required. Received version " + request.getPaymentDetailsVersion());
paymentRequest = request;
@@ -581,10 +631,20 @@ public class PaymentSession {
}
// This won't ever happen in practice. It would only happen if the user provided outputs
// that are obviously invalid. Still, we don't want to silently overflow.
if (totalValue.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0)
if (totalValue.compareTo(NetworkParameters.MAX_MONEY) > 0)
throw new PaymentRequestException.InvalidOutputs("The outputs are way too big.");
} catch (InvalidProtocolBufferException e) {
throw new PaymentRequestException(e);
}
}
/** Returns the protobuf that this object was instantiated with. */
public Protos.PaymentRequest getPaymentRequest() {
return paymentRequest;
}
/** Returns the protobuf that describes the payment to be made. */
public Protos.PaymentDetails getPaymentDetails() {
return paymentDetails;
}
}

View File

@@ -472,7 +472,7 @@ public class Script {
* spending input to provide a program matching that hash. This rule is "soft enforced" by the network as it does
* not exist in Satoshis original implementation. It means blocks containing P2SH transactions that don't match
* correctly are considered valid, but won't be mined upon, so they'll be rapidly re-orgd out of the chain. This
* logic is defined by <a href="https://en.bitcoin.it/wiki/BIP_0016">BIP 16</a>.</p>
* logic is defined by <a href="https://github.com/bitcoin/bips/blob/master/bip-0016.mediawiki">BIP 16</a>.</p>
*
* <p>bitcoinj does not support creation of P2SH transactions today. The goal of P2SH is to allow short addresses
* even for complex scripts (eg, multi-sig outputs) so they are convenient to work with in things like QRcodes or
@@ -660,10 +660,7 @@ public class Script {
continue;
switch(opcode) {
case OP_0:
// This is also OP_FALSE (they are both zero).
stack.add(new byte[]{0});
break;
// OP_0 is no opcode
case OP_1NEGATE:
stack.add(Utils.reverseBytes(Utils.encodeMPI(BigInteger.ONE.negate(), false)));
break;

View File

@@ -1,5 +1,6 @@
/**
* Copyright 2012 Google Inc.
* Copyright 2014 Andreas Schildbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,7 +26,10 @@ import com.google.bitcoin.script.Script;
import com.google.bitcoin.wallet.WalletTransaction;
import com.google.common.collect.Lists;
import com.google.protobuf.ByteString;
import com.google.protobuf.CodedInputStream;
import com.google.protobuf.TextFormat;
import com.google.protobuf.WireFormat;
import org.bitcoinj.wallet.Protos;
import org.bitcoinj.wallet.Protos.Wallet.EncryptionType;
import org.slf4j.Logger;
@@ -693,4 +697,25 @@ public class WalletProtobufSerializer {
default: confidence.setSource(TransactionConfidence.Source.UNKNOWN); break;
}
}
/**
* Cheap test to see if input stream is a wallet. This checks for a magic value at the beginning of the stream.
*
* @param is
* input stream to test
* @return true if input stream is a wallet
*/
public static boolean isWallet(InputStream is) {
try {
final CodedInputStream cis = CodedInputStream.newInstance(is);
final int tag = cis.readTag();
final int field = WireFormat.getTagFieldNumber(tag);
if (field != 1) // network_identifier
return false;
final String network = cis.readString();
return NetworkParameters.fromID(network) != null;
} catch (IOException x) {
return false;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012 the original author or authors.
* Copyright 2012, 2014 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -32,6 +32,7 @@ import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import static com.google.common.base.Preconditions.checkNotNull;
@@ -73,7 +74,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
* @author Andreas Schildbach (initial code)
* @author Jim Burton (enhancements for MultiBit)
* @author Gary Rowe (BIP21 support)
* @see <a href="https://en.bitcoin.it/wiki/BIP_0021">BIP 0021</a>
* @see <a href="https://github.com/bitcoin/bips/blob/master/bip-0021.mediawiki">BIP 0021</a>
*/
public class BitcoinURI {
/**
@@ -192,14 +193,15 @@ public class BitcoinURI {
private void parseParameters(@Nullable NetworkParameters params, String addressToken, String[] nameValuePairTokens) throws BitcoinURIParseException {
// Attempt to decode the rest of the tokens into a parameter map.
for (String nameValuePairToken : nameValuePairTokens) {
String[] tokens = nameValuePairToken.split("=");
if (tokens.length != 2 || "".equals(tokens[0])) {
throw new BitcoinURIParseException("Malformed Bitcoin URI - cannot parse name value pair '" +
final int sepIndex = nameValuePairToken.indexOf('=');
if (sepIndex == -1)
throw new BitcoinURIParseException("Malformed Bitcoin URI - no separator in '" +
nameValuePairToken + "'");
}
String nameToken = tokens[0].toLowerCase();
String valueToken = tokens[1];
if (sepIndex == 0)
throw new BitcoinURIParseException("Malformed Bitcoin URI - empty name '" +
nameValuePairToken + "'");
final String nameToken = nameValuePairToken.substring(0, sepIndex).toLowerCase(Locale.ENGLISH);
final String valueToken = nameValuePairToken.substring(sepIndex + 1);
// Parse the amount.
if (FIELD_AMOUNT.equals(nameToken)) {
@@ -219,7 +221,8 @@ public class BitcoinURI {
} else {
// Known fields and unknown parameters that are optional.
try {
putWithValidation(nameToken, URLDecoder.decode(valueToken, "UTF-8"));
if (valueToken.length() > 0)
putWithValidation(nameToken, URLDecoder.decode(valueToken, "UTF-8"));
} catch (UnsupportedEncodingException e) {
// Unreachable.
throw new RuntimeException(e);

View File

@@ -51,8 +51,11 @@ public class BlockFileLoader implements Iterable<Block>, Iterator<Block> {
*/
public static List<File> getReferenceClientBlockFileList() {
String defaultDataDir;
if (System.getProperty("os.name").toLowerCase().indexOf("win") >= 0) {
String OS = System.getProperty("os.name").toLowerCase();
if (OS.indexOf("win") >= 0) {
defaultDataDir = System.getenv("APPDATA") + "\\.bitcoin\\blocks\\";
} else if (OS.indexOf("mac") >= 0 || (OS.indexOf("darwin") >= 0)) {
defaultDataDir = System.getProperty("user.home") + "/Library/Application Support/Bitcoin/blocks/";
} else {
defaultDataDir = System.getProperty("user.home") + "/.bitcoin/blocks/";
}

View File

@@ -1,5 +1,6 @@
/*
* Copyright 2013 Google Inc.
* Copyright 2014 Andreas Schildbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,11 +17,15 @@
package com.google.bitcoin.wallet;
import com.google.bitcoin.core.NetworkParameters;
import com.google.bitcoin.core.Transaction;
import com.google.bitcoin.core.TransactionConfidence;
import com.google.bitcoin.core.TransactionOutput;
import com.google.bitcoin.core.Wallet;
import javax.annotation.Nullable;
import java.math.BigInteger;
import java.util.List;
import static com.google.common.base.Preconditions.checkState;
@@ -30,10 +35,18 @@ import static com.google.common.base.Preconditions.checkState;
* of specialised protocols you should not encounter non-final transactions.
*/
public class DefaultRiskAnalysis implements RiskAnalysis {
/**
* Any standard output smaller than this value (in satoshis) will be considered risky, as it's most likely be
* rejected by the network. Currently it's 546 satoshis. This is different from {@link Transaction#MIN_NONDUST_OUTPUT}
* because of an upcoming fee change in Bitcoin Core 0.9.
*/
public static final BigInteger MIN_ANALYSIS_NONDUST_OUTPUT = BigInteger.valueOf(546);
protected final Transaction tx;
protected final List<Transaction> dependencies;
protected final Wallet wallet;
private Transaction nonStandard;
protected Transaction nonFinal;
protected boolean analyzed;
@@ -48,6 +61,14 @@ public class DefaultRiskAnalysis implements RiskAnalysis {
checkState(!analyzed);
analyzed = true;
Result result = analyzeIsFinal();
if (result != Result.OK)
return result;
return analyzeIsStandard();
}
private Result analyzeIsFinal() {
// Transactions we create ourselves are, by definition, not at risk of double spending against us.
if (tx.getConfidence().getSource() == TransactionConfidence.Source.SELF)
return Result.OK;
@@ -71,6 +92,49 @@ public class DefaultRiskAnalysis implements RiskAnalysis {
return Result.OK;
}
private Result analyzeIsStandard() {
if (!wallet.getNetworkParameters().getId().equals(NetworkParameters.ID_MAINNET))
return Result.OK;
nonStandard = isStandard(tx);
if (nonStandard != null)
return Result.NON_STANDARD;
for (Transaction dep : dependencies) {
nonStandard = isStandard(dep);
if (nonStandard != null)
return Result.NON_STANDARD;
}
return Result.OK;
}
/**
* <p>Checks if a transaction is considered "standard" by the reference client's IsStandardTx and AreInputsStandard
* functions.</p>
*
* <p>Note that this method currently only implements a minimum of checks. More to be added later.</p>
*
* @return Either null if the transaction is standard, or the first transaction found which is considered nonstandard
*/
public Transaction isStandard(Transaction tx) {
if (tx.getVersion() > 1 || tx.getVersion() < 1)
return tx;
for (TransactionOutput output : tx.getOutputs()) {
if (MIN_ANALYSIS_NONDUST_OUTPUT.compareTo(output.getValue()) > 0)
return tx;
}
return null;
}
/** Returns the transaction that was found to be non-standard, or null. */
@Nullable
public Transaction getNonStandard() {
return nonStandard;
}
/** Returns the transaction that was found to be non-final, or null. */
@Nullable
public Transaction getNonFinal() {
@@ -83,6 +147,8 @@ public class DefaultRiskAnalysis implements RiskAnalysis {
return "Pending risk analysis for " + tx.getHashAsString();
else if (nonFinal != null)
return "Risky due to non-finality of " + nonFinal.getHashAsString();
else if (nonStandard != null)
return "Risky due to non-standard tx " + nonStandard.getHashAsString();
else
return "Non-risky";
}

View File

@@ -34,7 +34,8 @@ import java.util.List;
public interface RiskAnalysis {
public enum Result {
OK,
NON_FINAL
NON_FINAL,
NON_STANDARD
}
public Result analyze();

View File

@@ -1,5 +1,6 @@
/**
* Copyright 2013 Google Inc.
* Copyright 2014 Andreas Schildbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -77,6 +78,7 @@ public class WalletFiles {
this.executor = new ScheduledThreadPoolExecutor(1, builder.build());
this.executor.setKeepAliveTime(5, TimeUnit.SECONDS);
this.executor.allowCoreThreadTimeOut(true);
this.executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
this.wallet = checkNotNull(wallet);
// File must only be accessed from the auto-save executor from now on, to avoid simultaneous access.
this.file = checkNotNull(file);
@@ -132,4 +134,14 @@ public class WalletFiles {
return; // Already pending.
executor.schedule(saver, delay, delayTimeUnit);
}
/** Shut down auto-saving. */
public void shutdownAndWait() {
executor.shutdown();
try {
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS); // forever
} catch (InterruptedException x) {
throw new RuntimeException(x);
}
}
}

View File

@@ -52,6 +52,7 @@ public class ChainSplitTest {
@Before
public void setUp() throws Exception {
BriefLogFormatter.init();
Utils.setMockClock(); // Use mock clock
Wallet.SendRequest.DEFAULT_FEE_PER_KB = BigInteger.ZERO;
unitTestParams = UnitTestParams.get();
wallet = new Wallet(unitTestParams);

View File

@@ -51,6 +51,7 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static com.google.bitcoin.core.Utils.reverseBytes;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.junit.Assert.*;
public class ECKeyTest {
@@ -249,55 +250,25 @@ public class ECKeyTest {
@Test
public void testUnencryptedCreate() throws Exception {
ECKey unencryptedKey = new ECKey();
// The key should initially be unencrypted.
assertTrue(!unencryptedKey.isEncrypted());
// Copy the private key bytes for checking later.
byte[] originalPrivateKeyBytes = new byte[32];
System.arraycopy(unencryptedKey.getPrivKeyBytes(), 0, originalPrivateKeyBytes, 0, 32);
log.info("Original private key = " + Utils.bytesToHexString(originalPrivateKeyBytes));
// Encrypt the key.
ECKey encryptedKey = unencryptedKey.encrypt(keyCrypter, keyCrypter.deriveKey(PASSWORD1));
// The key should now be encrypted.
assertTrue("Key is not encrypted but it should be", encryptedKey.isEncrypted());
// The unencrypted private key bytes of the encrypted keychain
// should be null or all be blank.
byte[] privateKeyBytes = encryptedKey.getPrivKeyBytes();
if (privateKeyBytes != null) {
for (int i = 0; i < privateKeyBytes.length; i++) {
assertEquals("Byte " + i + " of the private key was not zero but should be", 0, privateKeyBytes[i]);
}
}
// Decrypt the key.
unencryptedKey = encryptedKey.decrypt(keyCrypter, keyCrypter.deriveKey(PASSWORD1));
// The key should be unencrypted
assertTrue("Key is not unencrypted but it should be", !unencryptedKey.isEncrypted());
// The reborn unencrypted private key bytes should match the
// original private key.
privateKeyBytes = unencryptedKey.getPrivKeyBytes();
log.info("Reborn decrypted private key = " + Utils.bytesToHexString(privateKeyBytes));
for (int i = 0; i < privateKeyBytes.length; i++) {
assertEquals("Byte " + i + " of the private key did not match the original", originalPrivateKeyBytes[i],
privateKeyBytes[i]);
}
Utils.setMockClock();
ECKey key = new ECKey();
long time = key.getCreationTimeSeconds();
assertNotEquals(0, time);
assertTrue(!key.isEncrypted());
byte[] originalPrivateKeyBytes = key.getPrivKeyBytes();
ECKey encryptedKey = key.encrypt(keyCrypter, keyCrypter.deriveKey(PASSWORD1));
assertEquals(time, encryptedKey.getCreationTimeSeconds());
assertTrue(encryptedKey.isEncrypted());
assertNull(encryptedKey.getPrivKeyBytes());
key = encryptedKey.decrypt(keyCrypter, keyCrypter.deriveKey(PASSWORD1));
assertTrue(!key.isEncrypted());
assertArrayEquals(originalPrivateKeyBytes, key.getPrivKeyBytes());
}
@Test
public void testEncryptedCreate() throws Exception {
ECKey unencryptedKey = new ECKey();
// Copy the private key bytes for checking later.
byte[] originalPrivateKeyBytes = new byte[32];
System.arraycopy(unencryptedKey.getPrivKeyBytes(), 0, originalPrivateKeyBytes, 0, 32);
byte[] originalPrivateKeyBytes = checkNotNull(unencryptedKey.getPrivKeyBytes());
log.info("Original private key = " + Utils.bytesToHexString(originalPrivateKeyBytes));
EncryptedPrivateKey encryptedPrivateKey = keyCrypter.encrypt(unencryptedKey.getPrivKeyBytes(), keyCrypter.deriveKey(PASSWORD1));
@@ -316,17 +287,8 @@ public class ECKeyTest {
// Decrypt the key.
ECKey rebornUnencryptedKey = encryptedKey.decrypt(keyCrypter, keyCrypter.deriveKey(PASSWORD1));
// The key should be unencrypted
assertTrue("Key is not unencrypted but it should be", !rebornUnencryptedKey.isEncrypted());
// The reborn unencrypted private key bytes should match the original private key.
privateKeyBytes = rebornUnencryptedKey.getPrivKeyBytes();
log.info("Reborn decrypted private key = " + Utils.bytesToHexString(privateKeyBytes));
for (int i = 0; i < privateKeyBytes.length; i++) {
assertEquals("Byte " + i + " of the private key did not match the original", originalPrivateKeyBytes[i], privateKeyBytes[i]);
}
assertTrue(!rebornUnencryptedKey.isEncrypted());
assertArrayEquals(originalPrivateKeyBytes, rebornUnencryptedKey.getPrivKeyBytes());
}
@Test

View File

@@ -96,7 +96,7 @@ public class FullBlockTestGenerator {
this.params = params;
coinbaseOutKey = new ECKey();
coinbaseOutKeyPubKey = coinbaseOutKey.getPubKey();
Utils.rollMockClock(0); // Set a mock clock for timestamp tests
Utils.setMockClock();
}
public RuleList getBlocksToTest(boolean addSigExpensiveBlocks, boolean runLargeReorgs, File blockStorageFile) throws ScriptException, ProtocolException, IOException {

View File

@@ -500,9 +500,7 @@ public class PeerGroupTest extends TestWithPeerGroup {
stopPeerServer(2);
assertEquals(2002, disconnectedPeers.take().getAddress().getPort()); // peer died
// Peer 2 is tried twice before peer 1, since it has a lower backoff due to recent success
Utils.passMockSleep();
assertEquals(2002, disconnectedPeers.take().getAddress().getPort());
// Peer 2 is tried before peer 1, since it has a lower backoff due to recent success
Utils.passMockSleep();
assertEquals(2002, disconnectedPeers.take().getAddress().getPort());
Utils.passMockSleep();
@@ -540,4 +538,29 @@ public class PeerGroupTest extends TestWithPeerGroup {
assertTrue(p3.lastReceivedFilter.contains(key.getPubKey()));
assertTrue(p3.lastReceivedFilter.contains(outpoint.bitcoinSerialize()));
}
@Test
public void testBloomResendOnNewKey() throws Exception {
// Check that when we add a new key to the wallet, the Bloom filter is re-calculated and re-sent.
peerGroup.startAndWait();
// Create a couple of peers.
InboundMessageQueuer p1 = connectPeer(1);
InboundMessageQueuer p2 = connectPeer(2);
BloomFilter f1 = p1.lastReceivedFilter;
BloomFilter f2 = p2.lastReceivedFilter;
final ECKey key = new ECKey();
wallet.addKey(key);
peerGroup.waitForJobQueue();
BloomFilter f3 = (BloomFilter) outbound(p1);
BloomFilter f4 = (BloomFilter) outbound(p2);
assertTrue(outbound(p1) instanceof MemoryPoolMessage);
assertTrue(outbound(p2) instanceof MemoryPoolMessage);
assertNotEquals(f1, f3);
assertNotEquals(f2, f4);
assertEquals(f3, f4);
assertTrue(f3.contains(key.getPubKey()));
assertTrue(f3.contains(key.getPubKeyHash()));
assertFalse(f1.contains(key.getPubKey()));
assertFalse(f1.contains(key.getPubKeyHash()));
}
}

View File

@@ -36,7 +36,6 @@ import java.math.BigInteger;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.ClosedChannelException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
@@ -436,7 +435,7 @@ public class PeerTest extends TestWithNetworkConnections {
@Test
public void fastCatchup() throws Exception {
connect();
Utils.setMockClock();
// Check that blocks before the fast catchup point are retrieved using getheaders, and after using getblocks.
// This test is INCOMPLETE because it does not check we handle >2000 blocks correctly.
Block b1 = createFakeBlock(blockStore).block;
@@ -484,7 +483,7 @@ public class PeerTest extends TestWithNetworkConnections {
@Test
public void pingPong() throws Exception {
connect();
Utils.rollMockClock(0);
Utils.setMockClock();
// No ping pong happened yet.
assertEquals(Long.MAX_VALUE, peer.getLastPingTime());
assertEquals(Long.MAX_VALUE, peer.getPingTime());

View File

@@ -52,6 +52,7 @@ public class TransactionBroadcastTest extends TestWithPeerGroup {
@Override
@Before
public void setUp() throws Exception {
Utils.setMockClock(); // Use mock clock
super.setUp(new MemoryBlockStore(UnitTestParams.get()));
peerGroup.addWallet(wallet);
// Fix the random permutation that TransactionBroadcast uses to shuffle the peers.

View File

@@ -115,4 +115,13 @@ public class UtilsTest {
Assert.assertArrayEquals(new byte[0], Utils.reverseDwordBytes(new byte[] {4,3,2,1,8,7,6,5}, 0));
Assert.assertArrayEquals(new byte[0], Utils.reverseDwordBytes(new byte[0], 0));
}
@Test
public void testMaxOfMostFreq() throws Exception {
assertEquals(0, Utils.maxOfMostFreq());
assertEquals(0, Utils.maxOfMostFreq(0, 0, 1));
assertEquals(2, Utils.maxOfMostFreq(1, 1, 2, 2));
assertEquals(1, Utils.maxOfMostFreq(1, 1, 2, 2, 1));
assertEquals(-1, Utils.maxOfMostFreq(-1, -1, 2, 2, -1));
}
}

View File

@@ -1,5 +1,6 @@
/**
* Copyright 2011 Google Inc.
* Copyright 2014 Andreas Schildbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +19,8 @@ package com.google.bitcoin.core;
import com.google.bitcoin.core.Transaction.SigHash;
import com.google.bitcoin.core.Wallet.SendRequest;
import com.google.bitcoin.wallet.DefaultCoinSelector;
import com.google.bitcoin.wallet.RiskAnalysis;
import com.google.bitcoin.wallet.WalletTransaction;
import com.google.bitcoin.wallet.WalletTransaction.Pool;
import com.google.bitcoin.crypto.KeyCrypter;
@@ -67,8 +70,6 @@ public class WalletTest extends TestWithWallet {
private Address myEncryptedAddress2;
private Wallet encryptedWallet;
// A wallet with an initial unencrypted private key and an encrypted private key.
private Wallet encryptedMixedWallet;
private static CharSequence PASSWORD1 = "my helicopter contains eels";
private static CharSequence WRONG_PASSWORD = "nothing noone nobody nowhere";
@@ -89,16 +90,11 @@ public class WalletTest extends TestWithWallet {
keyCrypter = new KeyCrypterScrypt(scryptParameters);
encryptedWallet = new Wallet(params, keyCrypter);
encryptedMixedWallet = new Wallet(params, keyCrypter);
aesKey = keyCrypter.deriveKey(PASSWORD1);
wrongAesKey = keyCrypter.deriveKey(WRONG_PASSWORD);
ECKey myEncryptedKey = encryptedWallet.addNewEncryptedKey(keyCrypter, aesKey);
myEncryptedAddress = myEncryptedKey.toAddress(params);
encryptedMixedWallet.addKey(new ECKey());
ECKey myEncryptedKey2 = encryptedMixedWallet.addNewEncryptedKey(keyCrypter, aesKey);
myEncryptedAddress2 = myEncryptedKey2.toAddress(params);
}
@After
@@ -111,7 +107,7 @@ public class WalletTest extends TestWithWallet {
public void basicSpending() throws Exception {
basicSpendingCommon(wallet, myAddress, new ECKey().toAddress(params), false);
}
@Test
public void basicSpendingToP2SH() throws Exception {
Address destination = new Address(params, params.getP2SHHeader(), Hex.decode("4a22c3c4cbb31e4d03b15550636762bda0baf85a"));
@@ -123,9 +119,114 @@ public class WalletTest extends TestWithWallet {
basicSpendingCommon(encryptedWallet, myEncryptedAddress, new ECKey().toAddress(params), true);
}
static class TestRiskAnalysis implements RiskAnalysis {
private final boolean risky;
public TestRiskAnalysis(boolean risky) {
this.risky = risky;
}
@Override
public Result analyze() {
return risky ? Result.NON_FINAL : Result.OK;
}
public static class Analyzer implements RiskAnalysis.Analyzer {
private final Transaction riskyTx;
Analyzer(Transaction riskyTx) {
this.riskyTx = riskyTx;
}
@Override
public RiskAnalysis create(Wallet wallet, Transaction tx, List<Transaction> dependencies) {
return new TestRiskAnalysis(tx == riskyTx);
}
}
}
static class TestCoinSelector extends DefaultCoinSelector {
@Override
protected boolean shouldSelect(Transaction tx) {
return true;
}
}
private Transaction cleanupCommon(Address destination) throws Exception {
receiveATransaction(wallet, myAddress);
BigInteger v2 = toNanoCoins(0, 50);
SendRequest req = SendRequest.to(destination, v2);
req.fee = toNanoCoins(0, 1);
wallet.completeTx(req);
Transaction t2 = req.tx;
// Broadcast the transaction and commit.
broadcastAndCommit(wallet, t2);
// At this point we have one pending and one spent
BigInteger v1 = toNanoCoins(0, 10);
Transaction t = sendMoneyToWallet(wallet, v1, myAddress, null);
Threading.waitForUserCode();
sendMoneyToWallet(wallet, t, null);
assertEquals("Wrong number of PENDING.4", 2, wallet.getPoolSize(Pool.PENDING));
assertEquals("Wrong number of UNSPENT.4", 0, wallet.getPoolSize(Pool.UNSPENT));
assertEquals("Wrong number of ALL.4", 3, wallet.getTransactions(true).size());
assertEquals(toNanoCoins(0, 59), wallet.getBalance(Wallet.BalanceType.ESTIMATED));
// Now we have another incoming pending
return t;
}
@Test
public void basicSpendingWithEncryptedMixedWallet() throws Exception {
basicSpendingCommon(encryptedMixedWallet, myEncryptedAddress2, new ECKey().toAddress(params), true);
public void cleanup() throws Exception {
Address destination = new ECKey().toAddress(params);
Transaction t = cleanupCommon(destination);
// Consider the new pending as risky and remove it from the wallet
wallet.setRiskAnalyzer(new TestRiskAnalysis.Analyzer(t));
wallet.cleanup();
assertTrue(wallet.isConsistent());
assertEquals("Wrong number of PENDING.5", 1, wallet.getPoolSize(WalletTransaction.Pool.PENDING));
assertEquals("Wrong number of UNSPENT.5", 0, wallet.getPoolSize(WalletTransaction.Pool.UNSPENT));
assertEquals("Wrong number of ALL.5", 2, wallet.getTransactions(true).size());
assertEquals(toNanoCoins(0, 49), wallet.getBalance(Wallet.BalanceType.ESTIMATED));
}
@Test
public void cleanupFailsDueToSpend() throws Exception {
Address destination = new ECKey().toAddress(params);
Transaction t = cleanupCommon(destination);
// Now we have another incoming pending. Spend everything.
BigInteger v3 = toNanoCoins(0, 58);
SendRequest req = SendRequest.to(destination, v3);
// Force selection of the incoming coin so that we can spend it
req.coinSelector = new TestCoinSelector();
req.fee = toNanoCoins(0, 1);
wallet.completeTx(req);
wallet.commitTx(req.tx);
assertEquals("Wrong number of PENDING.5", 3, wallet.getPoolSize(WalletTransaction.Pool.PENDING));
assertEquals("Wrong number of UNSPENT.5", 0, wallet.getPoolSize(WalletTransaction.Pool.UNSPENT));
assertEquals("Wrong number of ALL.5", 4, wallet.getTransactions(true).size());
// Consider the new pending as risky and try to remove it from the wallet
wallet.setRiskAnalyzer(new TestRiskAnalysis.Analyzer(t));
wallet.cleanup();
assertTrue(wallet.isConsistent());
// The removal should have failed
assertEquals("Wrong number of PENDING.5", 3, wallet.getPoolSize(WalletTransaction.Pool.PENDING));
assertEquals("Wrong number of UNSPENT.5", 0, wallet.getPoolSize(WalletTransaction.Pool.UNSPENT));
assertEquals("Wrong number of ALL.5", 4, wallet.getTransactions(true).size());
assertEquals(toNanoCoins(0, 0), wallet.getBalance(Wallet.BalanceType.ESTIMATED));
}
private void basicSpendingCommon(Wallet wallet, Address toAddress, Address destination, boolean testEncryption) throws Exception {
@@ -825,7 +926,7 @@ public class WalletTest extends TestWithWallet {
@Test
public void transactionsList() throws Exception {
// Check the wallet can give us an ordered list of all received transactions.
Utils.rollMockClock(0);
Utils.setMockClock();
Transaction tx1 = sendMoneyToWallet(Utils.toNanoCoins(1, 0), AbstractBlockChain.NewBlockType.BEST_CHAIN);
Utils.rollMockClock(60 * 10);
Transaction tx2 = sendMoneyToWallet(Utils.toNanoCoins(0, 5), AbstractBlockChain.NewBlockType.BEST_CHAIN);
@@ -863,7 +964,8 @@ public class WalletTest extends TestWithWallet {
@Test
public void keyCreationTime() throws Exception {
wallet = new Wallet(params);
long now = Utils.rollMockClock(0).getTime() / 1000; // Fix the mock clock.
Utils.setMockClock();
long now = Utils.currentTimeMillis() / 1000;
// No keys returns current time.
assertEquals(now, wallet.getEarliestKeyCreationTime());
Utils.rollMockClock(60);
@@ -877,7 +979,8 @@ public class WalletTest extends TestWithWallet {
@Test
public void scriptCreationTime() throws Exception {
wallet = new Wallet(params);
long now = Utils.rollMockClock(0).getTime() / 1000; // Fix the mock clock.
Utils.setMockClock();
long now = Utils.currentTimeMillis() / 1000;
// No keys returns current time.
assertEquals(now, wallet.getEarliestKeyCreationTime());
Utils.rollMockClock(60);
@@ -1109,9 +1212,24 @@ public class WalletTest extends TestWithWallet {
// Wait for an auto-save to occur.
latch.await();
assertFalse(hash4.equals(Sha256Hash.hashFileContents(f))); // File has now changed.
Sha256Hash hash5 = Sha256Hash.hashFileContents(f);
assertFalse(hash4.equals(hash5)); // File has now changed.
assertNotNull(results[0]);
assertEquals(f, results[1]);
// Now we shutdown auto-saving and expect wallet changes to remain unsaved, even "important" changes.
wallet.shutdownAutosaveAndWait();
results[0] = results[1] = null;
ECKey key2 = new ECKey();
wallet.addKey(key2);
assertEquals(hash5, Sha256Hash.hashFileContents(f)); // File has NOT changed.
Transaction t2 = createFakeTx(params, toNanoCoins(5, 0), key2);
Block b3 = createFakeBlock(blockStore, t2).block;
chain.add(b3);
Thread.sleep(2000); // Wait longer than autosave delay. TODO Fix the racyness.
assertEquals(hash5, Sha256Hash.hashFileContents(f)); // File has still NOT changed.
assertNull(results[0]);
assertNull(results[1]);
}
@Test
@@ -1178,38 +1296,33 @@ public class WalletTest extends TestWithWallet {
@Test
public void encryptionDecryptionBasic() throws Exception {
encryptionDecryptionBasicCommon(encryptedWallet);
encryptionDecryptionBasicCommon(encryptedMixedWallet);
}
private void encryptionDecryptionBasicCommon(Wallet wallet) {
// Check the wallet is initially of WalletType ENCRYPTED.
assertTrue("Wallet is not an encrypted wallet", wallet.getEncryptionType() == EncryptionType.ENCRYPTED_SCRYPT_AES);
assertTrue("Wallet is not an encrypted wallet", encryptedWallet.getEncryptionType() == EncryptionType.ENCRYPTED_SCRYPT_AES);
// Correct password should decrypt first encrypted private key.
assertTrue("checkPassword result is wrong with correct password.2", wallet.checkPassword(PASSWORD1));
assertTrue("checkPassword result is wrong with correct password.2", encryptedWallet.checkPassword(PASSWORD1));
// Incorrect password should not decrypt first encrypted private key.
assertFalse("checkPassword result is wrong with incorrect password.3", wallet.checkPassword(WRONG_PASSWORD));
assertFalse("checkPassword result is wrong with incorrect password.3", encryptedWallet.checkPassword(WRONG_PASSWORD));
// Decrypt wallet.
assertTrue("The keyCrypter is missing but should not be", keyCrypter != null);
wallet.decrypt(aesKey);
encryptedWallet.decrypt(aesKey);
// Wallet should now be unencrypted.
assertTrue("Wallet is not an unencrypted wallet", wallet.getKeyCrypter() == null);
assertTrue("Wallet is not an unencrypted wallet", encryptedWallet.getKeyCrypter() == null);
// Correct password should not decrypt first encrypted private key as wallet is unencrypted.
assertTrue("checkPassword result is wrong with correct password", !wallet.checkPassword(PASSWORD1));
assertTrue("checkPassword result is wrong with correct password", !encryptedWallet.checkPassword(PASSWORD1));
// Incorrect password should not decrypt first encrypted private key as wallet is unencrypted.
assertTrue("checkPassword result is wrong with incorrect password", !wallet.checkPassword(WRONG_PASSWORD));
assertTrue("checkPassword result is wrong with incorrect password", !encryptedWallet.checkPassword(WRONG_PASSWORD));
// Encrypt wallet.
wallet.encrypt(keyCrypter, aesKey);
encryptedWallet.encrypt(keyCrypter, aesKey);
// Wallet should now be of type WalletType.ENCRYPTED_SCRYPT_AES.
assertTrue("Wallet is not an encrypted wallet", wallet.getEncryptionType() == EncryptionType.ENCRYPTED_SCRYPT_AES);
assertTrue("Wallet is not an encrypted wallet", encryptedWallet.getEncryptionType() == EncryptionType.ENCRYPTED_SCRYPT_AES);
}
@Test
@@ -1260,6 +1373,19 @@ public class WalletTest extends TestWithWallet {
assertTrue("Wallet is not an encrypted wallet.3", encryptedWallet.getEncryptionType() == EncryptionType.ENCRYPTED_SCRYPT_AES);
}
@Test(expected = KeyCrypterException.class)
public void addUnencryptedKeyToEncryptedWallet() throws Exception {
ECKey key1 = new ECKey();
encryptedWallet.addKey(key1);
}
@Test(expected = KeyCrypterException.class)
public void addEncryptedKeyToUnencryptedWallet() throws Exception {
ECKey key1 = new ECKey();
key1 = key1.encrypt(keyCrypter, keyCrypter.deriveKey("PASSWORD!"));
wallet.addKey(key1);
}
@Test
public void encryptionDecryptionHomogenousKeys() throws Exception {
// Check the wallet is currently encrypted
@@ -2040,6 +2166,7 @@ public class WalletTest extends TestWithWallet {
@Test
public void keyRotation() throws Exception {
Utils.setMockClock();
// Watch out for wallet-initiated broadcasts.
MockTransactionBroadcaster broadcaster = new MockTransactionBroadcaster(wallet);
wallet.setTransactionBroadcaster(broadcaster);
@@ -2112,6 +2239,7 @@ public class WalletTest extends TestWithWallet {
ECKey key = new ECKey();
wallet.addKey(key);
Address address = key.toAddress(params);
Utils.setMockClock();
Utils.rollMockClock(86400);
for (int i = 0; i < 800; i++) {
sendMoneyToWallet(wallet, Utils.CENT, address, AbstractBlockChain.NewBlockType.BEST_CHAIN);

View File

@@ -35,7 +35,7 @@ import java.util.Arrays;
import java.util.List;
/**
* A test with test vectors as per BIP 32 spec: https://en.bitcoin.it/wiki/BIP_0032#Test_Vectors
* A test with test vectors as per BIP 32 spec: https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#Test_Vectors
*/
public class BIP32Test {
private static final Logger log = LoggerFactory.getLogger(BIP32Test.class);

View File

@@ -63,6 +63,7 @@ public class ChannelConnectionTest extends TestWithWallet {
@Before
public void setUp() throws Exception {
super.setUp();
Utils.setMockClock(); // Use mock clock
sendMoneyToWallet(Utils.COIN, AbstractBlockChain.NewBlockType.BEST_CHAIN);
sendMoneyToWallet(Utils.COIN, AbstractBlockChain.NewBlockType.BEST_CHAIN);
wallet.addExtension(new StoredPaymentChannelClientStates(wallet, failBroadcaster));
@@ -267,7 +268,7 @@ public class ChannelConnectionTest extends TestWithWallet {
@Test
public void testChannelResume() throws Exception {
// Tests various aspects of channel resuming.
Utils.rollMockClock(0);
Utils.setMockClock();
final Sha256Hash someServerId = Sha256Hash.create(new byte[]{});

View File

@@ -114,7 +114,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
public void basic() throws Exception {
// Check it all works when things are normal (no attacks, no problems).
Utils.rollMockClock(0); // Use mock clock
Utils.setMockClock(); // Use mock clock
final long EXPIRE_TIME = Utils.currentTimeMillis()/1000 + 60*60*24;
serverState = new PaymentChannelServerState(mockBroadcaster, serverWallet, serverKey, EXPIRE_TIME);
@@ -228,7 +228,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
StoredPaymentChannelClientStates stateStorage = new StoredPaymentChannelClientStates(wallet, mockBroadcaster);
wallet.addOrUpdateExtension(stateStorage);
Utils.rollMockClock(0); // Use mock clock
Utils.setMockClock(); // Use mock clock
final long EXPIRE_TIME = Utils.currentTimeMillis()/1000 + 60*60*24;
serverState = new PaymentChannelServerState(mockBroadcaster, serverWallet, serverKey, EXPIRE_TIME);
@@ -330,7 +330,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
// We'll broadcast only one tx: multisig contract
Utils.rollMockClock(0); // Use mock clock
Utils.setMockClock(); // Use mock clock
final long EXPIRE_TIME = Utils.currentTimeMillis()/1000 + 60*60*24;
serverState = new PaymentChannelServerState(mockBroadcaster, serverWallet, serverKey, EXPIRE_TIME);
@@ -541,7 +541,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
chain.add(makeSolvedTestBlock(blockStore.getChainHead().getHeader(), createFakeTx(params, Utils.CENT, myAddress)));
assertEquals(Utils.CENT, wallet.getBalance());
Utils.rollMockClock(0); // Use mock clock
Utils.setMockClock(); // Use mock clock
final long EXPIRE_TIME = Utils.currentTimeMillis()/1000 + 60*60*24;
serverState = new PaymentChannelServerState(mockBroadcaster, serverWallet, serverKey, EXPIRE_TIME);
@@ -644,7 +644,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
public void serverAddsFeeTest() throws Exception {
// Test that the server properly adds the necessary fee at the end (or just drops the payment if its not worth it)
Utils.rollMockClock(0); // Use mock clock
Utils.setMockClock(); // Use mock clock
final long EXPIRE_TIME = Utils.currentTimeMillis()/1000 + 60*60*24;
serverState = new PaymentChannelServerState(mockBroadcaster, serverWallet, serverKey, EXPIRE_TIME);
@@ -730,7 +730,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
// Tests that if the client double-spends the multisig contract after it is sent, no more payments are accepted
// Start with a copy of basic()....
Utils.rollMockClock(0); // Use mock clock
Utils.setMockClock(); // Use mock clock
final long EXPIRE_TIME = Utils.currentTimeMillis()/1000 + 60*60*24;
serverState = new PaymentChannelServerState(mockBroadcaster, serverWallet, serverKey, EXPIRE_TIME);

View File

@@ -1,5 +1,6 @@
/**
* Copyright 2013 Google Inc.
* Copyright 2014 Andreas Schildbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -82,6 +83,23 @@ public class PaymentSessionTest {
assertTrue(refundScript.equals(payment.getRefundTo(0).getScript()));
}
@Test
public void testDefaults() throws Exception {
Protos.Output.Builder outputBuilder = Protos.Output.newBuilder()
.setScript(ByteString.copyFrom(outputToMe.getScriptBytes()));
Protos.PaymentDetails paymentDetails = Protos.PaymentDetails.newBuilder()
.setTime(time)
.addOutputs(outputBuilder)
.build();
Protos.PaymentRequest paymentRequest = Protos.PaymentRequest.newBuilder()
.setSerializedPaymentDetails(paymentDetails.toByteString())
.build();
MockPaymentSession paymentSession = new MockPaymentSession(paymentRequest);
assertEquals(BigInteger.ZERO, paymentSession.getValue());
assertNull(paymentSession.getPaymentUrl());
assertNull(paymentSession.getMemo());
}
@Test
public void testExpiredPaymentRequest() throws Exception {
MockPaymentSession paymentSession = new MockPaymentSession(newExpiredPaymentRequest());
@@ -108,6 +126,7 @@ public class PaymentSessionTest {
MockPaymentSession paymentSession = new MockPaymentSession(paymentRequest);
PaymentSession.PkiVerificationData pkiData = paymentSession.verifyPki();
assertEquals("www.bitcoincore.org", pkiData.name);
assertEquals("The USERTRUST Network, Salt Lake City, US", pkiData.rootAuthorityName);
}
private Protos.PaymentRequest newSimplePaymentRequest() {

View File

@@ -1,5 +1,6 @@
/**
* Copyright 2011 Google Inc.
* Copyright 2014 Andreas Schildbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -192,10 +193,15 @@ public class ScriptTest {
if (line.trim().endsWith("],") || line.trim().equals("]")) {
String[] scripts = script.split(",");
try {
Script scriptSig = parseScriptString(scripts[0].replaceAll("[\"\\[\\]]", "").trim());
Script scriptPubKey = parseScriptString(scripts[1].replaceAll("[\"\\[\\]]", "").trim());
scripts[0] = scripts[0].replaceAll("[\"\\[\\]]", "").trim();
scripts[1] = scripts[1].replaceAll("[\"\\[\\]]", "").trim();
Script scriptSig = parseScriptString(scripts[0]);
Script scriptPubKey = parseScriptString(scripts[1]);
scriptSig.correctlySpends(new Transaction(params), 0, scriptPubKey, true);
System.err.println("scriptSig: " + scripts[0]);
System.err.println("scriptPubKey: " + scripts[1]);
System.err.flush();
fail();
} catch (VerificationException e) {
// Expected.

View File

@@ -1,3 +1,20 @@
/**
* Copyright 2012 Google Inc.
* Copyright 2014 Andreas Schildbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.bitcoin.store;
@@ -246,6 +263,8 @@ public class WalletProtobufSerializerTest {
ByteArrayOutputStream output = new ByteArrayOutputStream();
//System.out.println(WalletProtobufSerializer.walletToText(wallet));
new WalletProtobufSerializer().writeWallet(wallet, output);
ByteArrayInputStream test = new ByteArrayInputStream(output.toByteArray());
assertTrue(WalletProtobufSerializer.isWallet(test));
ByteArrayInputStream input = new ByteArrayInputStream(output.toByteArray());
return new WalletProtobufSerializer().readWallet(input);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012 the original author or authors.
* Copyright 2012, 2014 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -271,38 +271,16 @@ public class BitcoinURITest {
}
}
/**
* Handles a badly formatted label field
*
* @throws BitcoinURIParseException
* If something goes wrong
*/
@Test
public void testBad_Label() throws BitcoinURIParseException {
try {
testObject = new BitcoinURI(MainNetParams.get(), BitcoinURI.BITCOIN_SCHEME + ":" + MAINNET_GOOD_ADDRESS
+ "?label=");
fail("Expecting BitcoinURIParseException");
} catch (BitcoinURIParseException e) {
assertTrue(e.getMessage().contains("label"));
}
public void testEmpty_Label() throws BitcoinURIParseException {
assertNull(new BitcoinURI(MainNetParams.get(), BitcoinURI.BITCOIN_SCHEME + ":" + MAINNET_GOOD_ADDRESS
+ "?label=").getLabel());
}
/**
* Handles a badly formatted message field
*
* @throws BitcoinURIParseException
* If something goes wrong
*/
@Test
public void testBad_Message() throws BitcoinURIParseException {
try {
testObject = new BitcoinURI(MainNetParams.get(), BitcoinURI.BITCOIN_SCHEME + ":" + MAINNET_GOOD_ADDRESS
+ "?message=");
fail("Expecting BitcoinURIParseException");
} catch (BitcoinURIParseException e) {
assertTrue(e.getMessage().contains("message"));
}
public void testEmpty_Message() throws BitcoinURIParseException {
assertNull(new BitcoinURI(MainNetParams.get(), BitcoinURI.BITCOIN_SCHEME + ":" + MAINNET_GOOD_ADDRESS
+ "?message=").getMessage());
}
/**
@@ -322,21 +300,10 @@ public class BitcoinURITest {
}
}
/**
* Handles case when there are too many equals
*
* @throws BitcoinURIParseException
* If something goes wrong
*/
@Test
public void testBad_TooManyEquals() throws BitcoinURIParseException {
try {
testObject = new BitcoinURI(MainNetParams.get(), BitcoinURI.BITCOIN_SCHEME + ":" + MAINNET_GOOD_ADDRESS
+ "?label=aardvark=zebra");
fail("Expecting BitcoinURIParseException");
} catch (BitcoinURIParseException e) {
assertTrue(e.getMessage().contains("cannot parse name value pair"));
}
public void testGood_ManyEquals() throws BitcoinURIParseException {
assertEquals("aardvark=zebra", new BitcoinURI(MainNetParams.get(), BitcoinURI.BITCOIN_SCHEME + ":"
+ MAINNET_GOOD_ADDRESS + "?label=aardvark=zebra").getLabel());
}
/**
@@ -377,7 +344,7 @@ public class BitcoinURITest {
+ "?aardvark");
fail("Expecting BitcoinURIParseException");
} catch (BitcoinURIParseException e) {
assertTrue(e.getMessage().contains("cannot parse name value pair"));
assertTrue(e.getMessage().contains("no separator"));
}
// Unknown and required field

View File

@@ -39,6 +39,7 @@ public class DefaultCoinSelectorTest extends TestWithWallet {
@Override
public void setUp() throws Exception {
super.setUp();
Utils.setMockClock(); // Use mock clock
}
@After

View File

@@ -1,5 +1,6 @@
/*
* Copyright 2013 Google Inc.
* Copyright 2014 Andreas Schildbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,8 +17,10 @@
package com.google.bitcoin.wallet;
import java.math.BigInteger;
import com.google.bitcoin.core.*;
import com.google.bitcoin.params.UnitTestParams;
import com.google.bitcoin.params.MainNetParams;
import com.google.common.collect.ImmutableList;
import org.junit.Before;
import org.junit.Test;
@@ -27,7 +30,8 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
public class DefaultRiskAnalysisTest {
private static final NetworkParameters params = UnitTestParams.get();
// Uses mainnet because isStandard checks are disabled on testnet.
private static final NetworkParameters params = MainNetParams.get();
private Wallet wallet;
private final int TIMESTAMP = 1384190189;
private ECKey key1;
@@ -119,4 +123,22 @@ public class DefaultRiskAnalysisTest {
assertEquals(RiskAnalysis.Result.NON_FINAL, analysis.analyze());
assertEquals(tx1, analysis.getNonFinal());
}
@Test
public void nonStandardDust() {
Transaction standardTx = new Transaction(params);
standardTx.addInput(params.getGenesisBlock().getTransactions().get(0).getOutput(0));
standardTx.addOutput(Utils.COIN, key1);
assertEquals(RiskAnalysis.Result.OK, DefaultRiskAnalysis.FACTORY.create(wallet, standardTx, NO_DEPS).analyze());
Transaction dustTx = new Transaction(params);
dustTx.addInput(params.getGenesisBlock().getTransactions().get(0).getOutput(0));
dustTx.addOutput(BigInteger.ONE, key1); // 1 Satoshi
assertEquals(RiskAnalysis.Result.NON_STANDARD, DefaultRiskAnalysis.FACTORY.create(wallet, dustTx, NO_DEPS).analyze());
Transaction edgeCaseTx = new Transaction(params);
edgeCaseTx.addInput(params.getGenesisBlock().getTransactions().get(0).getOutput(0));
edgeCaseTx.addOutput(DefaultRiskAnalysis.MIN_ANALYSIS_NONDUST_OUTPUT, key1); // Dust threshold
assertEquals(RiskAnalysis.Result.OK, DefaultRiskAnalysis.FACTORY.create(wallet, edgeCaseTx, NO_DEPS).analyze());
}
}

View File

@@ -325,5 +325,7 @@
["NOP1 0x01 1", "HASH160 0x14 0xda1745e9b549bd0bfa1a569971c77eba30cd5a4b EQUAL"],
["0 0x01 0x50", "HASH160 0x14 0xece424a6bb6ddf4db592c0faed60685047a361b1 EQUAL", "OP_RESERVED in P2SH should fail"],
["0 0x01 VER", "HASH160 0x14 0x0f4d7845db968f2a81b530b6f3c1d6246d4c7e01 EQUAL", "OP_VER in P2SH should fail"]
["0 0x01 VER", "HASH160 0x14 0x0f4d7845db968f2a81b530b6f3c1d6246d4c7e01 EQUAL", "OP_VER in P2SH should fail"],
["0x00", "'00' EQUAL", "Basic OP_0 execution"]
]

View File

@@ -411,5 +411,7 @@
["0x4c 0x40 0x42424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242",
"0x4d 0x4000 0x42424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242424242 EQUAL",
"Basic PUSHDATA1 signedness check"]
"Basic PUSHDATA1 signedness check"],
["0x00", "SIZE 0 EQUAL", "Basic OP_0 execution"]
]

View File

@@ -21,7 +21,7 @@
<parent>
<groupId>com.google</groupId>
<artifactId>bitcoinj-parent</artifactId>
<version>0.11-SNAPSHOT</version>
<version>0.11.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -43,7 +43,7 @@ public class ForwardingService {
public static void main(String[] args) throws Exception {
// This line makes the log output more compact and easily read, especially when using the JDK log adapter.
BriefLogFormatter.init();
if (args.length < 2) {
if (args.length < 1) {
System.err.println("Usage: address-to-send-back-to [regtest|testnet]");
return;
}
@@ -51,10 +51,10 @@ public class ForwardingService {
// Figure out which network we should connect to. Each one gets its own set of files.
NetworkParameters params;
String filePrefix;
if (args[1].equals("testnet")) {
if (args.length > 1 && args[1].equals("testnet")) {
params = TestNet3Params.get();
filePrefix = "forwarding-service-testnet";
} else if (args[1].equals("regtest")) {
} else if (args.length > 1 && args[1].equals("regtest")) {
params = RegTestParams.get();
filePrefix = "forwarding-service-regtest";
} else {

View File

@@ -4,7 +4,7 @@
<groupId>com.google</groupId>
<artifactId>bitcoinj-parent</artifactId>
<version>0.11-SNAPSHOT</version>
<version>0.11.2</version>
<packaging>pom</packaging>
<modules>

View File

@@ -21,7 +21,7 @@
<parent>
<groupId>com.google</groupId>
<artifactId>bitcoinj-parent</artifactId>
<version>0.11-SNAPSHOT</version>
<version>0.11.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -539,6 +539,7 @@ public class WalletTool {
System.out.println("Pki-Verified Name: " + session.pkiVerificationData.name);
if (session.pkiVerificationData.orgName != null)
System.out.println("Pki-Verified Org: " + session.pkiVerificationData.orgName);
System.out.println("PKI data verified by: " + session.pkiVerificationData.rootAuthorityName);
}
final Wallet.SendRequest req = session.getSendRequest();
if (password != null) {

View File

@@ -6,7 +6,7 @@
<groupId>org.bitcoinj</groupId>
<artifactId>wallettemplate</artifactId>
<version>1.0-SNAPSHOT</version>
<version>1.0</version>
<build>
<plugins>
@@ -27,7 +27,7 @@
<dependency>
<groupId>com.google</groupId>
<artifactId>bitcoinj</artifactId>
<version>0.11-SNAPSHOT</version>
<version>0.11</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
@@ -37,7 +37,7 @@
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>13.0</version>
<version>13.0.1</version>
</dependency>
<dependency>
<groupId>com.aquafx-project</groupId>

View File

@@ -36,7 +36,7 @@ public class SendMoneyController {
try {
Address destination = new Address(Main.params, address.getText());
Wallet.SendRequest req = Wallet.SendRequest.emptyWallet(destination);
Main.bitcoin.wallet().sendCoins(req);
sendResult = Main.bitcoin.wallet().sendCoins(req);
Futures.addCallback(sendResult.broadcastComplete, new FutureCallback<Transaction>() {
@Override
public void onSuccess(Transaction result) {

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<?scenebuilder-classpath-element ../../../../target/classes?>
<?scenebuilder-classpath-element ../../../../../core/target/bitcoinj-0.11-SNAPSHOT.jar?>
<?scenebuilder-classpath-element ../../../../../core/target/bitcoinj-0.11.jar?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.geometry.*?>