From 8ff52f52178a8b1e89aa12349a9a1e0be51fb273 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Wed, 28 May 2014 13:26:19 +0200 Subject: [PATCH] Payment channels: update example client to reflect API changes. --- .../examples/ExamplePaymentChannelClient.java | 45 ++++++++++++------- .../examples/ExamplePaymentChannelServer.java | 9 ++-- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/examples/src/main/java/com/google/bitcoin/examples/ExamplePaymentChannelClient.java b/examples/src/main/java/com/google/bitcoin/examples/ExamplePaymentChannelClient.java index a3cd2d7f..33123dbc 100644 --- a/examples/src/main/java/com/google/bitcoin/examples/ExamplePaymentChannelClient.java +++ b/examples/src/main/java/com/google/bitcoin/examples/ExamplePaymentChannelClient.java @@ -17,16 +17,21 @@ package com.google.bitcoin.examples; -import com.google.bitcoin.core.*; +import com.google.bitcoin.core.ECKey; +import com.google.bitcoin.core.NetworkParameters; +import com.google.bitcoin.core.Utils; +import com.google.bitcoin.core.Wallet; import com.google.bitcoin.kits.WalletAppKit; -import com.google.bitcoin.params.TestNet3Params; +import com.google.bitcoin.params.RegTestParams; import com.google.bitcoin.protocols.channels.PaymentChannelClientConnection; import com.google.bitcoin.protocols.channels.StoredPaymentChannelClientStates; import com.google.bitcoin.protocols.channels.ValueOutOfRangeException; import com.google.bitcoin.utils.BriefLogFormatter; +import com.google.bitcoin.utils.Threading; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.Uninterruptibles; import org.slf4j.LoggerFactory; import java.io.File; @@ -34,10 +39,10 @@ import java.io.IOException; import java.math.BigInteger; import java.net.InetSocketAddress; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; import static com.google.bitcoin.core.Utils.CENT; import static java.math.BigInteger.TEN; -import static java.math.BigInteger.ZERO; /** * Simple client that connects to the given host, opens a channel, and pays one cent. @@ -58,7 +63,7 @@ public class ExamplePaymentChannelClient { public ExamplePaymentChannelClient() { channelSize = CENT; myKey = new ECKey(); - params = TestNet3Params.get(); + params = RegTestParams.get(); } public void run(final String host) throws Exception { @@ -74,6 +79,7 @@ public class ExamplePaymentChannelClient { wallet().addExtension(new StoredPaymentChannelClientStates(wallet(), peerGroup())); } }; + appKit.connectToLocalHost(); appKit.startAsync(); appKit.awaitRunning(); // We now have active network connections and a fully synced wallet. @@ -98,18 +104,16 @@ public class ExamplePaymentChannelClient { // demonstrates resuming a channel that wasn't closed yet. It should close automatically once we run out // of money on the channel. log.info("Round one ..."); - openAndSend(timeoutSecs, server, channelID); + openAndSend(timeoutSecs, server, channelID, 5); log.info("Round two ..."); log.info(appKit.wallet().toString()); - openAndSend(timeoutSecs, server, channelID); - log.info("Waiting ..."); - Thread.sleep(60 * 60 * 1000); // 1 hour. + openAndSend(timeoutSecs, server, channelID, 4); // 4 times because the opening of the channel made a payment. log.info("Stopping ..."); appKit.stopAsync(); appKit.awaitTerminated(); } - private void openAndSend(int timeoutSecs, InetSocketAddress server, String channelID) throws IOException, ValueOutOfRangeException, InterruptedException { + private void openAndSend(int timeoutSecs, InetSocketAddress server, String channelID, final int times) throws IOException, ValueOutOfRangeException, InterruptedException { PaymentChannelClientConnection client = new PaymentChannelClientConnection( server, timeoutSecs, appKit.wallet(), myKey, channelSize, channelID); // Opening the channel requires talking to the server, so it's asynchronous. @@ -117,17 +121,28 @@ public class ExamplePaymentChannelClient { Futures.addCallback(client.getChannelOpenFuture(), new FutureCallback() { @Override public void onSuccess(PaymentChannelClientConnection client) { - // Success! We should be able to try making micropayments now. Try doing it 5 times. - for (int i = 0; i < 5; i++) { + // By the time we get here, if the channel is new then we already made a micropayment! The reason is, + // we are not allowed to have payment channels that pay nothing at all. + log.info("Success! Trying to make {} micropayments. Already paid {} satoshis on this channel", + times, client.state().getValueSpent()); + final BigInteger MICROPAYMENT_SIZE = CENT.divide(TEN); + for (int i = 0; i < times; i++) { try { - client.incrementPayment(CENT.divide(TEN)); + // Wait because the act of making a micropayment is async, and we're not allowed to overlap. + // This callback is running on the user thread (see the last lines in openAndSend) so it's safe + // for us to block here: if we didn't select the right thread, we'd end up blocking the payment + // channels thread and would deadlock. + Uninterruptibles.getUninterruptibly(client.incrementPayment(MICROPAYMENT_SIZE)); } catch (ValueOutOfRangeException e) { log.error("Failed to increment payment by a CENT, remaining value is {}", client.state().getValueRefunded()); - System.exit(-3); + throw new RuntimeException(e); + } catch (ExecutionException e) { + log.error("Failed to increment payment", e); + throw new RuntimeException(e); } log.info("Successfully sent payment of one CENT, total remaining on channel is now {}", client.state().getValueRefunded()); } - if (client.state().getValueRefunded().equals(ZERO)) { + if (client.state().getValueRefunded().compareTo(MICROPAYMENT_SIZE) < 0) { // Now tell the server we're done so they should broadcast the final transaction and refund us what's // left. If we never do this then eventually the server will time out and do it anyway and if the // server goes away for longer, then eventually WE will time out and the refund tx will get broadcast @@ -146,7 +161,7 @@ public class ExamplePaymentChannelClient { log.error("Failed to open connection", throwable); latch.countDown(); } - }); + }, Threading.USER_THREAD); latch.await(); } diff --git a/examples/src/main/java/com/google/bitcoin/examples/ExamplePaymentChannelServer.java b/examples/src/main/java/com/google/bitcoin/examples/ExamplePaymentChannelServer.java index 507b6705..e45c2da7 100644 --- a/examples/src/main/java/com/google/bitcoin/examples/ExamplePaymentChannelServer.java +++ b/examples/src/main/java/com/google/bitcoin/examples/ExamplePaymentChannelServer.java @@ -17,9 +17,11 @@ package com.google.bitcoin.examples; -import com.google.bitcoin.core.*; +import com.google.bitcoin.core.NetworkParameters; +import com.google.bitcoin.core.Sha256Hash; +import com.google.bitcoin.core.VerificationException; import com.google.bitcoin.kits.WalletAppKit; -import com.google.bitcoin.params.TestNet3Params; +import com.google.bitcoin.params.RegTestParams; import com.google.bitcoin.protocols.channels.*; import com.google.bitcoin.utils.BriefLogFormatter; import org.slf4j.LoggerFactory; @@ -43,7 +45,7 @@ public class ExamplePaymentChannelServer implements PaymentChannelServerListener } public void run() throws Exception { - NetworkParameters params = TestNet3Params.get(); + NetworkParameters params = RegTestParams.get(); // Bring up all the objects we need, create/load a wallet, sync the chain, etc. We override WalletAppKit so we // can customize it by adding the extension objects - we have to do this before the wallet file is loaded so @@ -58,6 +60,7 @@ public class ExamplePaymentChannelServer implements PaymentChannelServerListener wallet().addExtension(storedStates); } }; + appKit.connectToLocalHost(); appKit.startAsync(); appKit.awaitRunning();