mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-08-01 12:31:23 +00:00
Compare commits
13 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
95abd140f0 | ||
|
e783895dd3 | ||
|
cc89cd0d16 | ||
|
28a5d8abbc | ||
|
3ffcce49f9 | ||
|
f581a53aa0 | ||
|
a5bc2f3794 | ||
|
5543b166cc | ||
|
3755835d11 | ||
|
0ee3dca7a0 | ||
|
1250aa8e3d | ||
|
410d4547a7 | ||
|
71391c1271 |
@@ -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 -->
|
||||
|
@@ -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;
|
||||
|
@@ -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.
|
||||
|
@@ -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 + "/";
|
||||
|
||||
|
@@ -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)));
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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";
|
||||
}
|
||||
|
@@ -34,7 +34,8 @@ import java.util.List;
|
||||
public interface RiskAnalysis {
|
||||
public enum Result {
|
||||
OK,
|
||||
NON_FINAL
|
||||
NON_FINAL,
|
||||
NON_STANDARD
|
||||
}
|
||||
|
||||
public Result analyze();
|
||||
|
@@ -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()));
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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() {
|
||||
|
@@ -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());
|
||||
}
|
||||
}
|
||||
|
@@ -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>
|
||||
|
||||
|
2
pom.xml
2
pom.xml
@@ -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>
|
||||
|
@@ -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>
|
||||
|
||||
|
@@ -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) {
|
||||
|
@@ -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>
|
||||
|
@@ -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.*?>
|
||||
|
Reference in New Issue
Block a user