All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.bitcoinj.protocols.channels.PaymentChannelClientConnection Maven / Gradle / Ivy

There is a newer version: 0.15-cm06
Show newest version
/*
 * Copyright 2013 Google Inc.
 *
 * 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 org.bitcoinj.protocols.channels;

import org.bitcoinj.core.Coin;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Utils;
import org.bitcoinj.net.NioClient;
import org.bitcoinj.net.ProtobufConnection;
import org.bitcoinj.protocols.channels.IPaymentChannelClient.ClientChannelProperties;
import org.bitcoinj.wallet.Wallet;

import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;

import com.google.protobuf.ByteString;
import org.bitcoin.paymentchannel.Protos;
import org.spongycastle.crypto.params.KeyParameter;

import javax.annotation.Nullable;
import java.io.IOException;
import java.net.InetSocketAddress;

/**
 * A simple utility class that runs the micropayment protocol over a raw TCP socket using NIO, standalone.
 */
public class PaymentChannelClientConnection {
    // Various futures which will be completed later
    private final SettableFuture channelOpenFuture = SettableFuture.create();

    private final PaymentChannelClient channelClient;
    private final ProtobufConnection wireParser;

    /**
     * Attempts to open a new connection to and open a payment channel with the given host and port, blocking until the
     * connection is open. The server is requested to keep the channel open for
     * {@link org.bitcoinj.protocols.channels.PaymentChannelClient#DEFAULT_TIME_WINDOW}
     * seconds. If the server proposes a longer time the channel will be closed.
     *
     * @param server         The host/port pair where the server is listening.
     * @param timeoutSeconds The connection timeout and read timeout during initialization. This should be large enough
     *                       to accommodate ECDSA signature operations and network latency.
     * @param wallet         The wallet which will be paid from, and where completed transactions will be committed.
     *                       Must be unencrypted. Must already have a {@link StoredPaymentChannelClientStates} object in its extensions set.
     * @param myKey          A freshly generated keypair used for the multisig contract and refund output.
     * @param maxValue       The maximum value this channel is allowed to request
     * @param serverId       A unique ID which is used to attempt reopening of an existing channel.
     *                       This must be unique to the server, and, if your application is exposing payment channels to some
     *                       API, this should also probably encompass some caller UID to avoid applications opening channels
     *                       which were created by others.
     * @throws IOException              if there's an issue using the network.
     * @throws ValueOutOfRangeException if the balance of wallet is lower than maxValue.
     */
    public PaymentChannelClientConnection(InetSocketAddress server, int timeoutSeconds, Wallet wallet, ECKey myKey,
                                          Coin maxValue, String serverId) throws IOException, ValueOutOfRangeException {
        this(server, timeoutSeconds, wallet, myKey, maxValue, serverId, null);
    }

    /**
     * Attempts to open a new connection to and open a payment channel with the given host and port, blocking until the
     * connection is open.  The server is requested to keep the channel open for
     * {@link org.bitcoinj.protocols.channels.PaymentChannelClient#DEFAULT_TIME_WINDOW} seconds.
     * If the server proposes a longer time the channel will be closed.
     *
     * @param server          The host/port pair where the server is listening.
     * @param timeoutSeconds  The connection timeout and read timeout during initialization. This should be large enough
     *                        to accommodate ECDSA signature operations and network latency.
     * @param wallet          The wallet which will be paid from, and where completed transactions will be committed.
     *                        Can be encrypted if user key is supplied when needed. Must already have a
     *                        {@link StoredPaymentChannelClientStates} object in its extensions set.
     * @param myKey           A freshly generated keypair used for the multisig contract and refund output.
     * @param maxValue        The maximum value this channel is allowed to request
     * @param serverId        A unique ID which is used to attempt reopening of an existing channel.
     *                        This must be unique to the server, and, if your application is exposing payment channels to some
     *                        API, this should also probably encompass some caller UID to avoid applications opening channels
     *                        which were created by others.
     * @param userKeySetup    Key derived from a user password, used to decrypt myKey, if it is encrypted, during setup.
     * @throws IOException              if there's an issue using the network.
     * @throws ValueOutOfRangeException if the balance of wallet is lower than maxValue.
     */
    public PaymentChannelClientConnection(InetSocketAddress server, int timeoutSeconds, Wallet wallet, ECKey myKey,
                                          Coin maxValue, String serverId, @Nullable KeyParameter userKeySetup) throws IOException, ValueOutOfRangeException {
        this(server, timeoutSeconds, wallet, myKey, maxValue, serverId, userKeySetup, PaymentChannelClient.defaultChannelProperties);
    }


    /**
     * Attempts to open a new connection to and open a payment channel with the given host and port, blocking until the
     * connection is open.  The server is requested to keep the channel open for {@code timeoutSeconds}
     * seconds. If the server proposes a longer time the channel will be closed.
     *
     * @param server The host/port pair where the server is listening.
     * @param timeoutSeconds The connection timeout and read timeout during initialization. This should be large enough
     *                       to accommodate ECDSA signature operations and network latency.
     * @param wallet The wallet which will be paid from, and where completed transactions will be committed.
     *               Can be encrypted if user key is supplied when needed. Must already have a
     *               {@link StoredPaymentChannelClientStates} object in its extensions set.
     * @param myKey A freshly generated keypair used for the multisig contract and refund output.
     * @param maxValue The maximum value this channel is allowed to request
     * @param serverId A unique ID which is used to attempt reopening of an existing channel.
     *                 This must be unique to the server, and, if your application is exposing payment channels to some
     *                 API, this should also probably encompass some caller UID to avoid applications opening channels
     *                 which were created by others.
     * @param userKeySetup Key derived from a user password, used to decrypt myKey, if it is encrypted, during setup.
     * @param clientChannelProperties Modifier to change the channel's configuration.
     *
     * @throws IOException if there's an issue using the network.
     * @throws ValueOutOfRangeException if the balance of wallet is lower than maxValue.
     */
    public PaymentChannelClientConnection(InetSocketAddress server, int timeoutSeconds, Wallet wallet, ECKey myKey,
                                          Coin maxValue, String serverId,
                                          @Nullable KeyParameter userKeySetup, final ClientChannelProperties clientChannelProperties)
            throws IOException, ValueOutOfRangeException {
        // Glue the object which vends/ingests protobuf messages in order to manage state to the network object which
        // reads/writes them to the wire in length prefixed form.
        channelClient = new PaymentChannelClient(wallet, myKey, maxValue, Sha256Hash.of(serverId.getBytes()),
                userKeySetup, clientChannelProperties, new PaymentChannelClient.ClientConnection() {
            @Override
            public void sendToServer(Protos.TwoWayChannelMessage msg) {
                wireParser.write(msg);
            }

            @Override
            public void destroyConnection(PaymentChannelCloseException.CloseReason reason) {
                channelOpenFuture.setException(new PaymentChannelCloseException("Payment channel client requested that the connection be closed: " + reason, reason));
                wireParser.closeConnection();
            }

            @Override
            public boolean acceptExpireTime(long expireTime) {
                return expireTime <= (clientChannelProperties.timeWindow() + Utils.currentTimeSeconds() + 60);  // One extra minute to compensate for time skew and latency
            }

            @Override
            public void channelOpen(boolean wasInitiated) {
                wireParser.setSocketTimeout(0);
                // Inform the API user that we're done and ready to roll.
                channelOpenFuture.set(PaymentChannelClientConnection.this);
            }
        });

        // And glue back in the opposite direction - network to the channelClient.
        wireParser = new ProtobufConnection(new ProtobufConnection.Listener() {
            @Override
            public void messageReceived(ProtobufConnection handler, Protos.TwoWayChannelMessage msg) {
                try {
                    channelClient.receiveMessage(msg);
                } catch (InsufficientMoneyException e) {
                    // We should only get this exception during INITIATE, so channelOpen wasn't called yet.
                    channelOpenFuture.setException(e);
                }
            }

            @Override
            public void connectionOpen(ProtobufConnection handler) {
                channelClient.connectionOpen();
            }

            @Override
            public void connectionClosed(ProtobufConnection handler) {
                channelClient.connectionClosed();
                channelOpenFuture.setException(new PaymentChannelCloseException("The TCP socket died",
                        PaymentChannelCloseException.CloseReason.CONNECTION_CLOSED));
            }
        }, Protos.TwoWayChannelMessage.getDefaultInstance(), Short.MAX_VALUE, timeoutSeconds*1000);

        // Initiate the outbound network connection. We don't need to keep this around. The wireParser object will handle
        // things from here on out.
        new NioClient(server, wireParser, timeoutSeconds * 1000);
    }

    /**
     * 

Gets a future which returns this when the channel is successfully opened, or throws an exception if there is * an error before the channel has reached the open state.

* *

After this future completes successfully, you may call * {@link PaymentChannelClientConnection#incrementPayment(Coin)} or * {@link PaymentChannelClientConnection#incrementPayment(Coin, com.google.protobuf.ByteString, KeyParameter)} to * begin paying the server.

*/ public ListenableFuture getChannelOpenFuture() { return channelOpenFuture; } /** * Increments the total value which we pay the server. * * @param size How many satoshis to increment the payment by (note: not the new total). * @throws ValueOutOfRangeException If the size is negative or would pay more than this channel's total value * ({@link PaymentChannelClientConnection#state()}.getTotalValue()) * @throws IllegalStateException If the channel has been closed or is not yet open * (see {@link PaymentChannelClientConnection#getChannelOpenFuture()} for the second) */ public ListenableFuture incrementPayment(Coin size) throws ValueOutOfRangeException, IllegalStateException { return channelClient.incrementPayment(size, null, null); } /** * Increments the total value which we pay the server. * * @param size How many satoshis to increment the payment by (note: not the new total). * @param info Information about this payment increment, used to extend this protocol. * @throws ValueOutOfRangeException If the size is negative or would pay more than this channel's total value * ({@link PaymentChannelClientConnection#state()}.getTotalValue()) * @throws IllegalStateException If the channel has been closed or is not yet open * (see {@link PaymentChannelClientConnection#getChannelOpenFuture()} for the second) */ /* public ListenableFuture incrementPayment(Coin size, ByteString info) throws ValueOutOfRangeException, IllegalStateException { return channelClient.incrementPayment(size, info, null); } */ /** * Increments the total value which we pay the server. * * @param size How many satoshis to increment the payment by (note: not the new total). * @param info Information about this payment increment, used to extend this protocol. * @param userKey Key derived from a user password, needed for any signing when the wallet is encrypted. * The wallet KeyCrypter is assumed. * @throws ValueOutOfRangeException If the size is negative or would pay more than this channel's total value * ({@link PaymentChannelClientConnection#state()}.getTotalValue()) * @throws IllegalStateException If the channel has been closed or is not yet open * (see {@link PaymentChannelClientConnection#getChannelOpenFuture()} for the second) */ public ListenableFuture incrementPayment(Coin size, @Nullable ByteString info, @Nullable KeyParameter userKey) throws ValueOutOfRangeException, IllegalStateException { return channelClient.incrementPayment(size, info, userKey); } /** *

Gets the {@link PaymentChannelV1ClientState} object which stores the current state of the connection with the * server.

* *

Note that if you call any methods which update state directly the server will not be notified and channel * initialization logic in the connection may fail unexpectedly.

*/ public PaymentChannelClientState state() { return channelClient.state(); } /** * Closes the connection, notifying the server it should settle the channel by broadcasting the most recent payment * transaction. */ public void settle() { // Shutdown is a little complicated. // // This call will cause the CLOSE message to be written to the wire, and then the destroyConnection() method that // we defined above will be called, which in turn will call wireParser.closeConnection(), which in turn will invoke // NioClient.closeConnection(), which will then close the socket triggering interruption of the network // thread it had created. That causes the background thread to die, which on its way out calls // ProtobufConnection.connectionClosed which invokes the connectionClosed method we defined above which in turn // then configures the open-future correctly and closes the state object. Phew! try { channelClient.settle(); } catch (IllegalStateException e) { // Already closed...oh well } } /** * Disconnects the network connection but doesn't request the server to settle the channel first (literally just * unplugs the network socket and marks the stored channel state as inactive). */ public void disconnectWithoutSettlement() { wireParser.closeConnection(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy