13 Commits

Author SHA1 Message Date
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
18 changed files with 415 additions and 47 deletions

View File

@@ -22,7 +22,7 @@
<parent>
<groupId>com.google</groupId>
<artifactId>bitcoinj-parent</artifactId>
<version>0.11-SNAPSHOT</version>
<version>0.11.1</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

@@ -803,11 +803,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;

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.

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.1";
/** The value that is prepended to the subVer field of this application. */
public static final String LIBRARY_SUBVER = "/BitCoinJ:" + BITCOINJ_VERSION + "/";

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;
@@ -1424,6 +1426,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 {
@@ -2264,6 +2300,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.
@@ -2280,9 +2340,9 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
BigInteger balance = getBalance(BalanceType.ESTIMATED);
builder.append(String.format("Wallet containing %s BTC (%d satoshis) in:%n",
bitcoinValueToPlainString(balance), balance.longValue()));
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();
@@ -2312,21 +2372,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 +2402,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

@@ -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;
@@ -72,6 +74,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 +230,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 +381,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 +393,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;
}
}
}
/**
@@ -461,13 +505,10 @@ public class PaymentSession {
else if (pair.getType().equals(RFC4519Style.o))
orgName = ((ASN1String)pair.getValue()).getString();
}
if (entityName == null && orgName == null)
throw new PaymentRequestException.PkiVerificationException("Invalid certificate, no CN or O fields");
// 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 +598,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 +620,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

@@ -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,14 @@
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.util.List;
import static com.google.common.base.Preconditions.checkState;
@@ -34,6 +38,7 @@ public class DefaultRiskAnalysis implements RiskAnalysis {
protected final List<Transaction> dependencies;
protected final Wallet wallet;
private Transaction nonStandard;
protected Transaction nonFinal;
protected boolean analyzed;
@@ -48,6 +53,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 +84,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 (output.getMinNonDustValue().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 +139,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

@@ -540,4 +540,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

@@ -18,6 +18,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;
@@ -111,7 +113,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"));
@@ -128,6 +130,116 @@ public class WalletTest extends TestWithWallet {
basicSpendingCommon(encryptedMixedWallet, myEncryptedAddress2, 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 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 {
// We'll set up a wallet that receives a coin, then sends a coin of lesser value and keeps the change. We
// will attach a small fee. Because the Bitcoin protocol makes it difficult to determine the fee of an

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 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(dustTx.getOutput(0).getMinNonDustValue(), key1); // Dust threshold
assertEquals(RiskAnalysis.Result.OK, DefaultRiskAnalysis.FACTORY.create(wallet, edgeCaseTx, NO_DEPS).analyze());
}
}

View File

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

View File

@@ -4,7 +4,7 @@
<groupId>com.google</groupId>
<artifactId>bitcoinj-parent</artifactId>
<version>0.11-SNAPSHOT</version>
<version>0.11.1</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.1</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

@@ -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.*?>