/* * 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.*; import org.bitcoinj.wallet.Wallet; import javax.annotation.Nullable; import java.util.Date; import java.util.Locale; import static com.google.common.base.Preconditions.checkArgument; /** * Represents the state of a channel once it has been opened in such a way that it can be stored and used to resume a * channel which was interrupted (eg on connection failure) or settle the channel automatically as the channel expire * time approaches. */ public class StoredServerChannel { /** * Channel version number. Currently can only be version 1 */ int majorVersion; Coin bestValueToMe; byte[] bestValueSignature; long refundTransactionUnlockTimeSecs; Transaction contract; TransactionOutput clientOutput; ECKey myKey; // Used in protocol v2 only ECKey clientKey; // In-memory pointer to the event handler which handles this channel if the client is connected. // Used as a flag to prevent duplicate connections and to disconnect the channel if its expire time approaches. private PaymentChannelServer connectedHandler = null; PaymentChannelServerState state = null; StoredServerChannel(@Nullable PaymentChannelServerState state, int majorVersion, Transaction contract, TransactionOutput clientOutput, long refundTransactionUnlockTimeSecs, ECKey myKey, ECKey clientKey, Coin bestValueToMe, @Nullable byte[] bestValueSignature) { this.majorVersion = majorVersion; this.contract = contract; this.clientOutput = clientOutput; this.refundTransactionUnlockTimeSecs = refundTransactionUnlockTimeSecs; this.myKey = myKey; this.clientKey = clientKey; this.bestValueToMe = bestValueToMe; this.bestValueSignature = bestValueSignature; this.state = state; } /** * <p>Updates the best value to the server to the given newValue and newSignature without any checking.</p> * <p>Does <i>NOT</i> notify the wallet of an update to the {@link StoredPaymentChannelServerStates}.</p> */ synchronized void updateValueToMe(Coin newValue, byte[] newSignature) { this.bestValueToMe = newValue; this.bestValueSignature = newSignature; } /** * Attempts to connect the given handler to this, returning true if it is the new handler, false if there was * already one attached. */ synchronized PaymentChannelServer setConnectedHandler(PaymentChannelServer connectedHandler, boolean override) { if (this.connectedHandler != null && !override) return this.connectedHandler; this.connectedHandler = connectedHandler; return connectedHandler; } /** Clears a handler that was connected with setConnectedHandler. */ synchronized void clearConnectedHandler() { this.connectedHandler = null; } /** * If a handler is connected, call its {@link org.bitcoinj.protocols.channels.PaymentChannelServer#close()} * method thus disconnecting the TCP connection. */ synchronized void closeConnectedHandler() { if (connectedHandler != null) connectedHandler.close(); } /** * Gets the canonical {@link PaymentChannelServerState} object for this channel, either by returning an existing one * or by creating a new one. * * @param wallet The wallet which holds the {@link PaymentChannelServerState} in which this is saved and which will * be used to complete transactions * @param broadcaster The {@link TransactionBroadcaster} which will be used to broadcast contract/payment transactions. */ public synchronized PaymentChannelServerState getOrCreateState(Wallet wallet, TransactionBroadcaster broadcaster) throws VerificationException { if (state == null) { switch (majorVersion) { case 1: state = new PaymentChannelV1ServerState(this, wallet, broadcaster); break; case 2: state = new PaymentChannelV2ServerState(this, wallet, broadcaster); break; default: throw new IllegalStateException("Invalid version number found"); } } checkArgument(wallet == state.wallet); return state; } @Override public synchronized String toString() { final String newline = String.format(Locale.US, "%n"); return String.format(Locale.US, "Stored server channel (%s)%n" + " Version: %d%n" + " Key: %s%n" + " Value to me: %s%n" + " Client output: %s%n" + " Refund unlock: %s (%d unix time)%n" + " Contract: %s%n", connectedHandler != null ? "connected" : "disconnected", majorVersion, myKey, bestValueToMe, clientOutput, new Date(refundTransactionUnlockTimeSecs * 1000), refundTransactionUnlockTimeSecs, contract.toString().replaceAll(newline, newline + " ")); } }