/* * 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. * 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.Sha256Hash; import org.bitcoinj.core.TransactionBroadcaster; import org.bitcoinj.net.NioServer; import org.bitcoinj.net.ProtobufConnection; import org.bitcoinj.net.StreamConnectionFactory; import org.bitcoinj.wallet.Wallet; import com.google.common.util.concurrent.ListenableFuture; 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.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import static com.google.common.base.Preconditions.checkNotNull; /** * Implements a listening TCP server that can accept connections from payment channel clients, and invokes the provided * event listeners when new channels are opened or payments arrive. This is the highest level class in the payment * channels API. Internally, sends protobuf messages to/from a newly created {@link PaymentChannelServer}. */ public class PaymentChannelServerListener { // The wallet and peergroup which are used to complete/broadcast transactions private final Wallet wallet; private final TransactionBroadcaster broadcaster; // The event handler factory which creates new ServerConnectionEventHandler per connection private final HandlerFactory eventHandlerFactory; private final Coin minAcceptedChannelSize; private NioServer server; private final int timeoutSeconds; /** * A factory which generates connection-specific event handlers. */ public interface HandlerFactory { /** * Called when a new connection completes version handshake to get a new connection-specific listener. * If null is returned, the connection is immediately closed. */ @Nullable ServerConnectionEventHandler onNewConnection(SocketAddress clientAddress); } private class ServerHandler { public ServerHandler(final SocketAddress address, final int timeoutSeconds) { paymentChannelManager = new PaymentChannelServer(broadcaster, wallet, minAcceptedChannelSize, new PaymentChannelServer.ServerConnection() { @Override public void sendToClient(Protos.TwoWayChannelMessage msg) { socketProtobufHandler.write(msg); } @Override public void destroyConnection(PaymentChannelCloseException.CloseReason reason) { if (closeReason != null) closeReason = reason; socketProtobufHandler.closeConnection(); } @Override public void channelOpen(Sha256Hash contractHash) { socketProtobufHandler.setSocketTimeout(0); eventHandler.channelOpen(contractHash); } @Override public ListenableFuture<ByteString> paymentIncrease(Coin by, Coin to, @Nullable ByteString info) { return eventHandler.paymentIncrease(by, to, info); } @Nullable @Override public ListenableFuture<KeyParameter> getUserKey() { return null; } }); protobufHandlerListener = new ProtobufConnection.Listener<Protos.TwoWayChannelMessage>() { @Override public synchronized void messageReceived(ProtobufConnection<Protos.TwoWayChannelMessage> handler, Protos.TwoWayChannelMessage msg) { paymentChannelManager.receiveMessage(msg); } @Override public synchronized void connectionClosed(ProtobufConnection<Protos.TwoWayChannelMessage> handler) { paymentChannelManager.connectionClosed(); if (closeReason != null) eventHandler.channelClosed(closeReason); else eventHandler.channelClosed(PaymentChannelCloseException.CloseReason.CONNECTION_CLOSED); eventHandler.setConnectionChannel(null); } @Override public synchronized void connectionOpen(ProtobufConnection<Protos.TwoWayChannelMessage> handler) { ServerConnectionEventHandler eventHandler = eventHandlerFactory.onNewConnection(address); if (eventHandler == null) handler.closeConnection(); else { ServerHandler.this.eventHandler = eventHandler; ServerHandler.this.eventHandler.setConnectionChannel(socketProtobufHandler); paymentChannelManager.connectionOpen(); } } }; socketProtobufHandler = new ProtobufConnection<Protos.TwoWayChannelMessage> (protobufHandlerListener, Protos.TwoWayChannelMessage.getDefaultInstance(), Short.MAX_VALUE, timeoutSeconds*1000); } private PaymentChannelCloseException.CloseReason closeReason; // The user-provided event handler private ServerConnectionEventHandler eventHandler; // The payment channel server which does the actual payment channel handling private final PaymentChannelServer paymentChannelManager; // The connection handler which puts/gets protobufs from the TCP socket private final ProtobufConnection<Protos.TwoWayChannelMessage> socketProtobufHandler; // The listener which connects to socketProtobufHandler private final ProtobufConnection.Listener<Protos.TwoWayChannelMessage> protobufHandlerListener; } /** * Binds to the given port and starts accepting new client connections. * @throws Exception If binding to the given port fails (eg SocketException: Permission denied for privileged ports) */ public void bindAndStart(int port) throws Exception { server = new NioServer(new StreamConnectionFactory() { @Override public ProtobufConnection<Protos.TwoWayChannelMessage> getNewConnection(InetAddress inetAddress, int port) { return new ServerHandler(new InetSocketAddress(inetAddress, port), timeoutSeconds).socketProtobufHandler; } }, new InetSocketAddress(port)); server.startAsync(); server.awaitRunning(); } /** * Sets up a new payment channel server which listens on the given port. * * @param broadcaster The PeerGroup on which transactions will be broadcast - should have multiple connections. * @param wallet The wallet which will be used to complete transactions * @param timeoutSeconds The read timeout between messages. This should accommodate latency and client ECDSA * signature operations. * @param minAcceptedChannelSize The minimum amount of coins clients must lock in to create a channel. Clients which * are unwilling or unable to lock in at least this value will immediately disconnect. * For this reason, a fairly conservative value (in terms of average value spent on a * channel) should generally be chosen. * @param eventHandlerFactory A factory which generates event handlers which are created for each new connection */ public PaymentChannelServerListener(TransactionBroadcaster broadcaster, Wallet wallet, final int timeoutSeconds, Coin minAcceptedChannelSize, HandlerFactory eventHandlerFactory) throws IOException { this.wallet = checkNotNull(wallet); this.broadcaster = checkNotNull(broadcaster); this.eventHandlerFactory = checkNotNull(eventHandlerFactory); this.minAcceptedChannelSize = checkNotNull(minAcceptedChannelSize); this.timeoutSeconds = timeoutSeconds; } /** * <p>Closes all client connections currently connected gracefully.</p> * * <p>Note that this does <i>not</i> settle the actual payment channels (and broadcast payment transactions), which * must be done using the {@link StoredPaymentChannelServerStates} which manages the states for the associated * wallet.</p> */ public void close() { server.stopAsync(); server.awaitTerminated(); } }