diff --git a/core/src/main/java/com/google/bitcoin/protocols/payments/PaymentSession.java b/core/src/main/java/com/google/bitcoin/protocols/payments/PaymentSession.java
index 41f2094c..fd1bc867 100644
--- a/core/src/main/java/com/google/bitcoin/protocols/payments/PaymentSession.java
+++ b/core/src/main/java/com/google/bitcoin/protocols/payments/PaymentSession.java
@@ -18,24 +18,21 @@ package com.google.bitcoin.protocols.payments;
import com.google.bitcoin.core.*;
import com.google.bitcoin.params.MainNetParams;
-import com.google.bitcoin.params.TestNet3Params;
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.collect.Lists;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.common.util.concurrent.MoreExecutors;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import org.bitcoin.protocols.payments.Protos;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.spongycastle.asn1.ASN1String;
import org.spongycastle.asn1.x500.AttributeTypeAndValue;
import org.spongycastle.asn1.x500.RDN;
-import org.spongycastle.asn1.x500.style.RFC4519Style;
import org.spongycastle.asn1.x500.X500Name;
+import org.spongycastle.asn1.x500.style.RFC4519Style;
import javax.annotation.Nullable;
import javax.security.auth.x500.X500Principal;
@@ -47,7 +44,6 @@ import java.security.cert.*;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
-import java.util.concurrent.Executors;
/**
*
Provides a standard implementation of the Payment Protocol (BIP 0070)
@@ -79,8 +75,7 @@ import java.util.concurrent.Executors;
* @see BIP 0070
*/
public class PaymentSession {
- private static final Logger log = LoggerFactory.getLogger(PaymentSession.class);
- private ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
+ private static ListeningExecutorService executor = Threading.THREAD_POOL;
private NetworkParameters params;
private String trustStorePath;
private Protos.PaymentRequest paymentRequest;
@@ -184,7 +179,6 @@ public class PaymentSession {
}
private static ListenableFuture fetchPaymentRequest(final URI uri, final boolean verifyPki, @Nullable final String trustStorePath) {
- ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
return executor.submit(new Callable() {
@Override
public PaymentSession call() throws Exception {
diff --git a/core/src/main/java/com/google/bitcoin/utils/Threading.java b/core/src/main/java/com/google/bitcoin/utils/Threading.java
index 072a73a7..adcf576c 100644
--- a/core/src/main/java/com/google/bitcoin/utils/Threading.java
+++ b/core/src/main/java/com/google/bitcoin/utils/Threading.java
@@ -17,16 +17,15 @@
package com.google.bitcoin.utils;
import com.google.common.util.concurrent.CycleDetectingLockFactory;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.Uninterruptibles;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantLock;
/**
@@ -35,6 +34,13 @@ import java.util.concurrent.locks.ReentrantLock;
* Also provides a worker thread that is designed for event listeners to be dispatched on.
*/
public class Threading {
+
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // User thread/event handling utilities
+ //
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
/**
* An executor with one thread that is intended for running event listeners on. This ensures all event listener code
* runs without any locks being held. It's intended for the API user to run things on. Callbacks registered by
@@ -78,8 +84,6 @@ public class Threading {
@Nullable
public static volatile Thread.UncaughtExceptionHandler uncaughtExceptionHandler;
- /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
public static class UserThread extends Thread implements Executor {
private static final Logger log = LoggerFactory.getLogger(UserThread.class);
private LinkedBlockingQueue tasks;
@@ -132,6 +136,12 @@ public class Threading {
};
}
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // Cycle detecting lock factories
+ //
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
private static CycleDetectingLockFactory.Policy policy;
public static CycleDetectingLockFactory factory;
@@ -159,4 +169,23 @@ public class Threading {
public static CycleDetectingLockFactory.Policy getPolicy() {
return policy;
}
+
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ //
+ // Generic worker pool.
+ //
+ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ /** A caching thread pool that creates daemon threads, which won't keep the JVM alive waiting for more work. */
+ public static ListeningExecutorService THREAD_POOL = MoreExecutors.listeningDecorator(
+ Executors.newCachedThreadPool(new ThreadFactory() {
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread t = new Thread(r);
+ t.setName("Threading.THREAD_POOL worker");
+ t.setDaemon(true);
+ return t;
+ }
+ })
+ );
}
diff --git a/tools/src/main/java/com/google/bitcoin/tools/WalletTool.java b/tools/src/main/java/com/google/bitcoin/tools/WalletTool.java
index c8f2a2d3..d107bc95 100644
--- a/tools/src/main/java/com/google/bitcoin/tools/WalletTool.java
+++ b/tools/src/main/java/com/google/bitcoin/tools/WalletTool.java
@@ -32,8 +32,6 @@ import com.google.bitcoin.utils.BriefLogFormatter;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList;
import com.google.common.io.Resources;
-import com.google.common.util.concurrent.FutureCallback;
-import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
@@ -444,8 +442,6 @@ public class WalletTool {
return;
} catch (ScriptException e) {
throw new RuntimeException(e);
- } catch (KeyCrypterException e) {
- throw new RuntimeException(e);
}
t = req.tx; // Not strictly required today.
System.out.println(t.getHashAsString());
@@ -460,11 +456,6 @@ public class WalletTool {
// network. Once propagation is complete and we heard the transaction back from all our peers, it will
// be committed to the wallet.
peers.broadcastTransaction(t).get();
- if (peers.getMinBroadcastConnections() == 1) {
- // Crap hack to work around some issue with Netty where the write future
- // completes before the remote peer actually hears the message.
- Thread.sleep(5000);
- }
} catch (BlockStoreException e) {
throw new RuntimeException(e);
} catch (KeyCrypterException e) {
@@ -479,56 +470,32 @@ public class WalletTool {
}
private static void sendPaymentRequest(String location, boolean verifyPki) {
- if (location.startsWith("http")) {
+ if (location.startsWith("http") || location.startsWith("bitcoin")) {
try {
- ListenableFuture future = PaymentSession.createFromUrl(location, verifyPki);
- Futures.addCallback(future, new FutureCallback() {
- @Override
- public void onSuccess(PaymentSession session) {
- if (session != null)
- send(session);
- else {
- System.err.println("Server returned null session");
- System.exit(1);
- }
- }
- public void onFailure(Throwable thrown) {
- System.err.println("Failed to fetch payment request " + thrown.getMessage());
- System.exit(1);
- }
- });
+ ListenableFuture future;
+ if (location.startsWith("http")) {
+ future = PaymentSession.createFromUrl(location, verifyPki);
+ } else {
+ BitcoinURI paymentRequestURI = new BitcoinURI(location);
+ future = PaymentSession.createFromBitcoinUri(paymentRequestURI, verifyPki);
+ }
+ PaymentSession session = future.get();
+ if (session != null) {
+ send(session);
+ } else {
+ System.err.println("Server returned null session");
+ System.exit(1);
+ }
} catch (PaymentRequestException e) {
System.err.println("Error creating payment session " + e.getMessage());
System.exit(1);
- }
- } else if (location.startsWith("bitcoin")) {
- BitcoinURI paymentRequestURI = null;
- try {
- paymentRequestURI = new BitcoinURI(location);
} catch (BitcoinURIParseException e) {
System.err.println("Invalid bitcoin uri: " + e.getMessage());
System.exit(1);
- }
- try {
- ListenableFuture future = PaymentSession.createFromBitcoinUri(paymentRequestURI, verifyPki);
- Futures.addCallback(future, new FutureCallback() {
- @Override
- public void onSuccess(PaymentSession session) {
- if (session != null)
- send(session);
- else {
- System.err.println("Server returned null session");
- System.exit(1);
- }
- }
- public void onFailure(Throwable thrown) {
- System.err.println("Failed to fetch payment request " + thrown.getMessage());
- System.exit(1);
- }
- });
- } catch (PaymentRequestException e) {
- System.err.println("Error creating payment session " + e.getMessage());
- System.exit(1);
+ } catch (InterruptedException e) {
+ // Ignore.
+ } catch (ExecutionException e) {
+ throw new RuntimeException(e);
}
} else {
// Try to open the payment request as a file.
@@ -537,7 +504,7 @@ public class WalletTool {
File paymentRequestFile = new File(location);
stream = new FileInputStream(paymentRequestFile);
} catch (Exception e) {
- System.err.println("Failed to open file " + e.getMessage());
+ System.err.println("Failed to open file: " + e.getMessage());
System.exit(1);
}
try {
@@ -557,6 +524,63 @@ public class WalletTool {
}
}
+ private static void send(PaymentSession session) {
+ try {
+ System.out.println("Payment Request");
+ System.out.println("Amount: " + session.getValue().doubleValue() / 100000 + "mBTC");
+ System.out.println("Date: " + session.getDate());
+ System.out.println("Memo: " + session.getMemo());
+ if (session.pkiVerificationData != null) {
+ System.out.println("Pki-Verified Name: " + session.pkiVerificationData.name);
+ if (session.pkiVerificationData.orgName != null)
+ System.out.println("Pki-Verified Org: " + session.pkiVerificationData.orgName);
+ }
+ final Wallet.SendRequest req = session.getSendRequest();
+ if (password != null) {
+ if (!wallet.checkPassword(password)) {
+ System.err.println("Password is incorrect.");
+ return;
+ }
+ req.aesKey = wallet.getKeyCrypter().deriveKey(password);
+ }
+ wallet.completeTx(req); // may throw InsufficientMoneyException.
+ if (options.has("offline")) {
+ wallet.commitTx(req.tx);
+ return;
+ }
+ setup();
+ // No refund address specified, no user-specified memo field.
+ ListenableFuture future = session.sendPayment(ImmutableList.of(req.tx), null, null);
+ if (future == null) {
+ // No payment_url for submission so, broadcast and wait.
+ peers.startAndWait();
+ peers.broadcastTransaction(req.tx).get();
+ } else {
+ PaymentSession.Ack ack = future.get();
+ wallet.commitTx(req.tx);
+ System.out.println("Memo from server: " + ack.getMemo());
+ }
+ } catch (PaymentRequestException e) {
+ System.err.println("Failed to send payment " + e.getMessage());
+ System.exit(1);
+ } catch (VerificationException e) {
+ System.err.println("Failed to send payment " + e.getMessage());
+ System.exit(1);
+ } catch (ExecutionException e) {
+ System.err.println("Failed to send payment " + e.getMessage());
+ System.exit(1);
+ } catch (IOException e) {
+ System.err.println("Invalid payment " + e.getMessage());
+ System.exit(1);
+ } catch (InterruptedException e1) {
+ // Ignore.
+ } catch (InsufficientMoneyException e) {
+ System.err.println("Insufficient funds: have " + Utils.bitcoinValueToFriendlyString(wallet.getBalance()));
+ } catch (BlockStoreException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
private static void wait(WaitForEnum waitFor) throws BlockStoreException {
final CountDownLatch latch = new CountDownLatch(1);
setup();
@@ -817,15 +841,15 @@ public class WalletTool {
} catch (AddressFormatException e) {
System.err.println(addr + " does not parse as a Bitcoin address of the right network parameters.");
return;
- }
+ }
}
if (key == null) {
System.err.println("Wallet does not seem to contain that key.");
return;
}
wallet.removeKey(key);
- }
-
+ }
+
private static void dumpWallet() throws BlockStoreException {
// Setup to get the chain height so we can estimate lock times, but don't wipe the transactions if it's not
// there just for the dump case.
@@ -833,49 +857,4 @@ public class WalletTool {
setup();
System.out.println(wallet.toString(true, true, true, chain));
}
-
- private static void send(PaymentSession session) {
- try {
- System.out.println("Payment Request");
- System.out.println("Amount: " + session.getValue().doubleValue() / 100000 + "mBTC");
- System.out.println("Date: " + session.getDate());
- System.out.println("Memo: " + session.getMemo());
- if (session.pkiVerificationData != null) {
- System.out.println("Pki-Verified Name: " + session.pkiVerificationData.name);
- if (session.pkiVerificationData.orgName != null)
- System.out.println("Pki-Verified Org: " + session.pkiVerificationData.orgName);
- }
- final Wallet.SendRequest req = session.getSendRequest();
- wallet.completeTx(req); // may throw InsufficientMoneyException.
- // No refund address specified, no user-specified memo field.
- ListenableFuture future = session.sendPayment(ImmutableList.of(req.tx), null, null);
- Futures.addCallback(future, new FutureCallback() {
- @Override
- public void onSuccess(PaymentSession.Ack ack) {
- try {
- wallet.commitTx(req.tx);
- System.out.println(ack.getMemo());
- } catch (VerificationException e) {
- System.err.println("Failed to send tx " + e.getMessage());
- System.exit(1);
- }
- }
- public void onFailure(Throwable thrown) {
- System.err.println("Failed to send payment " + thrown.getMessage());
- System.exit(1);
- }
- });
- }catch (PaymentRequestException e) {
- System.err.println("Failed to send payment " + e.getMessage());
- System.exit(1);
- } catch (VerificationException e) {
- System.err.println("Failed to send payment " + e.getMessage());
- System.exit(1);
- } catch (IOException e) {
- System.err.println("Invalid payment " + e.getMessage());
- System.exit(1);
- } catch (InsufficientMoneyException e) {
- System.err.println("Insufficient funds: have " + Utils.bitcoinValueToFriendlyString(wallet.getBalance()));
- }
- }
}