Gets the {@link PaymentChannelClientState} 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 synchronized PaymentChannelClientState state() {
+ return channelClient.state();
+ }
+
+ /**
+ * Closes the connection, notifying the server it should close the channel by broadcasting the most recent payment
+ * transaction.
+ */
+ public synchronized void close() {
+ // 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
+ // ProtobufClient.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
+ // ProtobufParser.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.close();
+ } catch (IllegalStateException e) {
+ // Already closed...oh well
+ }
+ }
+}
diff --git a/core/src/main/java/com/google/bitcoin/protocols/channels/PaymentChannelClientState.java b/core/src/main/java/com/google/bitcoin/protocols/channels/PaymentChannelClientState.java
new file mode 100644
index 00000000..eb11f5be
--- /dev/null
+++ b/core/src/main/java/com/google/bitcoin/protocols/channels/PaymentChannelClientState.java
@@ -0,0 +1,408 @@
+/*
+ * 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 com.google.bitcoin.protocols.channels;
+
+import java.math.BigInteger;
+import java.util.List;
+
+import com.google.bitcoin.core.*;
+import com.google.bitcoin.crypto.TransactionSignature;
+import com.google.bitcoin.script.Script;
+import com.google.bitcoin.script.ScriptBuilder;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static com.google.common.base.Preconditions.*;
+
+/**
+ * A payment channel is a method of sending money to someone such that the amount of money you send can be adjusted
+ * after the fact, in an efficient manner that does not require broadcasting to the network. This can be used to
+ * implement micropayments or other payment schemes in which immediate settlement is not required, but zero trust
+ * negotiation is. Note that this class only allows the amount of money sent to be incremented, not decremented.
+ *
+ * This class implements the core state machine for the client side of the protocol. The server side is implemented
+ * by {@link PaymentChannelServerState} and {@link PaymentChannelClientConnection} implements a network protocol
+ * suitable for TCP/IP connections which moves this class through each state. We say that the party who is sending funds
+ * is the client or initiating party. The party that is receiving the funds is the server or
+ * receiving party. Although the underlying Bitcoin protocol is capable of more complex relationships than that,
+ * this class implements only the simplest case.
+ *
+ * A channel has an expiry parameter. If the server halts after the multi-signature contract which locks
+ * up the given value is broadcast you could get stuck in a state where you've lost all the money put into the
+ * contract. To avoid this, a refund transaction is agreed ahead of time but it may only be used/broadcast after
+ * the expiry time. This is specified in terms of block timestamps and once the timestamp of the chain chain approaches
+ * the given time (within a few hours), the channel must be closed or else the client will broadcast the refund
+ * transaction and take back all the money once the expiry time is reached.
+ *
+ * To begin, the client calls {@link PaymentChannelClientState#initiate()}, which moves the channel into state
+ * INITIATED and creates the initial multi-sig contract and refund transaction. If the wallet has insufficient funds an
+ * exception will be thrown at this point. Once this is done, call
+ * {@link PaymentChannelClientState#getIncompleteRefundTransaction()} and pass the resultant transaction through to the
+ * server. Once you have retrieved the signature, use {@link PaymentChannelClientState#provideRefundSignature(byte[])}.
+ * If no exception is thrown at this point, we are secure against a malicious server attempting to destroy all our coins
+ * and can provide the server with the multi-sig contract (via {@link PaymentChannelClientState#getMultisigContract()})
+ * safely.
+ *
+ */
+public class PaymentChannelClientState {
+ private static final Logger log = LoggerFactory.getLogger(PaymentChannelClientState.class);
+
+ private final Wallet wallet;
+ // Both sides need a key (private in our case, public for the server) in order to manage the multisig contract
+ // and transactions that spend it.
+ private final ECKey myKey, serverMultisigKey;
+ // How much value (in satoshis) is locked up into the channel.
+ private final BigInteger totalValue;
+ // When the channel will automatically close in favor of the client, if the server halts before protocol termination
+ // specified in terms of block timestamps (so it can off real time by a few hours).
+ private final long expiryTime;
+
+ // The refund is a time locked transaction that spends all the money of the channel back to the client.
+ private Transaction refundTx;
+ private BigInteger refundFees;
+ // The multi-sig contract locks the value of the channel up such that the agreement of both parties is required
+ // to spend it.
+ private Transaction multisigContract;
+ private Script multisigScript;
+ // How much value is currently allocated to us. Starts as being same as totalValue.
+ private BigInteger valueToMe;
+
+ /**
+ * The different logical states the channel can be in. The channel starts out as NEW, and then steps through the
+ * states until it becomes finalized. The server should have already been contacted and asked for a public key
+ * by the time the NEW state is reached.
+ */
+ public enum State {
+ NEW,
+ INITIATED,
+ WAITING_FOR_SIGNED_REFUND,
+ PROVIDE_MULTISIG_CONTRACT_TO_SERVER,
+ READY,
+ EXPIRED
+ }
+ private State state;
+
+ // The id of this channel in the StoredPaymentChannelClientStates, or null if it is not stored
+ private StoredClientChannel storedChannel;
+
+ PaymentChannelClientState(StoredClientChannel storedClientChannel, Wallet wallet) throws VerificationException {
+ // The PaymentChannelClientConnection handles storedClientChannel.active and ensures we aren't resuming channels
+ this.wallet = checkNotNull(wallet);
+ this.multisigContract = checkNotNull(storedClientChannel.contract);
+ this.multisigScript = multisigContract.getOutput(0).getScriptPubKey();
+ this.refundTx = checkNotNull(storedClientChannel.refund);
+ this.refundFees = checkNotNull(storedClientChannel.refundFees);
+ this.expiryTime = refundTx.getLockTime();
+ this.myKey = checkNotNull(storedClientChannel.myKey);
+ this.serverMultisigKey = null;
+ this.totalValue = multisigContract.getOutput(0).getValue();
+ this.valueToMe = checkNotNull(storedClientChannel.valueToMe);
+ this.storedChannel = storedClientChannel;
+ this.state = State.READY;
+ }
+
+ /**
+ * Creates a state object for a payment channel client. It is expected that you be ready to
+ * {@link PaymentChannelClientState#initiate()} after construction (to avoid creating objects for channels which are
+ * not going to finish opening) and thus some parameters provided here are only used in
+ * {@link PaymentChannelClientState#initiate()} to create the Multisig contract and refund transaction.
+ *
+ * @param wallet a wallet that contains at least the specified amount of value.
+ * @param myKey a freshly generated private key for this channel.
+ * @param serverMultisigKey a public key retrieved from the server used for the initial multisig contract
+ * @param value how many satoshis to put into this contract. If the channel reaches this limit, it must be closed.
+ * It is suggested you use at least {@link Utils#CENT} to avoid paying fees if you need to spend the refund transaction
+ * @param expiryTimeInSeconds At what point (UNIX timestamp +/- a few hours) the channel will expire
+ *
+ * @throws VerificationException If either myKey's pubkey or serverMultisigKey's pubkey are non-canonical (ie invalid)
+ */
+ public PaymentChannelClientState(Wallet wallet, ECKey myKey, ECKey serverMultisigKey,
+ BigInteger value, long expiryTimeInSeconds) throws VerificationException {
+ checkArgument(value.compareTo(BigInteger.ZERO) > 0);
+ this.wallet = checkNotNull(wallet);
+ this.serverMultisigKey = checkNotNull(serverMultisigKey);
+ if (!myKey.isPubKeyCanonical() || !serverMultisigKey.isPubKeyCanonical())
+ throw new VerificationException("Pubkey was not canonical (ie non-standard)");
+ this.myKey = checkNotNull(myKey);
+ this.valueToMe = this.totalValue = checkNotNull(value);
+ this.expiryTime = expiryTimeInSeconds;
+ this.state = State.NEW;
+ }
+
+ /**
+ * This object implements a state machine, and this accessor returns which state it's currently in.
+ */
+ public synchronized State getState() {
+ return state;
+ }
+
+ /**
+ * Creates the initial multisig contract and incomplete refund transaction which can be requested at the appropriate
+ * time using {@link PaymentChannelClientState#getIncompleteRefundTransaction} and
+ * {@link PaymentChannelClientState#getMultisigContract()}
+ *
+ * @throws ValueOutOfRangeException If the value being used cannot be afforded or is too small to be accepted by the network
+ */
+ public synchronized void initiate() throws ValueOutOfRangeException {
+ final NetworkParameters params = wallet.getParams();
+ Transaction template = new Transaction(params);
+ // We always place the client key before the server key because, if either side wants some privacy, they can
+ // use a fresh key for the the multisig contract and nowhere else
+ List keys = Lists.newArrayList(myKey, serverMultisigKey);
+ // There is also probably a change output, but we don't bother shuffling them as it's obvious from the
+ // format which one is the change. If we start obfuscating the change output better in future this may
+ // be worth revisiting.
+ TransactionOutput multisigOutput = template.addOutput(totalValue, ScriptBuilder.createMultiSigOutputScript(2, keys));
+ if (multisigOutput.getMinNonDustValue().compareTo(totalValue) > 0)
+ throw new ValueOutOfRangeException("totalValue too small to use");
+ Wallet.SendRequest req = Wallet.SendRequest.forTx(template);
+ if (!wallet.completeTx(req))
+ throw new ValueOutOfRangeException("Cannot afford this channel");
+ BigInteger multisigFee = req.fee;
+ multisigContract = req.tx;
+ // Build a refund transaction that protects us in the case of a bad server that's just trying to cause havoc
+ // by locking up peoples money (perhaps as a precursor to a ransom attempt). We time lock it so the server
+ // has an assurance that we cannot take back our money by claiming a refund before the channel closes - this
+ // relies on the fact that since Bitcoin 0.8 time locked transactions are non-final. This will need to change
+ // in future as it breaks the intended design of timelocking/tx replacement, but for now it simplifies this
+ // specific protocol somewhat.
+ refundTx = new Transaction(params);
+ refundTx.addInput(multisigOutput).setSequenceNumber(0); // Allow replacement when it's eventually reactivated.
+ refundTx.setLockTime(expiryTime);
+ if (totalValue.compareTo(Utils.CENT) < 0) {
+ // Must pay min fee.
+ final BigInteger valueAfterFee = totalValue.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE);
+ if (Transaction.MIN_NONDUST_OUTPUT.compareTo(valueAfterFee) > 0)
+ throw new ValueOutOfRangeException("totalValue too small to use");
+ refundTx.addOutput(valueAfterFee, myKey.toAddress(params));
+ refundFees = multisigFee.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE);
+ } else {
+ refundTx.addOutput(totalValue, myKey.toAddress(params));
+ refundFees = multisigFee;
+ }
+ state = State.INITIATED;
+ // Client should now call getIncompleteRefundTransaction() and send it to the server.
+ }
+
+ /**
+ * Returns the transaction that locks the money to the agreement of both parties. Do not mutate the result.
+ * Once this step is done, you can use {@link PaymentChannelClientState#incrementPaymentBy(java.math.BigInteger)} to
+ * start paying the server.
+ */
+ public synchronized Transaction getMultisigContract() {
+ checkState(multisigContract != null);
+ if (state == State.PROVIDE_MULTISIG_CONTRACT_TO_SERVER)
+ state = State.READY;
+ return multisigContract;
+ }
+
+ /**
+ * Returns a partially signed (invalid) refund transaction that should be passed to the server. Once the server
+ * has checked it out and provided its own signature, call
+ * {@link PaymentChannelClientState#provideRefundSignature(byte[])} with the result.
+ */
+ public synchronized Transaction getIncompleteRefundTransaction() {
+ checkState(refundTx != null);
+ if (state == State.INITIATED)
+ state = State.WAITING_FOR_SIGNED_REFUND;
+ return refundTx;
+ }
+
+ /**
+ * When the servers signature for the refund transaction is received, call this to verify it and sign the
+ * complete refund ourselves.
+ *
+ * If this does not throw an exception, we are secure against the loss of funds and can safely provide the server
+ * with the multi-sig contract to lock in the agreement. In this case, both the multisig contract and the refund
+ * transaction are automatically committed to wallet so that it can handle broadcasting the refund transaction at
+ * the appropriate time if necessary.
+ */
+ public synchronized void provideRefundSignature(byte[] theirSignature) throws VerificationException {
+ checkNotNull(theirSignature);
+ checkState(state == State.WAITING_FOR_SIGNED_REFUND);
+ TransactionSignature theirSig = TransactionSignature.decodeFromBitcoin(theirSignature, true);
+ if (theirSig.sigHashMode() != Transaction.SigHash.NONE || !theirSig.anyoneCanPay())
+ throw new VerificationException("Refund signature was not SIGHASH_NONE|SIGHASH_ANYONECANPAY");
+ // Sign the refund transaction ourselves.
+ final TransactionOutput multisigContractOutput = multisigContract.getOutput(0);
+ try {
+ multisigScript = multisigContractOutput.getScriptPubKey();
+ } catch (ScriptException e) {
+ throw new RuntimeException(e); // Cannot happen: we built this ourselves.
+ }
+ TransactionSignature ourSignature =
+ refundTx.calculateSignature(0, myKey, multisigScript, Transaction.SigHash.ALL, false);
+ // Insert the signatures.
+ Script scriptSig = ScriptBuilder.createMultiSigInputScript(ImmutableList.of(ourSignature, theirSig));
+ log.info("Refund scriptSig: {}", scriptSig);
+ log.info("Multi-sig contract scriptPubKey: {}", multisigScript);
+ TransactionInput refundInput = refundTx.getInput(0);
+ refundInput.setScriptSig(scriptSig);
+ refundInput.verify(multisigContractOutput);
+ wallet.commitTx(multisigContract);
+ state = State.PROVIDE_MULTISIG_CONTRACT_TO_SERVER;
+ }
+
+ private synchronized Transaction makeUnsignedChannelContract(BigInteger valueToMe) throws ValueOutOfRangeException {
+ Transaction tx = new Transaction(wallet.getParams());
+ tx.addInput(multisigContract.getOutput(0));
+ // Our output always comes first.
+ // TODO: We should drop myKey in favor of output key + multisig key separation
+ // (as its always obvious who the client is based on T2 output order)
+ tx.addOutput(valueToMe, myKey.toAddress(wallet.getParams()));
+ return tx;
+ }
+
+ /**
+ * Checks if the channel is expired, setting state to {@link State#EXPIRED}, removing this channel from wallet
+ * storage and throwing an {@link IllegalStateException} if it is.
+ */
+ public synchronized void checkNotExpired() {
+ if (Utils.now().getTime()/1000 > expiryTime) {
+ state = State.EXPIRED;
+ disconnectFromChannel();
+ throw new IllegalStateException("Channel expired");
+ }
+ }
+
+ /**
+ * Updates the outputs on the payment contract transaction and re-signs it. The state must be READY in order to
+ * call this method. The signature that is returned should be sent to the server so it has the ability to broadcast
+ * the best seen payment when the channel closes or times out.
+ *
+ * The returned signature is over the payment transaction, which we never have a valid copy of and thus there
+ * is no accessor for it on this object.
+ *
+ * To spend the whole channel increment by {@link PaymentChannelClientState#getTotalValue()} -
+ * {@link PaymentChannelClientState#getValueRefunded()}
+ *
+ * @param size How many satoshis to increment the payment by (note: not the new total).
+ * @throws ValueOutOfRangeException If size is negative or the new value being returned as change is smaller than
+ * min nondust output size (including if the new total payment is larger than this
+ * channel's totalValue)
+ */
+ public synchronized byte[] incrementPaymentBy(BigInteger size) throws ValueOutOfRangeException {
+ checkState(state == State.READY);
+ checkNotExpired();
+ checkNotNull(size); // Validity of size will be checked by makeUnsignedChannelContract.
+ if (size.compareTo(BigInteger.ZERO) < 0)
+ throw new ValueOutOfRangeException("Tried to decrement payment");
+ BigInteger newValueToMe = valueToMe.subtract(size);
+ if (Transaction.MIN_NONDUST_OUTPUT.compareTo(newValueToMe) > 0 && !newValueToMe.equals(BigInteger.ZERO))
+ throw new ValueOutOfRangeException("New value being sent back as change was smaller than minimum nondust output");
+ Transaction tx = makeUnsignedChannelContract(newValueToMe);
+ log.info("Signing new contract: {}", tx);
+ Transaction.SigHash mode;
+ // If we spent all the money we put into this channel, we (by definition) don't care what the outputs are, so
+ // we sign with SIGHASH_NONE to let the server do what it wants.
+ if (newValueToMe.equals(BigInteger.ZERO))
+ mode = Transaction.SigHash.NONE;
+ else
+ mode = Transaction.SigHash.SINGLE;
+ TransactionSignature sig = tx.calculateSignature(0, myKey, multisigScript, mode, true);
+ valueToMe = newValueToMe;
+ updateChannelInWallet();
+ return sig.encodeToBitcoin();
+ }
+
+ private synchronized void updateChannelInWallet() {
+ if (storedChannel == null)
+ return;
+ storedChannel.updateValueToMe(valueToMe);
+ StoredPaymentChannelClientStates channels = (StoredPaymentChannelClientStates)
+ wallet.getExtensions().get(StoredPaymentChannelClientStates.EXTENSION_ID);
+ wallet.addOrUpdateExtension(channels);
+ }
+
+ /**
+ * Sets this channel's state in {@link StoredPaymentChannelClientStates} to unopened so this channel can be reopened
+ * later.
+ *
+ * @see PaymentChannelClientState#storeChannelInWallet(Sha256Hash)
+ */
+ public synchronized void disconnectFromChannel() {
+ if (storedChannel == null)
+ return;
+ synchronized (storedChannel) {
+ storedChannel.active = false;
+ }
+ storedChannel = null;
+ }
+
+ /**
+ * Stores this channel's state in the wallet as a part of a {@link StoredPaymentChannelClientStates} wallet
+ * extension and keeps it up-to-date each time payment is incremented. This allows the
+ * {@link StoredPaymentChannelClientStates} object to keep track of timeouts and broadcast the refund transaction
+ * when the channel expires.
+ *
+ * A channel may only be stored after it has fully opened (ie state == State.READY). The wallet provided in the
+ * constructor must already have a {@link StoredPaymentChannelClientStates} object in its extensions set.
+ *
+ * @param id A hash providing this channel with an id which uniquely identifies this server. It does not have to be
+ * unique.
+ */
+ public synchronized void storeChannelInWallet(Sha256Hash id) {
+ checkState(state == State.READY && id != null);
+ if (storedChannel != null) {
+ checkState(storedChannel.id.equals(id));
+ return;
+ }
+
+ StoredPaymentChannelClientStates channels = (StoredPaymentChannelClientStates)
+ wallet.getExtensions().get(StoredPaymentChannelClientStates.EXTENSION_ID);
+ checkState(channels.getChannel(id, multisigContract.getHash()) == null);
+ storedChannel = new StoredClientChannel(id, multisigContract, refundTx, myKey, valueToMe, refundFees);
+ channels.putChannel(storedChannel);
+ wallet.addOrUpdateExtension(channels);
+ }
+
+ /**
+ * Returns the fees that will be paid if the refund transaction has to be claimed because the server failed to close
+ * the channel properly. May only be called after {@link PaymentChannelClientState#initiate()}
+ */
+ public synchronized BigInteger getRefundTxFees() {
+ checkState(state.compareTo(State.NEW) > 0);
+ return refundFees;
+ }
+
+ /**
+ * Once the servers signature over the refund transaction has been received and provided using
+ * {@link PaymentChannelClientState#provideRefundSignature(byte[])} then this
+ * method can be called to receive the now valid and broadcastable refund transaction.
+ */
+ public synchronized Transaction getCompletedRefundTransaction() {
+ checkState(state.compareTo(State.WAITING_FOR_SIGNED_REFUND) > 0);
+ return refundTx;
+ }
+
+ /**
+ * Gets the total value of this channel (ie the maximum payment possible)
+ */
+ public BigInteger getTotalValue() {
+ return totalValue;
+ }
+
+ /**
+ * Gets the current amount refunded to us from the multisig contract (ie totalValue-valueSentToServer)
+ */
+ public synchronized BigInteger getValueRefunded() {
+ checkState(state == State.READY);
+ return valueToMe;
+ }
+}
diff --git a/core/src/main/java/com/google/bitcoin/protocols/channels/PaymentChannelCloseException.java b/core/src/main/java/com/google/bitcoin/protocols/channels/PaymentChannelCloseException.java
new file mode 100644
index 00000000..34daaf5d
--- /dev/null
+++ b/core/src/main/java/com/google/bitcoin/protocols/channels/PaymentChannelCloseException.java
@@ -0,0 +1,57 @@
+package com.google.bitcoin.protocols.channels;
+
+/**
+ * Used to indicate that a channel was closed before it was expected to be closed.
+ * This could mean the connection timed out, the other send sent an error or a CLOSE message, etc
+ */
+public class PaymentChannelCloseException extends Exception {
+ public enum CloseReason {
+ /** We could not find a version which was mutually acceptable with the client/server */
+ NO_ACCEPTABLE_VERSION,
+ /** Generated by a client when the server attempted to lock in our funds for an unacceptably long time */
+ TIME_WINDOW_TOO_LARGE,
+ /** Generated by a client when the server requested we lock up an unacceptably high value */
+ SERVER_REQUESTED_TOO_MUCH_VALUE,
+
+ // Values after here indicate its probably possible to try reopening channel again
+
+ /**
+ * The {@link com.google.bitcoin.protocols.channels.PaymentChannelClient#close()} method was called or the
+ * client sent a CLOSE message.
+ * As long as the server received the CLOSE message, this means that the channel was closed and the payment
+ * transaction (if any) was broadcast. If the client attempts to open a new connection, a new channel will have
+ * to be opened.
+ */
+ CLIENT_REQUESTED_CLOSE,
+
+ /**
+ * The {@link com.google.bitcoin.protocols.channels.PaymentChannelServer#close()} method was called or server
+ * sent a CLOSE message.
+ *
+ * This may occur if the server opts to close the connection for some reason, or automatically if the channel
+ * times out (called by {@link StoredPaymentChannelServerStates}).
+ *
+ * For a client, this usually indicates that we should try again if we need to continue paying (either
+ * opening a new channel or continuing with the same one depending on the server's preference)
+ */
+ SERVER_REQUESTED_CLOSE,
+
+ /** Remote side sent an ERROR message */
+ REMOTE_SENT_ERROR,
+ /** Remote side sent a message we did not understand */
+ REMOTE_SENT_INVALID_MESSAGE,
+
+ /** The connection was closed without an ERROR/CLOSE message */
+ CONNECTION_CLOSED,
+ }
+
+ CloseReason error;
+ public CloseReason getCloseReason() {
+ return error;
+ }
+
+ public PaymentChannelCloseException(String message, CloseReason error) {
+ super(message);
+ this.error = error;
+ }
+}
diff --git a/core/src/main/java/com/google/bitcoin/protocols/channels/PaymentChannelServer.java b/core/src/main/java/com/google/bitcoin/protocols/channels/PaymentChannelServer.java
new file mode 100644
index 00000000..cdb53ea1
--- /dev/null
+++ b/core/src/main/java/com/google/bitcoin/protocols/channels/PaymentChannelServer.java
@@ -0,0 +1,411 @@
+package com.google.bitcoin.protocols.channels;
+
+import java.math.BigInteger;
+import java.util.concurrent.locks.ReentrantLock;
+
+import com.google.bitcoin.core.*;
+import com.google.bitcoin.utils.Locks;
+import com.google.common.util.concurrent.MoreExecutors;
+import com.google.protobuf.ByteString;
+import net.jcip.annotations.GuardedBy;
+import org.bitcoin.paymentchannel.Protos;
+import org.slf4j.LoggerFactory;
+
+import com.google.bitcoin.protocols.channels.PaymentChannelCloseException.CloseReason;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * A handler class which handles most of the complexity of creating a payment channel connection by providing a
+ * simple in/out interface which is provided with protobufs from the client and which generates protobufs which should
+ * be sent to the client.
+ *
+ * Does all required verification of messages and properly stores state objects in the wallet-attached
+ * {@link StoredPaymentChannelServerStates} so that they are automatically closed when necessary and payment
+ * transactions are not lost if the application crashes before it unlocks.
+ */
+public class PaymentChannelServer {
+ //TODO: Update JavaDocs with notes for communication over stateless protocols
+ private static final org.slf4j.Logger log = LoggerFactory.getLogger(PaymentChannelServer.class);
+
+ protected final ReentrantLock lock = Locks.lock("channelserver");
+
+ // The step in the initialization process we are in, some of this is duplicated in the PaymentChannelServerState
+ private enum InitStep {
+ WAITING_ON_CLIENT_VERSION,
+ WAITING_ON_UNSIGNED_REFUND,
+ WAITING_ON_CONTRACT,
+ WAITING_ON_MULTISIG_ACCEPTANCE,
+ CHANNEL_OPEN
+ }
+ @GuardedBy("lock") private InitStep step = InitStep.WAITING_ON_CLIENT_VERSION;
+
+ /**
+ * Implements the connection between this server and the client, providing an interface which allows messages to be
+ * sent to the client, requests for the connection to the client to be closed, and callbacks which occur when the
+ * channel is fully open or the client completes a payment.
+ */
+ public interface ServerConnection {
+ /**
+ * Requests that the given message be sent to the client. There are no blocking requirements for this method,
+ * however the order of messages must be preserved.
+ *
+ * If the send fails, no exception should be thrown, however
+ * {@link PaymentChannelServer#connectionClosed()} should be called immediately.
+ *
+ * Called while holding a lock on the {@link PaymentChannelServer} object - be careful about reentrancy
+ */
+ public void sendToClient(Protos.TwoWayChannelMessage msg);
+
+ /**
+ * Requests that the connection to the client be closed
+ *
+ * Called while holding a lock on the {@link PaymentChannelServer} object - be careful about reentrancy
+ *
+ * @param reason The reason for the closure, see the individual values for more details.
+ * It is usually safe to ignore this value.
+ */
+ public void destroyConnection(CloseReason reason);
+
+ /**
+ * Triggered when the channel is opened and payments can begin
+ *
+ * Called while holding a lock on the {@link PaymentChannelServer} object - be careful about reentrancy
+ *
+ * @param contractHash A unique identifier which represents this channel (actually the hash of the multisig contract)
+ */
+ public void channelOpen(Sha256Hash contractHash);
+
+ /**
+ * Called when the payment in this channel was successfully incremented by the client
+ *
+ * Called while holding a lock on the {@link PaymentChannelServer} object - be careful about reentrancy
+ *
+ * @param by The increase in total payment
+ * @param to The new total payment to us (not including fees which may be required to claim the payment)
+ */
+ public void paymentIncrease(BigInteger by, BigInteger to);
+ }
+ @GuardedBy("lock") private final ServerConnection conn;
+
+ // Used to keep track of whether or not the "socket" ie connection is open and we can generate messages
+ @GuardedBy("lock") private boolean connectionOpen = false;
+ // Indicates that no further messages should be sent and we intend to close the connection
+ @GuardedBy("lock") private boolean connectionClosing = false;
+
+ // The wallet and peergroup which are used to complete/broadcast transactions
+ private final Wallet wallet;
+ private final PeerGroup peerGroup;
+
+ // The key used for multisig in this channel
+ @GuardedBy("lock") private ECKey myKey;
+
+ // The minimum accepted channel value
+ private final BigInteger minAcceptedChannelSize;
+
+ // The state manager for this channel
+ @GuardedBy("lock") private PaymentChannelServerState state;
+
+ // The time this channel expires (ie the refund transaction's locktime)
+ @GuardedBy("lock") private long expireTime;
+
+ /**
+ * The amount of time we request the client lock in their funds.
+ *
+ * The value defaults to 24 hours - 60 seconds and should always be greater than 2 hours plus the amount of time
+ * the channel is expected to be used and smaller than 24 hours minus the client <-> server latency minus some
+ * factor to account for client clock inaccuracy.
+ */
+ public long timeWindow = 24*60*60 - 60;
+
+ /**
+ * Creates a new server-side state manager which handles a single client connection.
+ *
+ * @param peerGroup The PeerGroup on which transactions will be broadcast - should have multiple connections.
+ * @param wallet The wallet which will be used to complete transactions.
+ * Unlike {@link PaymentChannelClient}, this does not have to already contain a StoredState manager
+ * @param minAcceptedChannelSize The minimum value the client must lock into this channel. A value too large will be
+ * rejected by clients, and a value too low will require excessive channel reopening
+ * and may cause fees to be require to close the channel. A reasonable value depends
+ * entirely on the expected maximum for the channel, and should likely be somewhere
+ * between a few bitcents and a bitcoin.
+ * @param conn A callback listener which represents the connection to the client (forwards messages we generate to
+ * the client and will close the connection on request)
+ */
+ public PaymentChannelServer(PeerGroup peerGroup, Wallet wallet, BigInteger minAcceptedChannelSize, ServerConnection conn) {
+ this.peerGroup = checkNotNull(peerGroup);
+ this.wallet = checkNotNull(wallet);
+ this.minAcceptedChannelSize = checkNotNull(minAcceptedChannelSize);
+ this.conn = checkNotNull(conn);
+ }
+
+ @GuardedBy("lock")
+ private void receiveVersionMessage(Protos.TwoWayChannelMessage msg) throws VerificationException {
+ Protos.ServerVersion.Builder versionNegotiationBuilder = Protos.ServerVersion.newBuilder()
+ .setMajor(0).setMinor(1);
+ conn.sendToClient(Protos.TwoWayChannelMessage.newBuilder()
+ .setType(Protos.TwoWayChannelMessage.MessageType.SERVER_VERSION)
+ .setServerVersion(versionNegotiationBuilder)
+ .build());
+
+ ByteString reopenChannelContractHash = msg.getClientVersion().getPreviousChannelContractHash();
+ if (reopenChannelContractHash != null && reopenChannelContractHash.size() == 32) {
+ StoredPaymentChannelServerStates channels = (StoredPaymentChannelServerStates)
+ wallet.getExtensions().get(StoredPaymentChannelServerStates.EXTENSION_ID);
+ if (channels != null) {
+ Sha256Hash contractHash = new Sha256Hash(reopenChannelContractHash.toByteArray());
+ StoredServerChannel storedServerChannel = channels.getChannel(contractHash);
+ if (storedServerChannel != null) {
+ if (storedServerChannel.setConnectedHandler(this)) {
+ log.info("Got resume version message, responding with VERSIONS and CHANNEL_OPEN");
+
+ state = storedServerChannel.getState(wallet, peerGroup);
+ step = InitStep.CHANNEL_OPEN;
+ conn.sendToClient(Protos.TwoWayChannelMessage.newBuilder()
+ .setType(Protos.TwoWayChannelMessage.MessageType.CHANNEL_OPEN)
+ .build());
+ conn.channelOpen(contractHash);
+ return;
+ }
+ }
+ }
+ }
+ log.info("Got initial version message, responding with VERSIONS and INITIATE");
+
+ myKey = new ECKey();
+ wallet.addKey(myKey);
+
+ expireTime = Utils.now().getTime() / 1000 + timeWindow;
+ step = InitStep.WAITING_ON_UNSIGNED_REFUND;
+
+ Protos.Initiate.Builder initiateBuilder = Protos.Initiate.newBuilder()
+ .setMultisigKey(ByteString.copyFrom(myKey.getPubKey()))
+ .setExpireTimeSecs(expireTime)
+ .setMinAcceptedChannelSize(minAcceptedChannelSize.longValue());
+
+ conn.sendToClient(Protos.TwoWayChannelMessage.newBuilder()
+ .setInitiate(initiateBuilder)
+ .setType(Protos.TwoWayChannelMessage.MessageType.INITIATE)
+ .build());
+ }
+
+ @GuardedBy("lock")
+ private void receiveRefundMessage(Protos.TwoWayChannelMessage msg) throws VerificationException {
+ checkState(step == InitStep.WAITING_ON_UNSIGNED_REFUND && msg.hasProvideRefund());
+ log.info("Got refund transaction, returning signature");
+
+ Protos.ProvideRefund providedRefund = msg.getProvideRefund();
+ state = new PaymentChannelServerState(peerGroup, wallet, myKey, expireTime);
+ byte[] signature = state.provideRefundTransaction(new Transaction(wallet.getParams(), providedRefund.getTx().toByteArray()),
+ providedRefund.getMultisigKey().toByteArray());
+
+ step = InitStep.WAITING_ON_CONTRACT;
+
+ Protos.ReturnRefund.Builder returnRefundBuilder = Protos.ReturnRefund.newBuilder()
+ .setSignature(ByteString.copyFrom(signature));
+
+ conn.sendToClient(Protos.TwoWayChannelMessage.newBuilder()
+ .setReturnRefund(returnRefundBuilder)
+ .setType(Protos.TwoWayChannelMessage.MessageType.RETURN_REFUND)
+ .build());
+ }
+
+ private void multisigContractPropogated(Sha256Hash contractHash) {
+ lock.lock();
+ try {
+ if (!connectionOpen || connectionClosing)
+ return;
+ state.storeChannelInWallet(PaymentChannelServer.this);
+ conn.sendToClient(Protos.TwoWayChannelMessage.newBuilder()
+ .setType(Protos.TwoWayChannelMessage.MessageType.CHANNEL_OPEN)
+ .build());
+ step = InitStep.CHANNEL_OPEN;
+ conn.channelOpen(contractHash);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ @GuardedBy("lock")
+ private void receiveContractMessage(Protos.TwoWayChannelMessage msg) throws VerificationException {
+ checkState(step == InitStep.WAITING_ON_CONTRACT && msg.hasProvideContract());
+ log.info("Got contract, broadcasting and responding with CHANNEL_OPEN");
+ Protos.ProvideContract providedContract = msg.getProvideContract();
+
+ //TODO notify connection handler that timeout should be significantly extended as we wait for network propagation?
+ final Transaction multisigContract = new Transaction(wallet.getParams(), providedContract.getTx().toByteArray());
+ step = InitStep.WAITING_ON_MULTISIG_ACCEPTANCE;
+ state.provideMultiSigContract(multisigContract)
+ .addListener(new Runnable() {
+ @Override
+ public void run() {
+ multisigContractPropogated(multisigContract.getHash());
+ }
+ }, MoreExecutors.sameThreadExecutor());
+ }
+
+ @GuardedBy("lock")
+ private void receiveUpdatePaymentMessage(Protos.TwoWayChannelMessage msg) throws VerificationException, ValueOutOfRangeException {
+ checkState(step == InitStep.CHANNEL_OPEN && msg.hasUpdatePayment());
+ log.info("Got a payment update");
+
+ Protos.UpdatePayment updatePayment = msg.getUpdatePayment();
+ BigInteger lastBestPayment = state.getBestValueToMe();
+ state.incrementPayment(BigInteger.valueOf(updatePayment.getClientChangeValue()), updatePayment.getSignature().toByteArray());
+ BigInteger bestPaymentChange = state.getBestValueToMe().subtract(lastBestPayment);
+
+ if (bestPaymentChange.compareTo(BigInteger.ZERO) > 0)
+ conn.paymentIncrease(bestPaymentChange, state.getBestValueToMe());
+ }
+
+ /**
+ * Called when a message is received from the client. Processes the given message and generates events based on its
+ * content.
+ */
+ public void receiveMessage(Protos.TwoWayChannelMessage msg) {
+ lock.lock();
+ try {
+ checkState(connectionOpen);
+ if (connectionClosing)
+ return;
+ // If we generate an error, we set errorBuilder and closeReason and break, otherwise we return
+ Protos.Error.Builder errorBuilder;
+ CloseReason closeReason;
+ try {
+ switch (msg.getType()) {
+ case CLIENT_VERSION:
+ checkState(step == InitStep.WAITING_ON_CLIENT_VERSION && msg.hasClientVersion());
+ if (msg.getClientVersion().getMajor() != 0) {
+ errorBuilder = Protos.Error.newBuilder()
+ .setCode(Protos.Error.ErrorCode.NO_ACCEPTABLE_VERSION);
+ closeReason = CloseReason.NO_ACCEPTABLE_VERSION;
+ break;
+ }
+
+ receiveVersionMessage(msg);
+ return;
+ case PROVIDE_REFUND:
+ receiveRefundMessage(msg);
+ return;
+ case PROVIDE_CONTRACT:
+ receiveContractMessage(msg);
+ return;
+ case UPDATE_PAYMENT:
+ receiveUpdatePaymentMessage(msg);
+ return;
+ case CLOSE:
+ log.info("Got CLOSE message, closing channel");
+ connectionClosing = true;
+ if (state != null)
+ state.close();
+ conn.destroyConnection(CloseReason.CLIENT_REQUESTED_CLOSE);
+ return;
+ case ERROR:
+ checkState(msg.hasError());
+ log.error("Client sent ERROR {} with explanation {}", msg.getError().getCode().name(),
+ msg.getError().hasExplanation() ? msg.getError().getExplanation() : "");
+ conn.destroyConnection(CloseReason.REMOTE_SENT_ERROR);
+ return;
+ default:
+ log.error("Got unknown message type or type that doesn't apply to servers.");
+ errorBuilder = Protos.Error.newBuilder()
+ .setCode(Protos.Error.ErrorCode.SYNTAX_ERROR);
+ closeReason = CloseReason.REMOTE_SENT_INVALID_MESSAGE;
+ break;
+ }
+ } catch (VerificationException e) {
+ log.error("Caught verification exception handling message from client {}", e);
+ errorBuilder = Protos.Error.newBuilder()
+ .setCode(Protos.Error.ErrorCode.BAD_TRANSACTION)
+ .setExplanation(e.getMessage());
+ closeReason = CloseReason.REMOTE_SENT_INVALID_MESSAGE;
+ } catch (ValueOutOfRangeException e) {
+ log.error("Caught value out of range exception handling message from client {}", e);
+ errorBuilder = Protos.Error.newBuilder()
+ .setCode(Protos.Error.ErrorCode.BAD_TRANSACTION)
+ .setExplanation(e.getMessage());
+ closeReason = CloseReason.REMOTE_SENT_INVALID_MESSAGE;
+ } catch (IllegalStateException e) {
+ log.error("Caught illegal state exception handling message from client {}", e);
+ errorBuilder = Protos.Error.newBuilder()
+ .setCode(Protos.Error.ErrorCode.SYNTAX_ERROR);
+ closeReason = CloseReason.REMOTE_SENT_INVALID_MESSAGE;
+ }
+ conn.sendToClient(Protos.TwoWayChannelMessage.newBuilder()
+ .setError(errorBuilder)
+ .setType(Protos.TwoWayChannelMessage.MessageType.ERROR)
+ .build());
+ conn.destroyConnection(closeReason);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Called when the connection terminates. Notifies the {@link StoredServerChannel} object that we can attempt to
+ * resume this channel in the future and stops generating messages for the client.
+ *
+ * Note that this MUST still be called even after either
+ * {@link ServerConnection#destroyConnection(CloseReason)} or
+ * {@link PaymentChannelServer#close()} is called to actually handle the connection close logic.
+ */
+ public void connectionClosed() {
+ lock.lock();
+ try {
+ log.info("Server channel closed.");
+ connectionOpen = false;
+
+ try {
+ if (state != null && state.getMultisigContract() != null) {
+ StoredPaymentChannelServerStates channels = (StoredPaymentChannelServerStates)
+ wallet.getExtensions().get(StoredPaymentChannelServerStates.EXTENSION_ID);
+ if (channels != null) {
+ StoredServerChannel storedServerChannel = channels.getChannel(state.getMultisigContract().getHash());
+ if (storedServerChannel != null) {
+ storedServerChannel.setConnectedHandler(null);
+ }
+ }
+ }
+ } catch (IllegalStateException e) {
+ // Expected when we call getMultisigContract() sometimes
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Called to indicate the connection has been opened and messages can now be generated for the client.
+ */
+ public void connectionOpen() {
+ lock.lock();
+ try {
+ log.info("New server channel active.");
+ connectionOpen = true;
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Closes the connection by generating a close message for the client and calls
+ * {@link ServerConnection#destroyConnection(CloseReason)}. Note that this does not broadcast
+ * the payment transaction and the client may still resume the same channel if they reconnect
+ *
+ * Note that {@link PaymentChannelServer#connectionClosed()} must still be called after the connection fully
+ * closes.
+ */
+ public void close() {
+ lock.lock();
+ try {
+ if (connectionOpen && !connectionClosing) {
+ conn.sendToClient(Protos.TwoWayChannelMessage.newBuilder()
+ .setType(Protos.TwoWayChannelMessage.MessageType.CLOSE)
+ .build());
+ conn.destroyConnection(CloseReason.SERVER_REQUESTED_CLOSE);
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+}
diff --git a/core/src/main/java/com/google/bitcoin/protocols/channels/PaymentChannelServerListener.java b/core/src/main/java/com/google/bitcoin/protocols/channels/PaymentChannelServerListener.java
new file mode 100644
index 00000000..87bab817
--- /dev/null
+++ b/core/src/main/java/com/google/bitcoin/protocols/channels/PaymentChannelServerListener.java
@@ -0,0 +1,183 @@
+/*
+ * 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 com.google.bitcoin.protocols.channels;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import com.google.bitcoin.core.PeerGroup;
+import com.google.bitcoin.core.Sha256Hash;
+import com.google.bitcoin.core.Wallet;
+import com.google.bitcoin.protocols.niowrapper.ProtobufParser;
+import com.google.bitcoin.protocols.niowrapper.ProtobufParserFactory;
+import com.google.bitcoin.protocols.niowrapper.ProtobufServer;
+import org.bitcoin.paymentchannel.Protos;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Manages a {@link PaymentChannelClient} by connecting to a server using a simple TCP socket and exchanging the
+ * necessary protobufs.
+ */
+public class PaymentChannelServerListener {
+ // The wallet and peergroup which are used to complete/broadcast transactions
+ private final Wallet wallet;
+ private final PeerGroup peerGroup;
+
+ // The event handler factory which creates new ServerConnectionEventHandler per connection
+ private final HandlerFactory eventHandlerFactory;
+ private final BigInteger minAcceptedChannelSize;
+
+ private final ProtobufServer server;
+
+ /**
+ * A factory which generates connection-specific event handlers.
+ */
+ public static 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 public ServerConnectionEventHandler onNewConnection(SocketAddress clientAddress);
+ }
+
+ private class ServerHandler {
+ public ServerHandler(final SocketAddress address, final int timeoutSeconds) {
+ paymentChannelManager = new PaymentChannelServer(peerGroup, 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 void paymentIncrease(BigInteger by, BigInteger to) {
+ eventHandler.paymentIncrease(by, to);
+ }
+ });
+
+ protobufHandlerListener = new ProtobufParser.Listener() {
+ @Override
+ public synchronized void messageReceived(ProtobufParser handler, Protos.TwoWayChannelMessage msg) {
+ paymentChannelManager.receiveMessage(msg);
+ }
+
+ @Override
+ public synchronized void connectionClosed(ProtobufParser handler) {
+ paymentChannelManager.connectionClosed();
+ if (closeReason != null)
+ eventHandler.channelClosed(closeReason);
+ else
+ eventHandler.channelClosed(PaymentChannelCloseException.CloseReason.CONNECTION_CLOSED);
+ eventHandler.setConnectionChannel(null);
+ }
+
+ @Override
+ public synchronized void connectionOpen(ProtobufParser handler) {
+ ServerConnectionEventHandler eventHandler = eventHandlerFactory.onNewConnection(address);
+ if (eventHandler == null)
+ handler.closeConnection();
+ else {
+ ServerHandler.this.eventHandler = eventHandler;
+ paymentChannelManager.connectionOpen();
+ }
+ }
+ };
+
+ socketProtobufHandler = new ProtobufParser
+ (protobufHandlerListener, Protos.TwoWayChannelMessage.getDefaultInstance(), Short.MAX_VALUE, timeoutSeconds*1000);
+ }
+
+ private PaymentChannelCloseException.CloseReason closeReason;
+
+ // The user-provided event handler
+ @Nonnull 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 ProtobufParser socketProtobufHandler;
+
+ // The listener which connects to socketProtobufHandler
+ private final ProtobufParser.Listener 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.start(new InetSocketAddress(port));
+ }
+
+ /**
+ * Sets up a new payment channel server which listens on the given port.
+ *
+ * @param peerGroup 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(PeerGroup peerGroup, Wallet wallet, final int timeoutSeconds, BigInteger minAcceptedChannelSize,
+ HandlerFactory eventHandlerFactory) throws IOException {
+ this.wallet = checkNotNull(wallet);
+ this.peerGroup = checkNotNull(peerGroup);
+ this.eventHandlerFactory = checkNotNull(eventHandlerFactory);
+ this.minAcceptedChannelSize = checkNotNull(minAcceptedChannelSize);
+
+ server = new ProtobufServer(new ProtobufParserFactory() {
+ @Override
+ public ProtobufParser getNewParser(InetAddress inetAddress, int port) {
+ return new ServerHandler(new InetSocketAddress(inetAddress, port), timeoutSeconds).socketProtobufHandler;
+ }
+ });
+ }
+
+ /**
+ * Closes all client connections currently connected gracefully.
+ *
+ * Note that this does not close the actual payment channels (and broadcast payment transactions), which
+ * must be done using the {@link StoredPaymentChannelServerStates} which manages the states for the associated
+ * wallet.
+ */
+ public void close() {
+ try {
+ server.stop();
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/core/src/main/java/com/google/bitcoin/protocols/channels/PaymentChannelServerState.java b/core/src/main/java/com/google/bitcoin/protocols/channels/PaymentChannelServerState.java
new file mode 100644
index 00000000..e5395dd1
--- /dev/null
+++ b/core/src/main/java/com/google/bitcoin/protocols/channels/PaymentChannelServerState.java
@@ -0,0 +1,472 @@
+/*
+ * 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 com.google.bitcoin.protocols.channels;
+
+import java.math.BigInteger;
+import java.util.Arrays;
+import javax.annotation.Nullable;
+
+import com.google.bitcoin.core.*;
+import com.google.bitcoin.crypto.TransactionSignature;
+import com.google.bitcoin.script.Script;
+import com.google.bitcoin.script.ScriptBuilder;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+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.SettableFuture;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static com.google.common.base.Preconditions.*;
+
+/**
+ * A payment channel is a method of sending money to someone such that the amount of money you send can be adjusted
+ * after the fact, in an efficient manner that does not require broadcasting to the network. This can be used to
+ * implement micropayments or other payment schemes in which immediate settlement is not required, but zero trust
+ * negotiation is. Note that this class only allows the amount of money received to be incremented, not decremented.
+ *
+ * This class implements the core state machine for the server side of the protocol. The client side is implemented
+ * by {@link PaymentChannelClientState} and {@link PaymentChannelServerListener} implements the server-side network
+ * protocol listening for TCP/IP connections and moving this class through each state. We say that the party who is
+ * sending funds is the client or initiating party. The party that is receiving the funds is the
+ * server or receiving party. Although the underlying Bitcoin protocol is capable of more complex
+ * relationships than that, this class implements only the simplest case.
+ *
+ * To protect clients from malicious servers, a channel has an expiry parameter. When this expiration is reached, the
+ * client will broadcast the created refund transaction and take back all the money in this channel. Because this is
+ * specified in terms of block timestamps, it is fairly fuzzy and it is possible to spend the refund transaction up to a
+ * few hours before the actual timestamp. Thus, it is very important that the channel be closed with plenty of time left
+ * to get the highest value payment transaction confirmed before the expire time (minimum 3-4 hours is suggested if the
+ * payment transaction has enough fee to be confirmed in the next block or two).
+ *
+ * To begin, we must provide the client with a pubkey which we wish to use for the multi-sig contract which locks in
+ * the channel. The client will then provide us with an incomplete refund transaction and the pubkey which they used in
+ * the multi-sig contract. We use this pubkey to recreate the multi-sig output and then sign that to the refund
+ * transaction. We provide that signature to the client and they then have the ability to spend the refund transaction
+ * at the specified expire time. The client then provides us with the full, signed multi-sig contract which we verify
+ * and broadcast, locking in their funds until we spend a payment transaction or the expire time is reached. The client
+ * can then begin paying by providing us with signatures for the multi-sig contract which pay some amount back to the
+ * client, and the rest is ours to do with as we wish.
+ */
+public class PaymentChannelServerState {
+ private static final Logger log = LoggerFactory.getLogger(PaymentChannelServerState.class);
+
+ /**
+ * The different logical states the channel can be in. Because the first action we need to track is the client
+ * providing the refund transaction, we begin in WAITING_FOR_REFUND_TRANSACTION. We then step through the states
+ * until READY, at which time the client can increase payment incrementally.
+ */
+ public enum State {
+ WAITING_FOR_REFUND_TRANSACTION,
+ WAITING_FOR_MULTISIG_CONTRACT,
+ WAITING_FOR_MULTISIG_ACCEPTANCE,
+ READY,
+ CLOSING,
+ CLOSED,
+ ERROR,
+ }
+ private State state;
+
+ // The client and server keys for the multi-sig contract
+ // We currently also use the serverKey for payouts, but this is not required
+ private ECKey clientKey, serverKey;
+
+ // Package-local for checkArguments in StoredServerChannel
+ final Wallet wallet;
+
+ // The peer group we will broadcast transactions to
+ private final PeerGroup peerGroup;
+
+ // The multi-sig contract and the output script from it
+ private Transaction multisigContract = null;
+ private Script multisigScript;
+
+ // The last signature the client provided for a payment transaction.
+ private byte[] bestValueSignature;
+
+ // The total value locked into the multi-sig output and the value to us in the last signature the client provided
+ private BigInteger totalValue;
+ private BigInteger bestValueToMe = BigInteger.ZERO;
+ private BigInteger feePaidForPayment;
+
+ // The refund/change transaction output that goes back to the client
+ private TransactionOutput clientOutput;
+ private long refundTransactionUnlockTimeSecs;
+
+ private long minExpireTime;
+
+ private StoredServerChannel storedServerChannel = null;
+
+ PaymentChannelServerState(StoredServerChannel storedServerChannel, Wallet wallet, PeerGroup peerGroup) throws VerificationException {
+ synchronized (storedServerChannel) {
+ this.wallet = checkNotNull(wallet);
+ this.peerGroup = checkNotNull(peerGroup);
+ this.multisigContract = checkNotNull(storedServerChannel.contract);
+ this.multisigScript = multisigContract.getOutput(0).getScriptPubKey();
+ this.clientKey = new ECKey(null, multisigScript.getChunks().get(1).data);
+ this.clientOutput = checkNotNull(storedServerChannel.clientOutput);
+ this.refundTransactionUnlockTimeSecs = storedServerChannel.refundTransactionUnlockTimeSecs;
+ this.serverKey = checkNotNull(storedServerChannel.myKey);
+ this.totalValue = multisigContract.getOutput(0).getValue();
+ this.bestValueToMe = checkNotNull(storedServerChannel.bestValueToMe);
+ this.bestValueSignature = storedServerChannel.bestValueSignature;
+ checkArgument(bestValueToMe.equals(BigInteger.ZERO) || bestValueSignature != null);
+ this.storedServerChannel = storedServerChannel;
+ storedServerChannel.state = this;
+ this.state = State.READY;
+ }
+ }
+
+ /**
+ * Creates a new state object to track the server side of a payment channel.
+ *
+ * @param peerGroup The peer group which we will broadcast transactions to, this should have multiple peers
+ * @param wallet The wallet which will be used to complete transactions
+ * @param serverKey The private key which we use for our part of the multi-sig contract
+ * (this MUST be fresh and CANNOT be used elsewhere)
+ * @param minExpireTime The earliest time at which the client can claim the refund transaction (UNIX timestamp of block)
+ */
+ public PaymentChannelServerState(PeerGroup peerGroup, Wallet wallet, ECKey serverKey, long minExpireTime) {
+ this.state = State.WAITING_FOR_REFUND_TRANSACTION;
+ this.serverKey = checkNotNull(serverKey);
+ this.wallet = checkNotNull(wallet);
+ this.peerGroup = checkNotNull(peerGroup);
+ this.minExpireTime = minExpireTime;
+ }
+
+ /**
+ * This object implements a state machine, and this accessor returns which state it's currently in.
+ */
+ public synchronized State getState() {
+ return state;
+ }
+
+ /**
+ * Called when the client provides the refund transaction.
+ * The refund transaction must have one input from the multisig contract (that we don't have yet) and one output
+ * that the client creates to themselves. This object will later be modified when we start getting paid.
+ *
+ * @param refundTx The refund transaction, this object will be mutated when payment is incremented.
+ * @param clientMultiSigPubKey The client's pubkey which is required for the multisig output
+ * @return Our signature that makes the refund transaction valid
+ * @throws VerificationException If the transaction isnt valid or did not meet the requirements of a refund transaction.
+ */
+ public synchronized byte[] provideRefundTransaction(Transaction refundTx, byte[] clientMultiSigPubKey) throws VerificationException {
+ checkNotNull(refundTx);
+ checkNotNull(clientMultiSigPubKey);
+ checkState(state == State.WAITING_FOR_REFUND_TRANSACTION);
+ log.info("Provided with refund transaction: {}", refundTx);
+ // Do a few very basic syntax sanity checks.
+ refundTx.verify();
+ // Verify that the refund transaction has a single input (that we can fill to sign the multisig output).
+ if (refundTx.getInputs().size() != 1)
+ throw new VerificationException("Refund transaction does not have exactly one input");
+ // Verify that the refund transaction has a time lock on it and a sequence number of zero.
+ if (refundTx.getInput(0).getSequenceNumber() != 0)
+ throw new VerificationException("Refund transaction's input's sequence number is non-0");
+ if (refundTx.getLockTime() < minExpireTime)
+ throw new VerificationException("Refund transaction has a lock time too soon");
+ // Verify the transaction has one output (we don't care about its contents, its up to the client)
+ // Note that because we sign with SIGHASH_NONE|SIGHASH_ANYOENCANPAY the client can later add more outputs and
+ // inputs, but we will need only one output later to create the paying transactions
+ if (refundTx.getOutputs().size() != 1)
+ throw new VerificationException("Refund transaction does not have exactly one output");
+
+ refundTransactionUnlockTimeSecs = refundTx.getLockTime();
+
+ // Sign the refund tx with the scriptPubKey and return the signature. We don't have the spending transaction
+ // so do the steps individually.
+ clientKey = new ECKey(null, clientMultiSigPubKey);
+ Script multisigPubKey = ScriptBuilder.createMultiSigOutputScript(2, ImmutableList.of(clientKey, serverKey));
+ // We are really only signing the fact that the transaction has a proper lock time and don't care about anything
+ // else, so we sign SIGHASH_NONE and SIGHASH_ANYONECANPAY.
+ TransactionSignature sig = refundTx.calculateSignature(0, serverKey, multisigPubKey, Transaction.SigHash.NONE, true);
+ log.info("Signed refund transaction.");
+ this.clientOutput = refundTx.getOutput(0);
+ state = State.WAITING_FOR_MULTISIG_CONTRACT;
+ return sig.encodeToBitcoin();
+ }
+
+ /**
+ * Called when the client provides the multi-sig contract. Checks that the previously-provided refund transaction
+ * spends this transaction (because we will use it as a base to create payment transactions) as well as output value
+ * and form (ie it is a 2-of-2 multisig to the correct keys).
+ *
+ * @param multisigContract The provided multisig contract. Do not mutate this object after this call.
+ * @return A future which completes when the provided multisig contract successfully broadcasts, or throws if the broadcast fails for some reason
+ * Note that if the network simply rejects the transaction, this future will never complete, a timeout should be used.
+ * @throws VerificationException If the provided multisig contract is not well-formed or does not meet previously-specified parameters
+ */
+ public synchronized ListenableFuture provideMultiSigContract(Transaction multisigContract) throws VerificationException {
+ checkNotNull(multisigContract);
+ checkState(state == State.WAITING_FOR_MULTISIG_CONTRACT);
+ try {
+ multisigContract.verify();
+ this.multisigContract = multisigContract;
+ this.multisigScript = multisigContract.getOutput(0).getScriptPubKey();
+
+ // Check that multisigContract's first output is a 2-of-2 multisig to the correct pubkeys in the correct order
+ final Script expectedSecript = ScriptBuilder.createMultiSigOutputScript(2, Lists.newArrayList(clientKey, serverKey));
+ if (!Arrays.equals(multisigScript.getProgram(), expectedSecript.getProgram()))
+ throw new VerificationException("Multisig contract's first output was not a standard 2-of-2 multisig to client and server in that order.");
+
+ this.totalValue = multisigContract.getOutput(0).getValue();
+ if (this.totalValue.compareTo(BigInteger.ZERO) <= 0)
+ throw new VerificationException("Not accepting an attempt to open a contract with zero value.");
+ } catch (VerificationException e) {
+ // We couldn't parse the multisig transaction or its output.
+ log.error("Provided multisig contract did not verify: {}", multisigContract.toString());
+ throw e;
+ }
+ log.info("Broadcasting multisig contract: {}", multisigContract);
+ state = State.WAITING_FOR_MULTISIG_ACCEPTANCE;
+ final SettableFuture future = SettableFuture.create();
+ Futures.addCallback(peerGroup.broadcastTransaction(multisigContract), new FutureCallback() {
+ @Override public void onSuccess(Transaction transaction) {
+ log.info("Successfully broadcast multisig contract {}. Channel now open.", transaction.getHashAsString());
+ state = State.READY;
+ future.set(PaymentChannelServerState.this);
+ }
+
+ @Override public void onFailure(Throwable throwable) {
+ // Couldn't broadcast the transaction for some reason.
+ log.error(throwable.toString());
+ throwable.printStackTrace();
+ state = State.ERROR;
+ future.setException(throwable);
+ }
+ });
+ return future;
+ }
+
+ // Create a payment transaction with valueToMe going back to us
+ private synchronized Wallet.SendRequest makeUnsignedChannelContract(BigInteger valueToMe) {
+ Transaction tx = new Transaction(wallet.getParams());
+ if (!totalValue.subtract(valueToMe).equals(BigInteger.ZERO)) {
+ clientOutput.setValue(totalValue.subtract(valueToMe));
+ tx.addOutput(clientOutput);
+ }
+ tx.addInput(multisigContract.getOutput(0));
+ return Wallet.SendRequest.forTx(tx);
+ }
+
+ /**
+ * Called when the client provides us with a new signature and wishes to increment total payment by size.
+ * Verifies the provided signature and only updates values if everything checks out.
+ * If the new refundSize is not the lowest we have seen, it is simply ignored.
+ *
+ * @param refundSize How many satoshis of the original contract are refunded to the client (the rest are ours)
+ * @param signatureBytes The new signature spending the multi-sig contract to a new payment transaction
+ * @throws VerificationException If the signature does not verify or size is out of range (incl being rejected by the network as dust).
+ */
+ public synchronized void incrementPayment(BigInteger refundSize, byte[] signatureBytes) throws VerificationException, ValueOutOfRangeException {
+ checkState(state == State.READY);
+ checkNotNull(refundSize);
+ checkNotNull(signatureBytes);
+ TransactionSignature signature = TransactionSignature.decodeFromBitcoin(signatureBytes, true);
+ // We allow snapping to zero for the payment amount because it's treated specially later, but not less than
+ // the dust level because that would prevent the transaction from being relayed/mined.
+ final boolean fullyUsedUp = refundSize.equals(BigInteger.ZERO);
+ if (refundSize.compareTo(clientOutput.getMinNonDustValue()) < 0 && !fullyUsedUp)
+ throw new ValueOutOfRangeException("Attempt to refund negative value or value too small to be accepted by the network");
+ BigInteger newValueToMe = totalValue.subtract(refundSize);
+ if (newValueToMe.compareTo(BigInteger.ZERO) < 0)
+ throw new ValueOutOfRangeException("Attempt to refund more than the contract allows.");
+ if (newValueToMe.compareTo(bestValueToMe) < 0)
+ return;
+
+ Transaction.SigHash mode;
+ // If the client doesn't want anything back, they shouldn't sign any outputs at all.
+ if (fullyUsedUp)
+ mode = Transaction.SigHash.NONE;
+ else
+ mode = Transaction.SigHash.SINGLE;
+
+ if (signature.sigHashMode() != mode || !signature.anyoneCanPay())
+ throw new VerificationException("New payment signature was not signed with the right SIGHASH flags.");
+
+ Wallet.SendRequest req = makeUnsignedChannelContract(newValueToMe);
+ // Now check the signature is correct.
+ // Note that the client must sign with SIGHASH_{SINGLE/NONE} | SIGHASH_ANYONECANPAY to allow us to add additional
+ // inputs (in case we need to add significant fee, or something...) and any outputs we want to pay to.
+ Sha256Hash sighash = req.tx.hashForSignature(0, multisigScript, mode, true);
+
+ if (!clientKey.verify(sighash, signature))
+ throw new VerificationException("Signature does not verify on tx\n" + req.tx);
+ bestValueToMe = newValueToMe;
+ bestValueSignature = signatureBytes;
+ updateChannelInWallet();
+ }
+
+ // Signs the first input of the transaction which must spend the multisig contract.
+ private void signMultisigInput(Transaction tx, Transaction.SigHash hashType, boolean anyoneCanPay) {
+ TransactionSignature signature = tx.calculateSignature(0, serverKey, multisigScript, hashType, anyoneCanPay);
+ byte[] mySig = signature.encodeToBitcoin();
+ Script scriptSig = ScriptBuilder.createMultiSigInputScriptBytes(ImmutableList.of(bestValueSignature, mySig));
+ tx.getInput(0).setScriptSig(scriptSig);
+ }
+
+ final SettableFuture closedFuture = SettableFuture.create();
+ /**
+ * Closes this channel and broadcasts the highest value payment transaction on the network.
+ *
+ * This will set the state to {@link State#CLOSED} if the transaction is successfully broadcast on the network.
+ * If we fail to broadcast for some reason, the state is set to {@link State#ERROR}.
+ *
+ * If the current state is before {@link State#READY} (ie we have not finished initializing the channel), we
+ * simply set the state to {@link State#CLOSED} and let the client handle getting its refund transaction confirmed.
+ *
+ *
+ * @return a future which completes when the provided multisig contract successfully broadcasts, or throws if the broadcast fails for some reason
+ * Note that if the network simply rejects the transaction, this future will never complete, a timeout should be used.
+ * @throws ValueOutOfRangeException If the payment transaction would have cost more in fees to spend than it was worth
+ */
+ public synchronized ListenableFuture close() throws ValueOutOfRangeException {
+ if (storedServerChannel != null) {
+ StoredServerChannel temp = storedServerChannel;
+ storedServerChannel = null;
+ StoredPaymentChannelServerStates channels = (StoredPaymentChannelServerStates)
+ wallet.getExtensions().get(StoredPaymentChannelServerStates.EXTENSION_ID);
+ channels.closeChannel(temp); // Calls this method again for us
+ checkState(state.compareTo(State.CLOSING) >= 0);
+ return closedFuture;
+ }
+
+ if (state.ordinal() < State.READY.ordinal()) {
+ state = State.CLOSED;
+ closedFuture.set(this);
+ return closedFuture;
+ }
+ if (state != State.READY) // We are already closing/closed/in an error state
+ return closedFuture;
+
+ if (bestValueToMe.equals(BigInteger.ZERO)) {
+ state = State.CLOSED;
+ closedFuture.set(this);
+ return closedFuture;
+ }
+ Transaction tx = null;
+ try {
+ Wallet.SendRequest req = makeUnsignedChannelContract(bestValueToMe);
+ tx = req.tx;
+ // Provide a BS signature so that completeTx wont freak out about unsigned inputs.
+ signMultisigInput(tx, Transaction.SigHash.NONE, true);
+ if (!wallet.completeTx(req)) // Let wallet handle adding additional inputs/fee as necessary.
+ throw new ValueOutOfRangeException("Unable to complete transaction - unable to pay required fee");
+ feePaidForPayment = req.fee;
+ if (feePaidForPayment.compareTo(bestValueToMe) >= 0)
+ throw new ValueOutOfRangeException("Had to pay more in fees than the channel was worth");
+ // Now really sign the multisig input.
+ signMultisigInput(tx, Transaction.SigHash.ALL, false);
+ // Some checks that shouldn't be necessary but it can't hurt to check.
+ tx.verify(); // Sanity check syntax.
+ for (TransactionInput input : tx.getInputs())
+ input.verify(); // Run scripts and ensure it is valid.
+ } catch (ValueOutOfRangeException e) {
+ throw e; // Don't fall through.
+ } catch (Exception e) {
+ log.error("Could not verify self-built tx\nMULTISIG {}\nCLOSE {}", multisigContract, tx != null ? tx : "");
+ throw new RuntimeException(e); // Should never happen.
+ }
+ state = State.CLOSING;
+ log.info("Closing channel, broadcasting tx {}", tx);
+ // The act of broadcasting the transaction will add it to the wallet.
+ ListenableFuture future = peerGroup.broadcastTransaction(tx);
+ Futures.addCallback(future, new FutureCallback() {
+ @Override public void onSuccess(Transaction transaction) {
+ log.info("TX {} propagated, channel successfully closed.", transaction.getHash());
+ state = State.CLOSED;
+ closedFuture.set(PaymentChannelServerState.this);
+ }
+
+ @Override public void onFailure(Throwable throwable) {
+ log.error("Failed to close channel, could not broadcast: {}", throwable.toString());
+ throwable.printStackTrace();
+ state = State.ERROR;
+ closedFuture.setException(throwable);
+ }
+ });
+ return closedFuture;
+ }
+
+ /**
+ * Gets the highest payment to ourselves (which we will receive on close(), not including fees)
+ */
+ public synchronized BigInteger getBestValueToMe() {
+ return bestValueToMe;
+ }
+
+ /**
+ * Gets the fee paid in the final payment transaction (only available if close() did not throw an exception)
+ */
+ public synchronized BigInteger getFeePaid() {
+ checkState(state == State.CLOSED || state == State.CLOSING);
+ return feePaidForPayment;
+ }
+
+ /**
+ * Gets the multisig contract which was used to initialize this channel
+ */
+ public synchronized Transaction getMultisigContract() {
+ checkState(multisigContract != null);
+ return multisigContract;
+ }
+
+ /**
+ * Gets the client's refund transaction which they can spend to get the entire channel value back if it reaches its
+ * lock time.
+ */
+ public synchronized long getRefundTransactionUnlockTime() {
+ checkState(state.compareTo(State.WAITING_FOR_MULTISIG_CONTRACT) > 0 && state != State.ERROR);
+ return refundTransactionUnlockTimeSecs;
+ }
+
+ private synchronized void updateChannelInWallet() {
+ if (storedServerChannel != null) {
+ storedServerChannel.updateValueToMe(bestValueToMe, bestValueSignature);
+ StoredPaymentChannelServerStates channels = (StoredPaymentChannelServerStates)
+ wallet.getExtensions().get(StoredPaymentChannelServerStates.EXTENSION_ID);
+ wallet.addOrUpdateExtension(channels);
+ }
+ }
+
+ /**
+ * Stores this channel's state in the wallet as a part of a {@link StoredPaymentChannelServerStates} wallet
+ * extension and keeps it up-to-date each time payment is incremented. This will be automatically removed when
+ * a call to {@link PaymentChannelServerState#close()} completes successfully. A channel may only be stored after it
+ * has fully opened (ie state == State.READY).
+ *
+ * @param connectedHandler The {@link PaymentChannelClientState} object which is managing this object. This will
+ * set the appropriate pointer in the newly created {@link StoredServerChannel} before it is
+ * committed to wallet.
+ */
+ public synchronized void storeChannelInWallet(@Nullable PaymentChannelServer connectedHandler) {
+ checkState(state == State.READY);
+ if (storedServerChannel != null)
+ return;
+
+ log.info("Storing state with contract hash {}.", multisigContract.getHash());
+ StoredPaymentChannelServerStates channels = (StoredPaymentChannelServerStates)
+ wallet.addOrGetExistingExtension(new StoredPaymentChannelServerStates(wallet, peerGroup));
+ storedServerChannel = new StoredServerChannel(this, multisigContract, clientOutput, refundTransactionUnlockTimeSecs, serverKey, bestValueToMe, bestValueSignature);
+ checkState(storedServerChannel.setConnectedHandler(connectedHandler));
+ channels.putChannel(storedServerChannel);
+ wallet.addOrUpdateExtension(channels);
+ }
+}
diff --git a/core/src/main/java/com/google/bitcoin/protocols/channels/ServerConnectionEventHandler.java b/core/src/main/java/com/google/bitcoin/protocols/channels/ServerConnectionEventHandler.java
new file mode 100644
index 00000000..bf6acf75
--- /dev/null
+++ b/core/src/main/java/com/google/bitcoin/protocols/channels/ServerConnectionEventHandler.java
@@ -0,0 +1,77 @@
+/*
+ * 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 com.google.bitcoin.protocols.channels;
+
+import java.math.BigInteger;
+
+import com.google.bitcoin.core.Sha256Hash;
+import com.google.bitcoin.protocols.niowrapper.ProtobufParser;
+import org.bitcoin.paymentchannel.Protos;
+
+/**
+* A connection-specific event handler that handles events generated by client connections on a {@link PaymentChannelServerListener}
+*/
+public abstract class ServerConnectionEventHandler {
+ private ProtobufParser connectionChannel;
+ // Called by ServerListener before channelOpen to set connectionChannel when it is ready to received application messages
+ // Also called with null to clear connectionChannel after channelClosed()
+ synchronized void setConnectionChannel(ProtobufParser connectionChannel) { this.connectionChannel = connectionChannel; }
+
+ /**
+ * Closes the channel with the client (will generate a
+ * {@link ServerConnectionEventHandler#channelClosed(PaymentChannelCloseException.CloseReason)} event)
+ *
+ * Note that this does NOT actually broadcast the most recent payment transaction, which will be triggered
+ * automatically when the channel times out by the {@link StoredPaymentChannelServerStates}, or manually by calling
+ * {@link StoredPaymentChannelServerStates#closeChannel(StoredServerChannel)} with the channel returned by
+ * {@link StoredPaymentChannelServerStates#getChannel(com.google.bitcoin.core.Sha256Hash)} with the id provided in
+ * {@link ServerConnectionEventHandler#channelOpen(com.google.bitcoin.core.Sha256Hash)}
+ */
+ protected final synchronized void closeChannel() {
+ if (connectionChannel == null)
+ throw new IllegalStateException("Channel is not fully initialized/has already been closed");
+
+ connectionChannel.write(Protos.TwoWayChannelMessage.newBuilder()
+ .setType(Protos.TwoWayChannelMessage.MessageType.CLOSE)
+ .build());
+ connectionChannel.closeConnection();
+ }
+
+ /**
+ * Triggered when the channel is opened and application messages/payments can begin
+ *
+ * @param channelId A unique identifier which represents this channel (actually the hash of the multisig contract)
+ */
+ public abstract void channelOpen(Sha256Hash channelId);
+
+ /**
+ * Called when the payment in this channel was successfully incremented by the client
+ *
+ * @param by The increase in total payment
+ * @param to The new total payment to us (not including fees which may be required to claim the payment)
+ */
+ public abstract void paymentIncrease(BigInteger by, BigInteger to);
+
+ /**
+ * Called when the channel was closed for some reason. May be called without a call to
+ * {@link ServerConnectionEventHandler#channelOpen(Sha256Hash)}.
+ *
+ * Note that the same channel can be reopened at any point before it expires if the client reconnects and
+ * requests it.
+ */
+ public abstract void channelClosed(PaymentChannelCloseException.CloseReason reason);
+}
diff --git a/core/src/main/java/com/google/bitcoin/protocols/channels/Simple channels protocol.txt b/core/src/main/java/com/google/bitcoin/protocols/channels/Simple channels protocol.txt
new file mode 100644
index 00000000..af89a6a6
--- /dev/null
+++ b/core/src/main/java/com/google/bitcoin/protocols/channels/Simple channels protocol.txt
@@ -0,0 +1,26 @@
+Using the protocol suggestion by Jeremy Spillman
+
+1) Client connects to server and asks for a public key.
+2) Server provides a fresh key. Client creates TX1 which pays to a 2-of-2 multisig output. It creates an invalid
+ TX2 which spends TX1 and pays all money back to itself. The refund TX is time locked.
+3) Client sends TX2 to server which verifies that it's valid and not connected to any transaction in its wallet.
+ Server signs TX2 and sends back the signature.
+4) Client verifies that the server signed TX2 correctly and then sends TX1 to the server, which verifies that it
+ was the tx connected to the thing it just signed, and then broadcasts it thus locking in the money.
+5) Each time the channel is adjusted, the client sends a new signed TX2 to the server which keeps it (does not need
+ to sign itself).
+
+If the client or server wants to close the channel, the last TX2 is broadcast. It's a normal, final transaction so
+it ends the negotiation at that point.
+
+If the server goes away and does not finalize the channel properly, the refund TX can be broadcast once the time lock
+expires. Note that you cannot broadcast the refund tx before the time lock expires (thus filling the mempool) due to
+the recent change to change non-final transactions non-standard. Thus TX replacement is not needed in this particular
+configuration.
+
+When TX replacement is re-activated, this configuration would become vulnerable to having the refund TX be broadcast
+by the client. We can require the refund TX to have an input sequence number of zero. The adjustment transactions have
+a sequence number of UINT_MAX as before, this means they would replace the refund tx if it were to be broadcast.
+
+This configuration is less general than a full payment channel with tx replacement activated, but for our purposes
+it does the trick.
diff --git a/core/src/main/java/com/google/bitcoin/protocols/channels/StoredPaymentChannelClientStates.java b/core/src/main/java/com/google/bitcoin/protocols/channels/StoredPaymentChannelClientStates.java
new file mode 100644
index 00000000..569b2b44
--- /dev/null
+++ b/core/src/main/java/com/google/bitcoin/protocols/channels/StoredPaymentChannelClientStates.java
@@ -0,0 +1,185 @@
+/*
+ * 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 com.google.bitcoin.protocols.channels;
+
+import java.io.*;
+import java.math.BigInteger;
+import java.util.Date;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import com.google.bitcoin.core.*;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.HashMultimap;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * This class maintains a set of {@link StoredClientChannel}s, automatically (re)broadcasting the contract transaction
+ * and broadcasting the refund transaction over the given {@link TransactionBroadcaster}.
+ */
+public class StoredPaymentChannelClientStates implements WalletExtension {
+ static final String EXTENSION_ID = StoredPaymentChannelClientStates.class.getName();
+
+ @VisibleForTesting final HashMultimap mapChannels = HashMultimap.create();
+ @VisibleForTesting final Timer channelTimeoutHandler = new Timer();
+
+ private Wallet containingWallet;
+ private final TransactionBroadcaster announcePeerGroup;
+
+ /**
+ * Creates a new StoredPaymentChannelClientStates and associates it with the given {@link Wallet} and
+ * {@link TransactionBroadcaster} which are used to complete and announce contract and refund
+ * transactions.
+ */
+ public StoredPaymentChannelClientStates(TransactionBroadcaster announcePeerGroup, Wallet containingWallet) {
+ this.announcePeerGroup = checkNotNull(announcePeerGroup);
+ this.containingWallet = checkNotNull(containingWallet);
+ }
+
+ /**
+ * Finds an inactive channel with the given id and returns it, or returns null.
+ */
+ public synchronized StoredClientChannel getInactiveChannelById(Sha256Hash id) {
+ Set setChannels = mapChannels.get(id);
+ for (StoredClientChannel channel : setChannels) {
+ synchronized (channel) {
+ if (!channel.active) {
+ channel.active = true;
+ return channel;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Finds a channel with the given id and contract hash and returns it, or returns null.
+ */
+ public synchronized StoredClientChannel getChannel(Sha256Hash id, Sha256Hash contractHash) {
+ Set setChannels = mapChannels.get(id);
+ for (StoredClientChannel channel : setChannels) {
+ if (channel.contract.getHash().equals(contractHash))
+ return channel;
+ }
+ return null;
+ }
+
+ /**
+ * Adds the given channel to this set of stored states, broadcasting the contract and refund transactions when the
+ * channel expires and notifies the wallet of an update to this wallet extension
+ */
+ public void putChannel(final StoredClientChannel channel) {
+ putChannel(channel, true);
+ }
+
+ // Adds this channel and optionally notifies the wallet of an update to this extension (used during deserialize)
+ private synchronized void putChannel(final StoredClientChannel channel, boolean updateWallet) {
+ mapChannels.put(channel.id, channel);
+ channelTimeoutHandler.schedule(new TimerTask() {
+ @Override
+ public void run() {
+ removeChannel(channel);
+ announcePeerGroup.broadcastTransaction(channel.contract);
+ announcePeerGroup.broadcastTransaction(channel.refund);
+ }
+ // Add the difference between real time and Utils.now() so that test-cases can use a mock clock.
+ }, new Date((channel.refund.getLockTime() + 60 * 5) * 1000 + (System.currentTimeMillis() - Utils.now().getTime())));
+ if (updateWallet)
+ containingWallet.addOrUpdateExtension(this);
+ }
+
+ /**
+ * Removes the channel with the given id from this set of stored states and notifies the wallet of an update to
+ * this wallet extension.
+ *
+ * Note that the channel will still have its contract and refund transactions broadcast via the connected
+ * {@link TransactionBroadcaster} as long as this {@link StoredPaymentChannelClientStates} continues to
+ * exist in memory.
+ */
+ public synchronized void removeChannel(StoredClientChannel channel) {
+ mapChannels.remove(channel.id, channel);
+ containingWallet.addOrUpdateExtension(this);
+ }
+
+ @Override
+ public String getWalletExtensionID() {
+ return EXTENSION_ID;
+ }
+
+ @Override
+ public boolean isWalletExtensionMandatory() {
+ return false;
+ }
+
+ @Override
+ public synchronized byte[] serializeWalletExtension() {
+ try {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(out);
+ for (StoredClientChannel channel : mapChannels.values()) {
+ oos.writeObject(channel);
+ }
+ return out.toByteArray();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public synchronized void deserializeWalletExtension(Wallet containingWallet, byte[] data) throws Exception {
+ checkState(this.containingWallet == null || this.containingWallet == containingWallet);
+ this.containingWallet = containingWallet;
+ ByteArrayInputStream inStream = new ByteArrayInputStream(data);
+ ObjectInputStream ois = new ObjectInputStream(inStream);
+ while (inStream.available() > 0) {
+ StoredClientChannel channel = (StoredClientChannel)ois.readObject();
+ putChannel(channel, false);
+ }
+ }
+}
+
+/**
+ * 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 keep track of refund transactions which need broadcast
+ * when they expire.
+ */
+class StoredClientChannel implements Serializable {
+ Sha256Hash id;
+ Transaction contract, refund;
+ ECKey myKey;
+ BigInteger valueToMe, refundFees;
+
+ // In-memory flag to indicate intent to resume this channel (or that the channel is already in use)
+ transient boolean active = false;
+
+ StoredClientChannel(Sha256Hash id, Transaction contract, Transaction refund, ECKey myKey, BigInteger valueToMe, BigInteger refundFees) {
+ this.id = id;
+ this.contract = contract;
+ this.refund = refund;
+ this.myKey = myKey;
+ this.valueToMe = valueToMe;
+ this.refundFees = refundFees;
+ this.active = true;
+ }
+
+ void updateValueToMe(BigInteger newValue) {
+ this.valueToMe = newValue;
+ }
+}
diff --git a/core/src/main/java/com/google/bitcoin/protocols/channels/StoredPaymentChannelServerStates.java b/core/src/main/java/com/google/bitcoin/protocols/channels/StoredPaymentChannelServerStates.java
new file mode 100644
index 00000000..fef8d755
--- /dev/null
+++ b/core/src/main/java/com/google/bitcoin/protocols/channels/StoredPaymentChannelServerStates.java
@@ -0,0 +1,144 @@
+/*
+ * 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 com.google.bitcoin.protocols.channels;
+
+import java.io.*;
+import java.util.*;
+
+import com.google.bitcoin.core.*;
+import com.google.common.annotations.VisibleForTesting;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Keeps track of a set of {@link StoredServerChannel}s and expires them 2 hours before their refund transactions
+ * unlock.
+ */
+public class StoredPaymentChannelServerStates implements WalletExtension {
+ static final String EXTENSION_ID = StoredPaymentChannelServerStates.class.getName();
+
+ @VisibleForTesting final Map mapChannels = new HashMap();
+ private final Wallet wallet;
+ private final PeerGroup announcePeerGroup;
+
+ private final Timer channelTimeoutHandler = new Timer();
+
+ /**
+ * The offset between the refund transaction's lock time and the time channels will be automatically closed.
+ * This defines a window during which we must get the last payment transaction verified, ie it should allow time for
+ * network propagation and for the payment transaction to be included in a block. Note that the channel expire time
+ * is measured in terms of our local clock, and the refund transaction's lock time is measured in terms of Bitcoin
+ * block header timestamps, which are allowed to drift up to two hours in the future, as measured by relaying nodes.
+ */
+ public static final long CHANNEL_EXPIRE_OFFSET = -2*60*60;
+
+ /**
+ * Creates a new PaymentChannelServerStateManager and associates it with the given {@link Wallet} and
+ * {@link PeerGroup} which are used to complete and announce payment transactions.
+ */
+ public StoredPaymentChannelServerStates(Wallet wallet, PeerGroup announcePeerGroup) {
+ this.wallet = checkNotNull(wallet);
+ this.announcePeerGroup = checkNotNull(announcePeerGroup);
+ }
+
+ /**
+ * Closes the given channel using {@link ServerConnectionEventHandler#closeChannel()} and
+ * {@link PaymentChannelServerState#close()} to notify any connected client of channel closure and to complete and
+ * broadcast the latest payment transaction.
+ *
+ * Removes the given channel from this set of {@link StoredServerChannel}s and notifies the wallet of a change to
+ * this wallet extension.
+ */
+ public synchronized void closeChannel(StoredServerChannel channel) {
+ synchronized (channel) {
+ if (channel.connectedHandler != null)
+ channel.connectedHandler.close(); // connectedHandler will be reset to null in connectionClosed
+ try {//TODO add event listener to PaymentChannelServerStateManager
+ channel.getState(wallet, announcePeerGroup).close(); // Closes the actual connection, not the channel
+ } catch (ValueOutOfRangeException e) {
+ e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
+ } catch (VerificationException e) {
+ e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
+ }
+ channel.state = null;
+ mapChannels.remove(channel.contract.getHash());
+ }
+ wallet.addOrUpdateExtension(this);
+ }
+
+ /**
+ * Gets the {@link StoredServerChannel} with the given channel id (ie contract transaction hash).
+ */
+ public synchronized StoredServerChannel getChannel(Sha256Hash id) {
+ return mapChannels.get(id);
+ }
+
+ /**
+ * Puts the given channel in the channels map and automatically closes it 2 hours before its refund transaction
+ * becomes spendable.
+ *
+ * Because there must be only one, canonical {@link StoredServerChannel} per channel, this method throws if the
+ * channel is already present in the set of channels.
+ */
+ public synchronized void putChannel(final StoredServerChannel channel) {
+ checkArgument(mapChannels.put(channel.contract.getHash(), checkNotNull(channel)) == null);
+ channelTimeoutHandler.schedule(new TimerTask() {
+ @Override
+ public void run() {
+ closeChannel(channel);
+ }
+ // Add the difference between real time and Utils.now() so that test-cases can use a mock clock.
+ }, new Date((channel.refundTransactionUnlockTimeSecs + CHANNEL_EXPIRE_OFFSET)*1000L
+ + (System.currentTimeMillis() - Utils.now().getTime())));
+ }
+
+ @Override
+ public String getWalletExtensionID() {
+ return EXTENSION_ID;
+ }
+
+ @Override
+ public boolean isWalletExtensionMandatory() {
+ return false;
+ }
+
+ @Override
+ public synchronized byte[] serializeWalletExtension() {
+ try {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(out);
+ for (StoredServerChannel channel : mapChannels.values()) {
+ oos.writeObject(channel);
+ }
+ return out.toByteArray();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public synchronized void deserializeWalletExtension(Wallet containingWallet, byte[] data) throws Exception {
+ checkArgument(containingWallet == wallet);
+ ByteArrayInputStream inStream = new ByteArrayInputStream(data);
+ ObjectInputStream ois = new ObjectInputStream(inStream);
+ while (inStream.available() > 0) {
+ StoredServerChannel channel = (StoredServerChannel)ois.readObject();
+ putChannel(channel);
+ }
+ }
+}
diff --git a/core/src/main/java/com/google/bitcoin/protocols/channels/StoredServerChannel.java b/core/src/main/java/com/google/bitcoin/protocols/channels/StoredServerChannel.java
new file mode 100644
index 00000000..239c9707
--- /dev/null
+++ b/core/src/main/java/com/google/bitcoin/protocols/channels/StoredServerChannel.java
@@ -0,0 +1,89 @@
+/*
+ * 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 com.google.bitcoin.protocols.channels;
+
+import java.io.Serializable;
+import java.math.BigInteger;
+
+import com.google.bitcoin.core.*;
+
+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 close the channel automatically as the channel expire
+ * time approaches.
+ */
+public class StoredServerChannel implements Serializable {
+ BigInteger bestValueToMe;
+ byte[] bestValueSignature;
+ long refundTransactionUnlockTimeSecs;
+ Transaction contract;
+ TransactionOutput clientOutput;
+ ECKey myKey;
+
+ // 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.
+ transient PaymentChannelServer connectedHandler = null;
+ transient PaymentChannelServerState state = null;
+
+ StoredServerChannel(PaymentChannelServerState state, Transaction contract, TransactionOutput clientOutput,
+ long refundTransactionUnlockTimeSecs, ECKey myKey, BigInteger bestValueToMe, byte[] bestValueSignature) {
+ this.contract = contract;
+ this.clientOutput = clientOutput;
+ this.refundTransactionUnlockTimeSecs = refundTransactionUnlockTimeSecs;
+ this.myKey = myKey;
+ this.bestValueToMe = bestValueToMe;
+ this.bestValueSignature = bestValueSignature;
+ this.state = state;
+ }
+
+ /**
+ * Updates the best value to the server to the given newValue and newSignature without any checking.
+ * Does NOT notify the wallet of an update to the {@link com.google.bitcoin.protocols.channels.StoredPaymentChannelServerStates}.
+ */
+ public synchronized void updateValueToMe(BigInteger 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. A null connectedHandler clears this's connected handler no matter its current state.
+ */
+ synchronized boolean setConnectedHandler(PaymentChannelServer connectedHandler) {
+ if (this.connectedHandler != null && connectedHandler != null)
+ return false;
+ this.connectedHandler = connectedHandler;
+ return true;
+ }
+
+ /**
+ * Gets the canonical {@link com.google.bitcoin.protocols.channels.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 com.google.bitcoin.protocols.channels.PaymentChannelServerState} in which this is saved and which will
+ * be used to complete transactions
+ * @param peerGroup The {@link com.google.bitcoin.core.PeerGroup} which will be used to broadcast contract/payment transactions.
+ */
+ public synchronized PaymentChannelServerState getState(Wallet wallet, PeerGroup peerGroup) throws VerificationException {
+ if (state == null)
+ state = new PaymentChannelServerState(this, wallet, peerGroup);
+ checkArgument(wallet == state.wallet);
+ return state;
+ }
+}
diff --git a/core/src/main/java/com/google/bitcoin/protocols/channels/ValueOutOfRangeException.java b/core/src/main/java/com/google/bitcoin/protocols/channels/ValueOutOfRangeException.java
new file mode 100644
index 00000000..8095dffd
--- /dev/null
+++ b/core/src/main/java/com/google/bitcoin/protocols/channels/ValueOutOfRangeException.java
@@ -0,0 +1,10 @@
+package com.google.bitcoin.protocols.channels;
+
+/**
+ * Used when a given value is either too large too afford or too small for the network to accept.
+ */
+public class ValueOutOfRangeException extends Exception {
+ public ValueOutOfRangeException(String message) {
+ super(message);
+ }
+}
diff --git a/core/src/main/java/org/bitcoin/paymentchannel/Protos.java b/core/src/main/java/org/bitcoin/paymentchannel/Protos.java
new file mode 100644
index 00000000..439ba1da
--- /dev/null
+++ b/core/src/main/java/org/bitcoin/paymentchannel/Protos.java
@@ -0,0 +1,5265 @@
+// Generated by the protocol buffer compiler. DO NOT EDIT!
+// source: paymentchannel.proto
+
+package org.bitcoin.paymentchannel;
+
+public final class Protos {
+ private Protos() {}
+ public static void registerAllExtensions(
+ com.google.protobuf.ExtensionRegistry registry) {
+ }
+ public interface TwoWayChannelMessageOrBuilder
+ extends com.google.protobuf.MessageOrBuilder {
+
+ // required .paymentchannels.TwoWayChannelMessage.MessageType type = 1;
+ boolean hasType();
+ org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage.MessageType getType();
+
+ // optional .paymentchannels.ClientVersion client_version = 2;
+ boolean hasClientVersion();
+ org.bitcoin.paymentchannel.Protos.ClientVersion getClientVersion();
+ org.bitcoin.paymentchannel.Protos.ClientVersionOrBuilder getClientVersionOrBuilder();
+
+ // optional .paymentchannels.ServerVersion server_version = 3;
+ boolean hasServerVersion();
+ org.bitcoin.paymentchannel.Protos.ServerVersion getServerVersion();
+ org.bitcoin.paymentchannel.Protos.ServerVersionOrBuilder getServerVersionOrBuilder();
+
+ // optional .paymentchannels.Initiate initiate = 4;
+ boolean hasInitiate();
+ org.bitcoin.paymentchannel.Protos.Initiate getInitiate();
+ org.bitcoin.paymentchannel.Protos.InitiateOrBuilder getInitiateOrBuilder();
+
+ // optional .paymentchannels.ProvideRefund provide_refund = 5;
+ boolean hasProvideRefund();
+ org.bitcoin.paymentchannel.Protos.ProvideRefund getProvideRefund();
+ org.bitcoin.paymentchannel.Protos.ProvideRefundOrBuilder getProvideRefundOrBuilder();
+
+ // optional .paymentchannels.ReturnRefund return_refund = 6;
+ boolean hasReturnRefund();
+ org.bitcoin.paymentchannel.Protos.ReturnRefund getReturnRefund();
+ org.bitcoin.paymentchannel.Protos.ReturnRefundOrBuilder getReturnRefundOrBuilder();
+
+ // optional .paymentchannels.ProvideContract provide_contract = 7;
+ boolean hasProvideContract();
+ org.bitcoin.paymentchannel.Protos.ProvideContract getProvideContract();
+ org.bitcoin.paymentchannel.Protos.ProvideContractOrBuilder getProvideContractOrBuilder();
+
+ // optional .paymentchannels.UpdatePayment update_payment = 8;
+ boolean hasUpdatePayment();
+ org.bitcoin.paymentchannel.Protos.UpdatePayment getUpdatePayment();
+ org.bitcoin.paymentchannel.Protos.UpdatePaymentOrBuilder getUpdatePaymentOrBuilder();
+
+ // optional .paymentchannels.Error error = 10;
+ boolean hasError();
+ org.bitcoin.paymentchannel.Protos.Error getError();
+ org.bitcoin.paymentchannel.Protos.ErrorOrBuilder getErrorOrBuilder();
+ }
+ public static final class TwoWayChannelMessage extends
+ com.google.protobuf.GeneratedMessage
+ implements TwoWayChannelMessageOrBuilder {
+ // Use TwoWayChannelMessage.newBuilder() to construct.
+ private TwoWayChannelMessage(Builder builder) {
+ super(builder);
+ }
+ private TwoWayChannelMessage(boolean noInit) {}
+
+ private static final TwoWayChannelMessage defaultInstance;
+ public static TwoWayChannelMessage getDefaultInstance() {
+ return defaultInstance;
+ }
+
+ public TwoWayChannelMessage getDefaultInstanceForType() {
+ return defaultInstance;
+ }
+
+ public static final com.google.protobuf.Descriptors.Descriptor
+ getDescriptor() {
+ return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_TwoWayChannelMessage_descriptor;
+ }
+
+ protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
+ internalGetFieldAccessorTable() {
+ return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_TwoWayChannelMessage_fieldAccessorTable;
+ }
+
+ public enum MessageType
+ implements com.google.protobuf.ProtocolMessageEnum {
+ CLIENT_VERSION(0, 1),
+ SERVER_VERSION(1, 2),
+ INITIATE(2, 3),
+ PROVIDE_REFUND(3, 4),
+ RETURN_REFUND(4, 5),
+ PROVIDE_CONTRACT(5, 6),
+ CHANNEL_OPEN(6, 7),
+ UPDATE_PAYMENT(7, 8),
+ CLOSE(8, 9),
+ ERROR(9, 10),
+ ;
+
+ public static final int CLIENT_VERSION_VALUE = 1;
+ public static final int SERVER_VERSION_VALUE = 2;
+ public static final int INITIATE_VALUE = 3;
+ public static final int PROVIDE_REFUND_VALUE = 4;
+ public static final int RETURN_REFUND_VALUE = 5;
+ public static final int PROVIDE_CONTRACT_VALUE = 6;
+ public static final int CHANNEL_OPEN_VALUE = 7;
+ public static final int UPDATE_PAYMENT_VALUE = 8;
+ public static final int CLOSE_VALUE = 9;
+ public static final int ERROR_VALUE = 10;
+
+
+ public final int getNumber() { return value; }
+
+ public static MessageType valueOf(int value) {
+ switch (value) {
+ case 1: return CLIENT_VERSION;
+ case 2: return SERVER_VERSION;
+ case 3: return INITIATE;
+ case 4: return PROVIDE_REFUND;
+ case 5: return RETURN_REFUND;
+ case 6: return PROVIDE_CONTRACT;
+ case 7: return CHANNEL_OPEN;
+ case 8: return UPDATE_PAYMENT;
+ case 9: return CLOSE;
+ case 10: return ERROR;
+ default: return null;
+ }
+ }
+
+ public static com.google.protobuf.Internal.EnumLiteMap
+ internalGetValueMap() {
+ return internalValueMap;
+ }
+ private static com.google.protobuf.Internal.EnumLiteMap
+ internalValueMap =
+ new com.google.protobuf.Internal.EnumLiteMap() {
+ public MessageType findValueByNumber(int number) {
+ return MessageType.valueOf(number);
+ }
+ };
+
+ public final com.google.protobuf.Descriptors.EnumValueDescriptor
+ getValueDescriptor() {
+ return getDescriptor().getValues().get(index);
+ }
+ public final com.google.protobuf.Descriptors.EnumDescriptor
+ getDescriptorForType() {
+ return getDescriptor();
+ }
+ public static final com.google.protobuf.Descriptors.EnumDescriptor
+ getDescriptor() {
+ return org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage.getDescriptor().getEnumTypes().get(0);
+ }
+
+ private static final MessageType[] VALUES = {
+ CLIENT_VERSION, SERVER_VERSION, INITIATE, PROVIDE_REFUND, RETURN_REFUND, PROVIDE_CONTRACT, CHANNEL_OPEN, UPDATE_PAYMENT, CLOSE, ERROR,
+ };
+
+ public static MessageType valueOf(
+ com.google.protobuf.Descriptors.EnumValueDescriptor desc) {
+ if (desc.getType() != getDescriptor()) {
+ throw new java.lang.IllegalArgumentException(
+ "EnumValueDescriptor is not for this type.");
+ }
+ return VALUES[desc.getIndex()];
+ }
+
+ private final int index;
+ private final int value;
+
+ private MessageType(int index, int value) {
+ this.index = index;
+ this.value = value;
+ }
+
+ // @@protoc_insertion_point(enum_scope:paymentchannels.TwoWayChannelMessage.MessageType)
+ }
+
+ private int bitField0_;
+ // required .paymentchannels.TwoWayChannelMessage.MessageType type = 1;
+ public static final int TYPE_FIELD_NUMBER = 1;
+ private org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage.MessageType type_;
+ public boolean hasType() {
+ return ((bitField0_ & 0x00000001) == 0x00000001);
+ }
+ public org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage.MessageType getType() {
+ return type_;
+ }
+
+ // optional .paymentchannels.ClientVersion client_version = 2;
+ public static final int CLIENT_VERSION_FIELD_NUMBER = 2;
+ private org.bitcoin.paymentchannel.Protos.ClientVersion clientVersion_;
+ public boolean hasClientVersion() {
+ return ((bitField0_ & 0x00000002) == 0x00000002);
+ }
+ public org.bitcoin.paymentchannel.Protos.ClientVersion getClientVersion() {
+ return clientVersion_;
+ }
+ public org.bitcoin.paymentchannel.Protos.ClientVersionOrBuilder getClientVersionOrBuilder() {
+ return clientVersion_;
+ }
+
+ // optional .paymentchannels.ServerVersion server_version = 3;
+ public static final int SERVER_VERSION_FIELD_NUMBER = 3;
+ private org.bitcoin.paymentchannel.Protos.ServerVersion serverVersion_;
+ public boolean hasServerVersion() {
+ return ((bitField0_ & 0x00000004) == 0x00000004);
+ }
+ public org.bitcoin.paymentchannel.Protos.ServerVersion getServerVersion() {
+ return serverVersion_;
+ }
+ public org.bitcoin.paymentchannel.Protos.ServerVersionOrBuilder getServerVersionOrBuilder() {
+ return serverVersion_;
+ }
+
+ // optional .paymentchannels.Initiate initiate = 4;
+ public static final int INITIATE_FIELD_NUMBER = 4;
+ private org.bitcoin.paymentchannel.Protos.Initiate initiate_;
+ public boolean hasInitiate() {
+ return ((bitField0_ & 0x00000008) == 0x00000008);
+ }
+ public org.bitcoin.paymentchannel.Protos.Initiate getInitiate() {
+ return initiate_;
+ }
+ public org.bitcoin.paymentchannel.Protos.InitiateOrBuilder getInitiateOrBuilder() {
+ return initiate_;
+ }
+
+ // optional .paymentchannels.ProvideRefund provide_refund = 5;
+ public static final int PROVIDE_REFUND_FIELD_NUMBER = 5;
+ private org.bitcoin.paymentchannel.Protos.ProvideRefund provideRefund_;
+ public boolean hasProvideRefund() {
+ return ((bitField0_ & 0x00000010) == 0x00000010);
+ }
+ public org.bitcoin.paymentchannel.Protos.ProvideRefund getProvideRefund() {
+ return provideRefund_;
+ }
+ public org.bitcoin.paymentchannel.Protos.ProvideRefundOrBuilder getProvideRefundOrBuilder() {
+ return provideRefund_;
+ }
+
+ // optional .paymentchannels.ReturnRefund return_refund = 6;
+ public static final int RETURN_REFUND_FIELD_NUMBER = 6;
+ private org.bitcoin.paymentchannel.Protos.ReturnRefund returnRefund_;
+ public boolean hasReturnRefund() {
+ return ((bitField0_ & 0x00000020) == 0x00000020);
+ }
+ public org.bitcoin.paymentchannel.Protos.ReturnRefund getReturnRefund() {
+ return returnRefund_;
+ }
+ public org.bitcoin.paymentchannel.Protos.ReturnRefundOrBuilder getReturnRefundOrBuilder() {
+ return returnRefund_;
+ }
+
+ // optional .paymentchannels.ProvideContract provide_contract = 7;
+ public static final int PROVIDE_CONTRACT_FIELD_NUMBER = 7;
+ private org.bitcoin.paymentchannel.Protos.ProvideContract provideContract_;
+ public boolean hasProvideContract() {
+ return ((bitField0_ & 0x00000040) == 0x00000040);
+ }
+ public org.bitcoin.paymentchannel.Protos.ProvideContract getProvideContract() {
+ return provideContract_;
+ }
+ public org.bitcoin.paymentchannel.Protos.ProvideContractOrBuilder getProvideContractOrBuilder() {
+ return provideContract_;
+ }
+
+ // optional .paymentchannels.UpdatePayment update_payment = 8;
+ public static final int UPDATE_PAYMENT_FIELD_NUMBER = 8;
+ private org.bitcoin.paymentchannel.Protos.UpdatePayment updatePayment_;
+ public boolean hasUpdatePayment() {
+ return ((bitField0_ & 0x00000080) == 0x00000080);
+ }
+ public org.bitcoin.paymentchannel.Protos.UpdatePayment getUpdatePayment() {
+ return updatePayment_;
+ }
+ public org.bitcoin.paymentchannel.Protos.UpdatePaymentOrBuilder getUpdatePaymentOrBuilder() {
+ return updatePayment_;
+ }
+
+ // optional .paymentchannels.Error error = 10;
+ public static final int ERROR_FIELD_NUMBER = 10;
+ private org.bitcoin.paymentchannel.Protos.Error error_;
+ public boolean hasError() {
+ return ((bitField0_ & 0x00000100) == 0x00000100);
+ }
+ public org.bitcoin.paymentchannel.Protos.Error getError() {
+ return error_;
+ }
+ public org.bitcoin.paymentchannel.Protos.ErrorOrBuilder getErrorOrBuilder() {
+ return error_;
+ }
+
+ private void initFields() {
+ type_ = org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage.MessageType.CLIENT_VERSION;
+ clientVersion_ = org.bitcoin.paymentchannel.Protos.ClientVersion.getDefaultInstance();
+ serverVersion_ = org.bitcoin.paymentchannel.Protos.ServerVersion.getDefaultInstance();
+ initiate_ = org.bitcoin.paymentchannel.Protos.Initiate.getDefaultInstance();
+ provideRefund_ = org.bitcoin.paymentchannel.Protos.ProvideRefund.getDefaultInstance();
+ returnRefund_ = org.bitcoin.paymentchannel.Protos.ReturnRefund.getDefaultInstance();
+ provideContract_ = org.bitcoin.paymentchannel.Protos.ProvideContract.getDefaultInstance();
+ updatePayment_ = org.bitcoin.paymentchannel.Protos.UpdatePayment.getDefaultInstance();
+ error_ = org.bitcoin.paymentchannel.Protos.Error.getDefaultInstance();
+ }
+ private byte memoizedIsInitialized = -1;
+ public final boolean isInitialized() {
+ byte isInitialized = memoizedIsInitialized;
+ if (isInitialized != -1) return isInitialized == 1;
+
+ if (!hasType()) {
+ memoizedIsInitialized = 0;
+ return false;
+ }
+ if (hasClientVersion()) {
+ if (!getClientVersion().isInitialized()) {
+ memoizedIsInitialized = 0;
+ return false;
+ }
+ }
+ if (hasServerVersion()) {
+ if (!getServerVersion().isInitialized()) {
+ memoizedIsInitialized = 0;
+ return false;
+ }
+ }
+ if (hasInitiate()) {
+ if (!getInitiate().isInitialized()) {
+ memoizedIsInitialized = 0;
+ return false;
+ }
+ }
+ if (hasProvideRefund()) {
+ if (!getProvideRefund().isInitialized()) {
+ memoizedIsInitialized = 0;
+ return false;
+ }
+ }
+ if (hasReturnRefund()) {
+ if (!getReturnRefund().isInitialized()) {
+ memoizedIsInitialized = 0;
+ return false;
+ }
+ }
+ if (hasProvideContract()) {
+ if (!getProvideContract().isInitialized()) {
+ memoizedIsInitialized = 0;
+ return false;
+ }
+ }
+ if (hasUpdatePayment()) {
+ if (!getUpdatePayment().isInitialized()) {
+ memoizedIsInitialized = 0;
+ return false;
+ }
+ }
+ memoizedIsInitialized = 1;
+ return true;
+ }
+
+ public void writeTo(com.google.protobuf.CodedOutputStream output)
+ throws java.io.IOException {
+ getSerializedSize();
+ if (((bitField0_ & 0x00000001) == 0x00000001)) {
+ output.writeEnum(1, type_.getNumber());
+ }
+ if (((bitField0_ & 0x00000002) == 0x00000002)) {
+ output.writeMessage(2, clientVersion_);
+ }
+ if (((bitField0_ & 0x00000004) == 0x00000004)) {
+ output.writeMessage(3, serverVersion_);
+ }
+ if (((bitField0_ & 0x00000008) == 0x00000008)) {
+ output.writeMessage(4, initiate_);
+ }
+ if (((bitField0_ & 0x00000010) == 0x00000010)) {
+ output.writeMessage(5, provideRefund_);
+ }
+ if (((bitField0_ & 0x00000020) == 0x00000020)) {
+ output.writeMessage(6, returnRefund_);
+ }
+ if (((bitField0_ & 0x00000040) == 0x00000040)) {
+ output.writeMessage(7, provideContract_);
+ }
+ if (((bitField0_ & 0x00000080) == 0x00000080)) {
+ output.writeMessage(8, updatePayment_);
+ }
+ if (((bitField0_ & 0x00000100) == 0x00000100)) {
+ output.writeMessage(10, error_);
+ }
+ getUnknownFields().writeTo(output);
+ }
+
+ private int memoizedSerializedSize = -1;
+ public int getSerializedSize() {
+ int size = memoizedSerializedSize;
+ if (size != -1) return size;
+
+ size = 0;
+ if (((bitField0_ & 0x00000001) == 0x00000001)) {
+ size += com.google.protobuf.CodedOutputStream
+ .computeEnumSize(1, type_.getNumber());
+ }
+ if (((bitField0_ & 0x00000002) == 0x00000002)) {
+ size += com.google.protobuf.CodedOutputStream
+ .computeMessageSize(2, clientVersion_);
+ }
+ if (((bitField0_ & 0x00000004) == 0x00000004)) {
+ size += com.google.protobuf.CodedOutputStream
+ .computeMessageSize(3, serverVersion_);
+ }
+ if (((bitField0_ & 0x00000008) == 0x00000008)) {
+ size += com.google.protobuf.CodedOutputStream
+ .computeMessageSize(4, initiate_);
+ }
+ if (((bitField0_ & 0x00000010) == 0x00000010)) {
+ size += com.google.protobuf.CodedOutputStream
+ .computeMessageSize(5, provideRefund_);
+ }
+ if (((bitField0_ & 0x00000020) == 0x00000020)) {
+ size += com.google.protobuf.CodedOutputStream
+ .computeMessageSize(6, returnRefund_);
+ }
+ if (((bitField0_ & 0x00000040) == 0x00000040)) {
+ size += com.google.protobuf.CodedOutputStream
+ .computeMessageSize(7, provideContract_);
+ }
+ if (((bitField0_ & 0x00000080) == 0x00000080)) {
+ size += com.google.protobuf.CodedOutputStream
+ .computeMessageSize(8, updatePayment_);
+ }
+ if (((bitField0_ & 0x00000100) == 0x00000100)) {
+ size += com.google.protobuf.CodedOutputStream
+ .computeMessageSize(10, error_);
+ }
+ size += getUnknownFields().getSerializedSize();
+ memoizedSerializedSize = size;
+ return size;
+ }
+
+ private static final long serialVersionUID = 0L;
+ @java.lang.Override
+ protected java.lang.Object writeReplace()
+ throws java.io.ObjectStreamException {
+ return super.writeReplace();
+ }
+
+ public static org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage parseFrom(
+ com.google.protobuf.ByteString data)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data).buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage parseFrom(
+ com.google.protobuf.ByteString data,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data, extensionRegistry)
+ .buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage parseFrom(byte[] data)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data).buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage parseFrom(
+ byte[] data,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data, extensionRegistry)
+ .buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage parseFrom(java.io.InputStream input)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input).buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage parseFrom(
+ java.io.InputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input, extensionRegistry)
+ .buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage parseDelimitedFrom(java.io.InputStream input)
+ throws java.io.IOException {
+ Builder builder = newBuilder();
+ if (builder.mergeDelimitedFrom(input)) {
+ return builder.buildParsed();
+ } else {
+ return null;
+ }
+ }
+ public static org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage parseDelimitedFrom(
+ java.io.InputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ Builder builder = newBuilder();
+ if (builder.mergeDelimitedFrom(input, extensionRegistry)) {
+ return builder.buildParsed();
+ } else {
+ return null;
+ }
+ }
+ public static org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage parseFrom(
+ com.google.protobuf.CodedInputStream input)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input).buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage parseFrom(
+ com.google.protobuf.CodedInputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input, extensionRegistry)
+ .buildParsed();
+ }
+
+ public static Builder newBuilder() { return Builder.create(); }
+ public Builder newBuilderForType() { return newBuilder(); }
+ public static Builder newBuilder(org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage prototype) {
+ return newBuilder().mergeFrom(prototype);
+ }
+ public Builder toBuilder() { return newBuilder(this); }
+
+ @java.lang.Override
+ protected Builder newBuilderForType(
+ com.google.protobuf.GeneratedMessage.BuilderParent parent) {
+ Builder builder = new Builder(parent);
+ return builder;
+ }
+ public static final class Builder extends
+ com.google.protobuf.GeneratedMessage.Builder
+ implements org.bitcoin.paymentchannel.Protos.TwoWayChannelMessageOrBuilder {
+ public static final com.google.protobuf.Descriptors.Descriptor
+ getDescriptor() {
+ return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_TwoWayChannelMessage_descriptor;
+ }
+
+ protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
+ internalGetFieldAccessorTable() {
+ return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_TwoWayChannelMessage_fieldAccessorTable;
+ }
+
+ // Construct using org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage.newBuilder()
+ private Builder() {
+ maybeForceBuilderInitialization();
+ }
+
+ private Builder(BuilderParent parent) {
+ super(parent);
+ maybeForceBuilderInitialization();
+ }
+ private void maybeForceBuilderInitialization() {
+ if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
+ getClientVersionFieldBuilder();
+ getServerVersionFieldBuilder();
+ getInitiateFieldBuilder();
+ getProvideRefundFieldBuilder();
+ getReturnRefundFieldBuilder();
+ getProvideContractFieldBuilder();
+ getUpdatePaymentFieldBuilder();
+ getErrorFieldBuilder();
+ }
+ }
+ private static Builder create() {
+ return new Builder();
+ }
+
+ public Builder clear() {
+ super.clear();
+ type_ = org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage.MessageType.CLIENT_VERSION;
+ bitField0_ = (bitField0_ & ~0x00000001);
+ if (clientVersionBuilder_ == null) {
+ clientVersion_ = org.bitcoin.paymentchannel.Protos.ClientVersion.getDefaultInstance();
+ } else {
+ clientVersionBuilder_.clear();
+ }
+ bitField0_ = (bitField0_ & ~0x00000002);
+ if (serverVersionBuilder_ == null) {
+ serverVersion_ = org.bitcoin.paymentchannel.Protos.ServerVersion.getDefaultInstance();
+ } else {
+ serverVersionBuilder_.clear();
+ }
+ bitField0_ = (bitField0_ & ~0x00000004);
+ if (initiateBuilder_ == null) {
+ initiate_ = org.bitcoin.paymentchannel.Protos.Initiate.getDefaultInstance();
+ } else {
+ initiateBuilder_.clear();
+ }
+ bitField0_ = (bitField0_ & ~0x00000008);
+ if (provideRefundBuilder_ == null) {
+ provideRefund_ = org.bitcoin.paymentchannel.Protos.ProvideRefund.getDefaultInstance();
+ } else {
+ provideRefundBuilder_.clear();
+ }
+ bitField0_ = (bitField0_ & ~0x00000010);
+ if (returnRefundBuilder_ == null) {
+ returnRefund_ = org.bitcoin.paymentchannel.Protos.ReturnRefund.getDefaultInstance();
+ } else {
+ returnRefundBuilder_.clear();
+ }
+ bitField0_ = (bitField0_ & ~0x00000020);
+ if (provideContractBuilder_ == null) {
+ provideContract_ = org.bitcoin.paymentchannel.Protos.ProvideContract.getDefaultInstance();
+ } else {
+ provideContractBuilder_.clear();
+ }
+ bitField0_ = (bitField0_ & ~0x00000040);
+ if (updatePaymentBuilder_ == null) {
+ updatePayment_ = org.bitcoin.paymentchannel.Protos.UpdatePayment.getDefaultInstance();
+ } else {
+ updatePaymentBuilder_.clear();
+ }
+ bitField0_ = (bitField0_ & ~0x00000080);
+ if (errorBuilder_ == null) {
+ error_ = org.bitcoin.paymentchannel.Protos.Error.getDefaultInstance();
+ } else {
+ errorBuilder_.clear();
+ }
+ bitField0_ = (bitField0_ & ~0x00000100);
+ return this;
+ }
+
+ public Builder clone() {
+ return create().mergeFrom(buildPartial());
+ }
+
+ public com.google.protobuf.Descriptors.Descriptor
+ getDescriptorForType() {
+ return org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage.getDescriptor();
+ }
+
+ public org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage getDefaultInstanceForType() {
+ return org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage.getDefaultInstance();
+ }
+
+ public org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage build() {
+ org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage result = buildPartial();
+ if (!result.isInitialized()) {
+ throw newUninitializedMessageException(result);
+ }
+ return result;
+ }
+
+ private org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage buildParsed()
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage result = buildPartial();
+ if (!result.isInitialized()) {
+ throw newUninitializedMessageException(
+ result).asInvalidProtocolBufferException();
+ }
+ return result;
+ }
+
+ public org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage buildPartial() {
+ org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage result = new org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage(this);
+ int from_bitField0_ = bitField0_;
+ int to_bitField0_ = 0;
+ if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
+ to_bitField0_ |= 0x00000001;
+ }
+ result.type_ = type_;
+ if (((from_bitField0_ & 0x00000002) == 0x00000002)) {
+ to_bitField0_ |= 0x00000002;
+ }
+ if (clientVersionBuilder_ == null) {
+ result.clientVersion_ = clientVersion_;
+ } else {
+ result.clientVersion_ = clientVersionBuilder_.build();
+ }
+ if (((from_bitField0_ & 0x00000004) == 0x00000004)) {
+ to_bitField0_ |= 0x00000004;
+ }
+ if (serverVersionBuilder_ == null) {
+ result.serverVersion_ = serverVersion_;
+ } else {
+ result.serverVersion_ = serverVersionBuilder_.build();
+ }
+ if (((from_bitField0_ & 0x00000008) == 0x00000008)) {
+ to_bitField0_ |= 0x00000008;
+ }
+ if (initiateBuilder_ == null) {
+ result.initiate_ = initiate_;
+ } else {
+ result.initiate_ = initiateBuilder_.build();
+ }
+ if (((from_bitField0_ & 0x00000010) == 0x00000010)) {
+ to_bitField0_ |= 0x00000010;
+ }
+ if (provideRefundBuilder_ == null) {
+ result.provideRefund_ = provideRefund_;
+ } else {
+ result.provideRefund_ = provideRefundBuilder_.build();
+ }
+ if (((from_bitField0_ & 0x00000020) == 0x00000020)) {
+ to_bitField0_ |= 0x00000020;
+ }
+ if (returnRefundBuilder_ == null) {
+ result.returnRefund_ = returnRefund_;
+ } else {
+ result.returnRefund_ = returnRefundBuilder_.build();
+ }
+ if (((from_bitField0_ & 0x00000040) == 0x00000040)) {
+ to_bitField0_ |= 0x00000040;
+ }
+ if (provideContractBuilder_ == null) {
+ result.provideContract_ = provideContract_;
+ } else {
+ result.provideContract_ = provideContractBuilder_.build();
+ }
+ if (((from_bitField0_ & 0x00000080) == 0x00000080)) {
+ to_bitField0_ |= 0x00000080;
+ }
+ if (updatePaymentBuilder_ == null) {
+ result.updatePayment_ = updatePayment_;
+ } else {
+ result.updatePayment_ = updatePaymentBuilder_.build();
+ }
+ if (((from_bitField0_ & 0x00000100) == 0x00000100)) {
+ to_bitField0_ |= 0x00000100;
+ }
+ if (errorBuilder_ == null) {
+ result.error_ = error_;
+ } else {
+ result.error_ = errorBuilder_.build();
+ }
+ result.bitField0_ = to_bitField0_;
+ onBuilt();
+ return result;
+ }
+
+ public Builder mergeFrom(com.google.protobuf.Message other) {
+ if (other instanceof org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage) {
+ return mergeFrom((org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage)other);
+ } else {
+ super.mergeFrom(other);
+ return this;
+ }
+ }
+
+ public Builder mergeFrom(org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage other) {
+ if (other == org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage.getDefaultInstance()) return this;
+ if (other.hasType()) {
+ setType(other.getType());
+ }
+ if (other.hasClientVersion()) {
+ mergeClientVersion(other.getClientVersion());
+ }
+ if (other.hasServerVersion()) {
+ mergeServerVersion(other.getServerVersion());
+ }
+ if (other.hasInitiate()) {
+ mergeInitiate(other.getInitiate());
+ }
+ if (other.hasProvideRefund()) {
+ mergeProvideRefund(other.getProvideRefund());
+ }
+ if (other.hasReturnRefund()) {
+ mergeReturnRefund(other.getReturnRefund());
+ }
+ if (other.hasProvideContract()) {
+ mergeProvideContract(other.getProvideContract());
+ }
+ if (other.hasUpdatePayment()) {
+ mergeUpdatePayment(other.getUpdatePayment());
+ }
+ if (other.hasError()) {
+ mergeError(other.getError());
+ }
+ this.mergeUnknownFields(other.getUnknownFields());
+ return this;
+ }
+
+ public final boolean isInitialized() {
+ if (!hasType()) {
+
+ return false;
+ }
+ if (hasClientVersion()) {
+ if (!getClientVersion().isInitialized()) {
+
+ return false;
+ }
+ }
+ if (hasServerVersion()) {
+ if (!getServerVersion().isInitialized()) {
+
+ return false;
+ }
+ }
+ if (hasInitiate()) {
+ if (!getInitiate().isInitialized()) {
+
+ return false;
+ }
+ }
+ if (hasProvideRefund()) {
+ if (!getProvideRefund().isInitialized()) {
+
+ return false;
+ }
+ }
+ if (hasReturnRefund()) {
+ if (!getReturnRefund().isInitialized()) {
+
+ return false;
+ }
+ }
+ if (hasProvideContract()) {
+ if (!getProvideContract().isInitialized()) {
+
+ return false;
+ }
+ }
+ if (hasUpdatePayment()) {
+ if (!getUpdatePayment().isInitialized()) {
+
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public Builder mergeFrom(
+ com.google.protobuf.CodedInputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+ com.google.protobuf.UnknownFieldSet.newBuilder(
+ this.getUnknownFields());
+ while (true) {
+ int tag = input.readTag();
+ switch (tag) {
+ case 0:
+ this.setUnknownFields(unknownFields.build());
+ onChanged();
+ return this;
+ default: {
+ if (!parseUnknownField(input, unknownFields,
+ extensionRegistry, tag)) {
+ this.setUnknownFields(unknownFields.build());
+ onChanged();
+ return this;
+ }
+ break;
+ }
+ case 8: {
+ int rawValue = input.readEnum();
+ org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage.MessageType value = org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage.MessageType.valueOf(rawValue);
+ if (value == null) {
+ unknownFields.mergeVarintField(1, rawValue);
+ } else {
+ bitField0_ |= 0x00000001;
+ type_ = value;
+ }
+ break;
+ }
+ case 18: {
+ org.bitcoin.paymentchannel.Protos.ClientVersion.Builder subBuilder = org.bitcoin.paymentchannel.Protos.ClientVersion.newBuilder();
+ if (hasClientVersion()) {
+ subBuilder.mergeFrom(getClientVersion());
+ }
+ input.readMessage(subBuilder, extensionRegistry);
+ setClientVersion(subBuilder.buildPartial());
+ break;
+ }
+ case 26: {
+ org.bitcoin.paymentchannel.Protos.ServerVersion.Builder subBuilder = org.bitcoin.paymentchannel.Protos.ServerVersion.newBuilder();
+ if (hasServerVersion()) {
+ subBuilder.mergeFrom(getServerVersion());
+ }
+ input.readMessage(subBuilder, extensionRegistry);
+ setServerVersion(subBuilder.buildPartial());
+ break;
+ }
+ case 34: {
+ org.bitcoin.paymentchannel.Protos.Initiate.Builder subBuilder = org.bitcoin.paymentchannel.Protos.Initiate.newBuilder();
+ if (hasInitiate()) {
+ subBuilder.mergeFrom(getInitiate());
+ }
+ input.readMessage(subBuilder, extensionRegistry);
+ setInitiate(subBuilder.buildPartial());
+ break;
+ }
+ case 42: {
+ org.bitcoin.paymentchannel.Protos.ProvideRefund.Builder subBuilder = org.bitcoin.paymentchannel.Protos.ProvideRefund.newBuilder();
+ if (hasProvideRefund()) {
+ subBuilder.mergeFrom(getProvideRefund());
+ }
+ input.readMessage(subBuilder, extensionRegistry);
+ setProvideRefund(subBuilder.buildPartial());
+ break;
+ }
+ case 50: {
+ org.bitcoin.paymentchannel.Protos.ReturnRefund.Builder subBuilder = org.bitcoin.paymentchannel.Protos.ReturnRefund.newBuilder();
+ if (hasReturnRefund()) {
+ subBuilder.mergeFrom(getReturnRefund());
+ }
+ input.readMessage(subBuilder, extensionRegistry);
+ setReturnRefund(subBuilder.buildPartial());
+ break;
+ }
+ case 58: {
+ org.bitcoin.paymentchannel.Protos.ProvideContract.Builder subBuilder = org.bitcoin.paymentchannel.Protos.ProvideContract.newBuilder();
+ if (hasProvideContract()) {
+ subBuilder.mergeFrom(getProvideContract());
+ }
+ input.readMessage(subBuilder, extensionRegistry);
+ setProvideContract(subBuilder.buildPartial());
+ break;
+ }
+ case 66: {
+ org.bitcoin.paymentchannel.Protos.UpdatePayment.Builder subBuilder = org.bitcoin.paymentchannel.Protos.UpdatePayment.newBuilder();
+ if (hasUpdatePayment()) {
+ subBuilder.mergeFrom(getUpdatePayment());
+ }
+ input.readMessage(subBuilder, extensionRegistry);
+ setUpdatePayment(subBuilder.buildPartial());
+ break;
+ }
+ case 82: {
+ org.bitcoin.paymentchannel.Protos.Error.Builder subBuilder = org.bitcoin.paymentchannel.Protos.Error.newBuilder();
+ if (hasError()) {
+ subBuilder.mergeFrom(getError());
+ }
+ input.readMessage(subBuilder, extensionRegistry);
+ setError(subBuilder.buildPartial());
+ break;
+ }
+ }
+ }
+ }
+
+ private int bitField0_;
+
+ // required .paymentchannels.TwoWayChannelMessage.MessageType type = 1;
+ private org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage.MessageType type_ = org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage.MessageType.CLIENT_VERSION;
+ public boolean hasType() {
+ return ((bitField0_ & 0x00000001) == 0x00000001);
+ }
+ public org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage.MessageType getType() {
+ return type_;
+ }
+ public Builder setType(org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage.MessageType value) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ bitField0_ |= 0x00000001;
+ type_ = value;
+ onChanged();
+ return this;
+ }
+ public Builder clearType() {
+ bitField0_ = (bitField0_ & ~0x00000001);
+ type_ = org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage.MessageType.CLIENT_VERSION;
+ onChanged();
+ return this;
+ }
+
+ // optional .paymentchannels.ClientVersion client_version = 2;
+ private org.bitcoin.paymentchannel.Protos.ClientVersion clientVersion_ = org.bitcoin.paymentchannel.Protos.ClientVersion.getDefaultInstance();
+ private com.google.protobuf.SingleFieldBuilder<
+ org.bitcoin.paymentchannel.Protos.ClientVersion, org.bitcoin.paymentchannel.Protos.ClientVersion.Builder, org.bitcoin.paymentchannel.Protos.ClientVersionOrBuilder> clientVersionBuilder_;
+ public boolean hasClientVersion() {
+ return ((bitField0_ & 0x00000002) == 0x00000002);
+ }
+ public org.bitcoin.paymentchannel.Protos.ClientVersion getClientVersion() {
+ if (clientVersionBuilder_ == null) {
+ return clientVersion_;
+ } else {
+ return clientVersionBuilder_.getMessage();
+ }
+ }
+ public Builder setClientVersion(org.bitcoin.paymentchannel.Protos.ClientVersion value) {
+ if (clientVersionBuilder_ == null) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ clientVersion_ = value;
+ onChanged();
+ } else {
+ clientVersionBuilder_.setMessage(value);
+ }
+ bitField0_ |= 0x00000002;
+ return this;
+ }
+ public Builder setClientVersion(
+ org.bitcoin.paymentchannel.Protos.ClientVersion.Builder builderForValue) {
+ if (clientVersionBuilder_ == null) {
+ clientVersion_ = builderForValue.build();
+ onChanged();
+ } else {
+ clientVersionBuilder_.setMessage(builderForValue.build());
+ }
+ bitField0_ |= 0x00000002;
+ return this;
+ }
+ public Builder mergeClientVersion(org.bitcoin.paymentchannel.Protos.ClientVersion value) {
+ if (clientVersionBuilder_ == null) {
+ if (((bitField0_ & 0x00000002) == 0x00000002) &&
+ clientVersion_ != org.bitcoin.paymentchannel.Protos.ClientVersion.getDefaultInstance()) {
+ clientVersion_ =
+ org.bitcoin.paymentchannel.Protos.ClientVersion.newBuilder(clientVersion_).mergeFrom(value).buildPartial();
+ } else {
+ clientVersion_ = value;
+ }
+ onChanged();
+ } else {
+ clientVersionBuilder_.mergeFrom(value);
+ }
+ bitField0_ |= 0x00000002;
+ return this;
+ }
+ public Builder clearClientVersion() {
+ if (clientVersionBuilder_ == null) {
+ clientVersion_ = org.bitcoin.paymentchannel.Protos.ClientVersion.getDefaultInstance();
+ onChanged();
+ } else {
+ clientVersionBuilder_.clear();
+ }
+ bitField0_ = (bitField0_ & ~0x00000002);
+ return this;
+ }
+ public org.bitcoin.paymentchannel.Protos.ClientVersion.Builder getClientVersionBuilder() {
+ bitField0_ |= 0x00000002;
+ onChanged();
+ return getClientVersionFieldBuilder().getBuilder();
+ }
+ public org.bitcoin.paymentchannel.Protos.ClientVersionOrBuilder getClientVersionOrBuilder() {
+ if (clientVersionBuilder_ != null) {
+ return clientVersionBuilder_.getMessageOrBuilder();
+ } else {
+ return clientVersion_;
+ }
+ }
+ private com.google.protobuf.SingleFieldBuilder<
+ org.bitcoin.paymentchannel.Protos.ClientVersion, org.bitcoin.paymentchannel.Protos.ClientVersion.Builder, org.bitcoin.paymentchannel.Protos.ClientVersionOrBuilder>
+ getClientVersionFieldBuilder() {
+ if (clientVersionBuilder_ == null) {
+ clientVersionBuilder_ = new com.google.protobuf.SingleFieldBuilder<
+ org.bitcoin.paymentchannel.Protos.ClientVersion, org.bitcoin.paymentchannel.Protos.ClientVersion.Builder, org.bitcoin.paymentchannel.Protos.ClientVersionOrBuilder>(
+ clientVersion_,
+ getParentForChildren(),
+ isClean());
+ clientVersion_ = null;
+ }
+ return clientVersionBuilder_;
+ }
+
+ // optional .paymentchannels.ServerVersion server_version = 3;
+ private org.bitcoin.paymentchannel.Protos.ServerVersion serverVersion_ = org.bitcoin.paymentchannel.Protos.ServerVersion.getDefaultInstance();
+ private com.google.protobuf.SingleFieldBuilder<
+ org.bitcoin.paymentchannel.Protos.ServerVersion, org.bitcoin.paymentchannel.Protos.ServerVersion.Builder, org.bitcoin.paymentchannel.Protos.ServerVersionOrBuilder> serverVersionBuilder_;
+ public boolean hasServerVersion() {
+ return ((bitField0_ & 0x00000004) == 0x00000004);
+ }
+ public org.bitcoin.paymentchannel.Protos.ServerVersion getServerVersion() {
+ if (serverVersionBuilder_ == null) {
+ return serverVersion_;
+ } else {
+ return serverVersionBuilder_.getMessage();
+ }
+ }
+ public Builder setServerVersion(org.bitcoin.paymentchannel.Protos.ServerVersion value) {
+ if (serverVersionBuilder_ == null) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ serverVersion_ = value;
+ onChanged();
+ } else {
+ serverVersionBuilder_.setMessage(value);
+ }
+ bitField0_ |= 0x00000004;
+ return this;
+ }
+ public Builder setServerVersion(
+ org.bitcoin.paymentchannel.Protos.ServerVersion.Builder builderForValue) {
+ if (serverVersionBuilder_ == null) {
+ serverVersion_ = builderForValue.build();
+ onChanged();
+ } else {
+ serverVersionBuilder_.setMessage(builderForValue.build());
+ }
+ bitField0_ |= 0x00000004;
+ return this;
+ }
+ public Builder mergeServerVersion(org.bitcoin.paymentchannel.Protos.ServerVersion value) {
+ if (serverVersionBuilder_ == null) {
+ if (((bitField0_ & 0x00000004) == 0x00000004) &&
+ serverVersion_ != org.bitcoin.paymentchannel.Protos.ServerVersion.getDefaultInstance()) {
+ serverVersion_ =
+ org.bitcoin.paymentchannel.Protos.ServerVersion.newBuilder(serverVersion_).mergeFrom(value).buildPartial();
+ } else {
+ serverVersion_ = value;
+ }
+ onChanged();
+ } else {
+ serverVersionBuilder_.mergeFrom(value);
+ }
+ bitField0_ |= 0x00000004;
+ return this;
+ }
+ public Builder clearServerVersion() {
+ if (serverVersionBuilder_ == null) {
+ serverVersion_ = org.bitcoin.paymentchannel.Protos.ServerVersion.getDefaultInstance();
+ onChanged();
+ } else {
+ serverVersionBuilder_.clear();
+ }
+ bitField0_ = (bitField0_ & ~0x00000004);
+ return this;
+ }
+ public org.bitcoin.paymentchannel.Protos.ServerVersion.Builder getServerVersionBuilder() {
+ bitField0_ |= 0x00000004;
+ onChanged();
+ return getServerVersionFieldBuilder().getBuilder();
+ }
+ public org.bitcoin.paymentchannel.Protos.ServerVersionOrBuilder getServerVersionOrBuilder() {
+ if (serverVersionBuilder_ != null) {
+ return serverVersionBuilder_.getMessageOrBuilder();
+ } else {
+ return serverVersion_;
+ }
+ }
+ private com.google.protobuf.SingleFieldBuilder<
+ org.bitcoin.paymentchannel.Protos.ServerVersion, org.bitcoin.paymentchannel.Protos.ServerVersion.Builder, org.bitcoin.paymentchannel.Protos.ServerVersionOrBuilder>
+ getServerVersionFieldBuilder() {
+ if (serverVersionBuilder_ == null) {
+ serverVersionBuilder_ = new com.google.protobuf.SingleFieldBuilder<
+ org.bitcoin.paymentchannel.Protos.ServerVersion, org.bitcoin.paymentchannel.Protos.ServerVersion.Builder, org.bitcoin.paymentchannel.Protos.ServerVersionOrBuilder>(
+ serverVersion_,
+ getParentForChildren(),
+ isClean());
+ serverVersion_ = null;
+ }
+ return serverVersionBuilder_;
+ }
+
+ // optional .paymentchannels.Initiate initiate = 4;
+ private org.bitcoin.paymentchannel.Protos.Initiate initiate_ = org.bitcoin.paymentchannel.Protos.Initiate.getDefaultInstance();
+ private com.google.protobuf.SingleFieldBuilder<
+ org.bitcoin.paymentchannel.Protos.Initiate, org.bitcoin.paymentchannel.Protos.Initiate.Builder, org.bitcoin.paymentchannel.Protos.InitiateOrBuilder> initiateBuilder_;
+ public boolean hasInitiate() {
+ return ((bitField0_ & 0x00000008) == 0x00000008);
+ }
+ public org.bitcoin.paymentchannel.Protos.Initiate getInitiate() {
+ if (initiateBuilder_ == null) {
+ return initiate_;
+ } else {
+ return initiateBuilder_.getMessage();
+ }
+ }
+ public Builder setInitiate(org.bitcoin.paymentchannel.Protos.Initiate value) {
+ if (initiateBuilder_ == null) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ initiate_ = value;
+ onChanged();
+ } else {
+ initiateBuilder_.setMessage(value);
+ }
+ bitField0_ |= 0x00000008;
+ return this;
+ }
+ public Builder setInitiate(
+ org.bitcoin.paymentchannel.Protos.Initiate.Builder builderForValue) {
+ if (initiateBuilder_ == null) {
+ initiate_ = builderForValue.build();
+ onChanged();
+ } else {
+ initiateBuilder_.setMessage(builderForValue.build());
+ }
+ bitField0_ |= 0x00000008;
+ return this;
+ }
+ public Builder mergeInitiate(org.bitcoin.paymentchannel.Protos.Initiate value) {
+ if (initiateBuilder_ == null) {
+ if (((bitField0_ & 0x00000008) == 0x00000008) &&
+ initiate_ != org.bitcoin.paymentchannel.Protos.Initiate.getDefaultInstance()) {
+ initiate_ =
+ org.bitcoin.paymentchannel.Protos.Initiate.newBuilder(initiate_).mergeFrom(value).buildPartial();
+ } else {
+ initiate_ = value;
+ }
+ onChanged();
+ } else {
+ initiateBuilder_.mergeFrom(value);
+ }
+ bitField0_ |= 0x00000008;
+ return this;
+ }
+ public Builder clearInitiate() {
+ if (initiateBuilder_ == null) {
+ initiate_ = org.bitcoin.paymentchannel.Protos.Initiate.getDefaultInstance();
+ onChanged();
+ } else {
+ initiateBuilder_.clear();
+ }
+ bitField0_ = (bitField0_ & ~0x00000008);
+ return this;
+ }
+ public org.bitcoin.paymentchannel.Protos.Initiate.Builder getInitiateBuilder() {
+ bitField0_ |= 0x00000008;
+ onChanged();
+ return getInitiateFieldBuilder().getBuilder();
+ }
+ public org.bitcoin.paymentchannel.Protos.InitiateOrBuilder getInitiateOrBuilder() {
+ if (initiateBuilder_ != null) {
+ return initiateBuilder_.getMessageOrBuilder();
+ } else {
+ return initiate_;
+ }
+ }
+ private com.google.protobuf.SingleFieldBuilder<
+ org.bitcoin.paymentchannel.Protos.Initiate, org.bitcoin.paymentchannel.Protos.Initiate.Builder, org.bitcoin.paymentchannel.Protos.InitiateOrBuilder>
+ getInitiateFieldBuilder() {
+ if (initiateBuilder_ == null) {
+ initiateBuilder_ = new com.google.protobuf.SingleFieldBuilder<
+ org.bitcoin.paymentchannel.Protos.Initiate, org.bitcoin.paymentchannel.Protos.Initiate.Builder, org.bitcoin.paymentchannel.Protos.InitiateOrBuilder>(
+ initiate_,
+ getParentForChildren(),
+ isClean());
+ initiate_ = null;
+ }
+ return initiateBuilder_;
+ }
+
+ // optional .paymentchannels.ProvideRefund provide_refund = 5;
+ private org.bitcoin.paymentchannel.Protos.ProvideRefund provideRefund_ = org.bitcoin.paymentchannel.Protos.ProvideRefund.getDefaultInstance();
+ private com.google.protobuf.SingleFieldBuilder<
+ org.bitcoin.paymentchannel.Protos.ProvideRefund, org.bitcoin.paymentchannel.Protos.ProvideRefund.Builder, org.bitcoin.paymentchannel.Protos.ProvideRefundOrBuilder> provideRefundBuilder_;
+ public boolean hasProvideRefund() {
+ return ((bitField0_ & 0x00000010) == 0x00000010);
+ }
+ public org.bitcoin.paymentchannel.Protos.ProvideRefund getProvideRefund() {
+ if (provideRefundBuilder_ == null) {
+ return provideRefund_;
+ } else {
+ return provideRefundBuilder_.getMessage();
+ }
+ }
+ public Builder setProvideRefund(org.bitcoin.paymentchannel.Protos.ProvideRefund value) {
+ if (provideRefundBuilder_ == null) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ provideRefund_ = value;
+ onChanged();
+ } else {
+ provideRefundBuilder_.setMessage(value);
+ }
+ bitField0_ |= 0x00000010;
+ return this;
+ }
+ public Builder setProvideRefund(
+ org.bitcoin.paymentchannel.Protos.ProvideRefund.Builder builderForValue) {
+ if (provideRefundBuilder_ == null) {
+ provideRefund_ = builderForValue.build();
+ onChanged();
+ } else {
+ provideRefundBuilder_.setMessage(builderForValue.build());
+ }
+ bitField0_ |= 0x00000010;
+ return this;
+ }
+ public Builder mergeProvideRefund(org.bitcoin.paymentchannel.Protos.ProvideRefund value) {
+ if (provideRefundBuilder_ == null) {
+ if (((bitField0_ & 0x00000010) == 0x00000010) &&
+ provideRefund_ != org.bitcoin.paymentchannel.Protos.ProvideRefund.getDefaultInstance()) {
+ provideRefund_ =
+ org.bitcoin.paymentchannel.Protos.ProvideRefund.newBuilder(provideRefund_).mergeFrom(value).buildPartial();
+ } else {
+ provideRefund_ = value;
+ }
+ onChanged();
+ } else {
+ provideRefundBuilder_.mergeFrom(value);
+ }
+ bitField0_ |= 0x00000010;
+ return this;
+ }
+ public Builder clearProvideRefund() {
+ if (provideRefundBuilder_ == null) {
+ provideRefund_ = org.bitcoin.paymentchannel.Protos.ProvideRefund.getDefaultInstance();
+ onChanged();
+ } else {
+ provideRefundBuilder_.clear();
+ }
+ bitField0_ = (bitField0_ & ~0x00000010);
+ return this;
+ }
+ public org.bitcoin.paymentchannel.Protos.ProvideRefund.Builder getProvideRefundBuilder() {
+ bitField0_ |= 0x00000010;
+ onChanged();
+ return getProvideRefundFieldBuilder().getBuilder();
+ }
+ public org.bitcoin.paymentchannel.Protos.ProvideRefundOrBuilder getProvideRefundOrBuilder() {
+ if (provideRefundBuilder_ != null) {
+ return provideRefundBuilder_.getMessageOrBuilder();
+ } else {
+ return provideRefund_;
+ }
+ }
+ private com.google.protobuf.SingleFieldBuilder<
+ org.bitcoin.paymentchannel.Protos.ProvideRefund, org.bitcoin.paymentchannel.Protos.ProvideRefund.Builder, org.bitcoin.paymentchannel.Protos.ProvideRefundOrBuilder>
+ getProvideRefundFieldBuilder() {
+ if (provideRefundBuilder_ == null) {
+ provideRefundBuilder_ = new com.google.protobuf.SingleFieldBuilder<
+ org.bitcoin.paymentchannel.Protos.ProvideRefund, org.bitcoin.paymentchannel.Protos.ProvideRefund.Builder, org.bitcoin.paymentchannel.Protos.ProvideRefundOrBuilder>(
+ provideRefund_,
+ getParentForChildren(),
+ isClean());
+ provideRefund_ = null;
+ }
+ return provideRefundBuilder_;
+ }
+
+ // optional .paymentchannels.ReturnRefund return_refund = 6;
+ private org.bitcoin.paymentchannel.Protos.ReturnRefund returnRefund_ = org.bitcoin.paymentchannel.Protos.ReturnRefund.getDefaultInstance();
+ private com.google.protobuf.SingleFieldBuilder<
+ org.bitcoin.paymentchannel.Protos.ReturnRefund, org.bitcoin.paymentchannel.Protos.ReturnRefund.Builder, org.bitcoin.paymentchannel.Protos.ReturnRefundOrBuilder> returnRefundBuilder_;
+ public boolean hasReturnRefund() {
+ return ((bitField0_ & 0x00000020) == 0x00000020);
+ }
+ public org.bitcoin.paymentchannel.Protos.ReturnRefund getReturnRefund() {
+ if (returnRefundBuilder_ == null) {
+ return returnRefund_;
+ } else {
+ return returnRefundBuilder_.getMessage();
+ }
+ }
+ public Builder setReturnRefund(org.bitcoin.paymentchannel.Protos.ReturnRefund value) {
+ if (returnRefundBuilder_ == null) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ returnRefund_ = value;
+ onChanged();
+ } else {
+ returnRefundBuilder_.setMessage(value);
+ }
+ bitField0_ |= 0x00000020;
+ return this;
+ }
+ public Builder setReturnRefund(
+ org.bitcoin.paymentchannel.Protos.ReturnRefund.Builder builderForValue) {
+ if (returnRefundBuilder_ == null) {
+ returnRefund_ = builderForValue.build();
+ onChanged();
+ } else {
+ returnRefundBuilder_.setMessage(builderForValue.build());
+ }
+ bitField0_ |= 0x00000020;
+ return this;
+ }
+ public Builder mergeReturnRefund(org.bitcoin.paymentchannel.Protos.ReturnRefund value) {
+ if (returnRefundBuilder_ == null) {
+ if (((bitField0_ & 0x00000020) == 0x00000020) &&
+ returnRefund_ != org.bitcoin.paymentchannel.Protos.ReturnRefund.getDefaultInstance()) {
+ returnRefund_ =
+ org.bitcoin.paymentchannel.Protos.ReturnRefund.newBuilder(returnRefund_).mergeFrom(value).buildPartial();
+ } else {
+ returnRefund_ = value;
+ }
+ onChanged();
+ } else {
+ returnRefundBuilder_.mergeFrom(value);
+ }
+ bitField0_ |= 0x00000020;
+ return this;
+ }
+ public Builder clearReturnRefund() {
+ if (returnRefundBuilder_ == null) {
+ returnRefund_ = org.bitcoin.paymentchannel.Protos.ReturnRefund.getDefaultInstance();
+ onChanged();
+ } else {
+ returnRefundBuilder_.clear();
+ }
+ bitField0_ = (bitField0_ & ~0x00000020);
+ return this;
+ }
+ public org.bitcoin.paymentchannel.Protos.ReturnRefund.Builder getReturnRefundBuilder() {
+ bitField0_ |= 0x00000020;
+ onChanged();
+ return getReturnRefundFieldBuilder().getBuilder();
+ }
+ public org.bitcoin.paymentchannel.Protos.ReturnRefundOrBuilder getReturnRefundOrBuilder() {
+ if (returnRefundBuilder_ != null) {
+ return returnRefundBuilder_.getMessageOrBuilder();
+ } else {
+ return returnRefund_;
+ }
+ }
+ private com.google.protobuf.SingleFieldBuilder<
+ org.bitcoin.paymentchannel.Protos.ReturnRefund, org.bitcoin.paymentchannel.Protos.ReturnRefund.Builder, org.bitcoin.paymentchannel.Protos.ReturnRefundOrBuilder>
+ getReturnRefundFieldBuilder() {
+ if (returnRefundBuilder_ == null) {
+ returnRefundBuilder_ = new com.google.protobuf.SingleFieldBuilder<
+ org.bitcoin.paymentchannel.Protos.ReturnRefund, org.bitcoin.paymentchannel.Protos.ReturnRefund.Builder, org.bitcoin.paymentchannel.Protos.ReturnRefundOrBuilder>(
+ returnRefund_,
+ getParentForChildren(),
+ isClean());
+ returnRefund_ = null;
+ }
+ return returnRefundBuilder_;
+ }
+
+ // optional .paymentchannels.ProvideContract provide_contract = 7;
+ private org.bitcoin.paymentchannel.Protos.ProvideContract provideContract_ = org.bitcoin.paymentchannel.Protos.ProvideContract.getDefaultInstance();
+ private com.google.protobuf.SingleFieldBuilder<
+ org.bitcoin.paymentchannel.Protos.ProvideContract, org.bitcoin.paymentchannel.Protos.ProvideContract.Builder, org.bitcoin.paymentchannel.Protos.ProvideContractOrBuilder> provideContractBuilder_;
+ public boolean hasProvideContract() {
+ return ((bitField0_ & 0x00000040) == 0x00000040);
+ }
+ public org.bitcoin.paymentchannel.Protos.ProvideContract getProvideContract() {
+ if (provideContractBuilder_ == null) {
+ return provideContract_;
+ } else {
+ return provideContractBuilder_.getMessage();
+ }
+ }
+ public Builder setProvideContract(org.bitcoin.paymentchannel.Protos.ProvideContract value) {
+ if (provideContractBuilder_ == null) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ provideContract_ = value;
+ onChanged();
+ } else {
+ provideContractBuilder_.setMessage(value);
+ }
+ bitField0_ |= 0x00000040;
+ return this;
+ }
+ public Builder setProvideContract(
+ org.bitcoin.paymentchannel.Protos.ProvideContract.Builder builderForValue) {
+ if (provideContractBuilder_ == null) {
+ provideContract_ = builderForValue.build();
+ onChanged();
+ } else {
+ provideContractBuilder_.setMessage(builderForValue.build());
+ }
+ bitField0_ |= 0x00000040;
+ return this;
+ }
+ public Builder mergeProvideContract(org.bitcoin.paymentchannel.Protos.ProvideContract value) {
+ if (provideContractBuilder_ == null) {
+ if (((bitField0_ & 0x00000040) == 0x00000040) &&
+ provideContract_ != org.bitcoin.paymentchannel.Protos.ProvideContract.getDefaultInstance()) {
+ provideContract_ =
+ org.bitcoin.paymentchannel.Protos.ProvideContract.newBuilder(provideContract_).mergeFrom(value).buildPartial();
+ } else {
+ provideContract_ = value;
+ }
+ onChanged();
+ } else {
+ provideContractBuilder_.mergeFrom(value);
+ }
+ bitField0_ |= 0x00000040;
+ return this;
+ }
+ public Builder clearProvideContract() {
+ if (provideContractBuilder_ == null) {
+ provideContract_ = org.bitcoin.paymentchannel.Protos.ProvideContract.getDefaultInstance();
+ onChanged();
+ } else {
+ provideContractBuilder_.clear();
+ }
+ bitField0_ = (bitField0_ & ~0x00000040);
+ return this;
+ }
+ public org.bitcoin.paymentchannel.Protos.ProvideContract.Builder getProvideContractBuilder() {
+ bitField0_ |= 0x00000040;
+ onChanged();
+ return getProvideContractFieldBuilder().getBuilder();
+ }
+ public org.bitcoin.paymentchannel.Protos.ProvideContractOrBuilder getProvideContractOrBuilder() {
+ if (provideContractBuilder_ != null) {
+ return provideContractBuilder_.getMessageOrBuilder();
+ } else {
+ return provideContract_;
+ }
+ }
+ private com.google.protobuf.SingleFieldBuilder<
+ org.bitcoin.paymentchannel.Protos.ProvideContract, org.bitcoin.paymentchannel.Protos.ProvideContract.Builder, org.bitcoin.paymentchannel.Protos.ProvideContractOrBuilder>
+ getProvideContractFieldBuilder() {
+ if (provideContractBuilder_ == null) {
+ provideContractBuilder_ = new com.google.protobuf.SingleFieldBuilder<
+ org.bitcoin.paymentchannel.Protos.ProvideContract, org.bitcoin.paymentchannel.Protos.ProvideContract.Builder, org.bitcoin.paymentchannel.Protos.ProvideContractOrBuilder>(
+ provideContract_,
+ getParentForChildren(),
+ isClean());
+ provideContract_ = null;
+ }
+ return provideContractBuilder_;
+ }
+
+ // optional .paymentchannels.UpdatePayment update_payment = 8;
+ private org.bitcoin.paymentchannel.Protos.UpdatePayment updatePayment_ = org.bitcoin.paymentchannel.Protos.UpdatePayment.getDefaultInstance();
+ private com.google.protobuf.SingleFieldBuilder<
+ org.bitcoin.paymentchannel.Protos.UpdatePayment, org.bitcoin.paymentchannel.Protos.UpdatePayment.Builder, org.bitcoin.paymentchannel.Protos.UpdatePaymentOrBuilder> updatePaymentBuilder_;
+ public boolean hasUpdatePayment() {
+ return ((bitField0_ & 0x00000080) == 0x00000080);
+ }
+ public org.bitcoin.paymentchannel.Protos.UpdatePayment getUpdatePayment() {
+ if (updatePaymentBuilder_ == null) {
+ return updatePayment_;
+ } else {
+ return updatePaymentBuilder_.getMessage();
+ }
+ }
+ public Builder setUpdatePayment(org.bitcoin.paymentchannel.Protos.UpdatePayment value) {
+ if (updatePaymentBuilder_ == null) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ updatePayment_ = value;
+ onChanged();
+ } else {
+ updatePaymentBuilder_.setMessage(value);
+ }
+ bitField0_ |= 0x00000080;
+ return this;
+ }
+ public Builder setUpdatePayment(
+ org.bitcoin.paymentchannel.Protos.UpdatePayment.Builder builderForValue) {
+ if (updatePaymentBuilder_ == null) {
+ updatePayment_ = builderForValue.build();
+ onChanged();
+ } else {
+ updatePaymentBuilder_.setMessage(builderForValue.build());
+ }
+ bitField0_ |= 0x00000080;
+ return this;
+ }
+ public Builder mergeUpdatePayment(org.bitcoin.paymentchannel.Protos.UpdatePayment value) {
+ if (updatePaymentBuilder_ == null) {
+ if (((bitField0_ & 0x00000080) == 0x00000080) &&
+ updatePayment_ != org.bitcoin.paymentchannel.Protos.UpdatePayment.getDefaultInstance()) {
+ updatePayment_ =
+ org.bitcoin.paymentchannel.Protos.UpdatePayment.newBuilder(updatePayment_).mergeFrom(value).buildPartial();
+ } else {
+ updatePayment_ = value;
+ }
+ onChanged();
+ } else {
+ updatePaymentBuilder_.mergeFrom(value);
+ }
+ bitField0_ |= 0x00000080;
+ return this;
+ }
+ public Builder clearUpdatePayment() {
+ if (updatePaymentBuilder_ == null) {
+ updatePayment_ = org.bitcoin.paymentchannel.Protos.UpdatePayment.getDefaultInstance();
+ onChanged();
+ } else {
+ updatePaymentBuilder_.clear();
+ }
+ bitField0_ = (bitField0_ & ~0x00000080);
+ return this;
+ }
+ public org.bitcoin.paymentchannel.Protos.UpdatePayment.Builder getUpdatePaymentBuilder() {
+ bitField0_ |= 0x00000080;
+ onChanged();
+ return getUpdatePaymentFieldBuilder().getBuilder();
+ }
+ public org.bitcoin.paymentchannel.Protos.UpdatePaymentOrBuilder getUpdatePaymentOrBuilder() {
+ if (updatePaymentBuilder_ != null) {
+ return updatePaymentBuilder_.getMessageOrBuilder();
+ } else {
+ return updatePayment_;
+ }
+ }
+ private com.google.protobuf.SingleFieldBuilder<
+ org.bitcoin.paymentchannel.Protos.UpdatePayment, org.bitcoin.paymentchannel.Protos.UpdatePayment.Builder, org.bitcoin.paymentchannel.Protos.UpdatePaymentOrBuilder>
+ getUpdatePaymentFieldBuilder() {
+ if (updatePaymentBuilder_ == null) {
+ updatePaymentBuilder_ = new com.google.protobuf.SingleFieldBuilder<
+ org.bitcoin.paymentchannel.Protos.UpdatePayment, org.bitcoin.paymentchannel.Protos.UpdatePayment.Builder, org.bitcoin.paymentchannel.Protos.UpdatePaymentOrBuilder>(
+ updatePayment_,
+ getParentForChildren(),
+ isClean());
+ updatePayment_ = null;
+ }
+ return updatePaymentBuilder_;
+ }
+
+ // optional .paymentchannels.Error error = 10;
+ private org.bitcoin.paymentchannel.Protos.Error error_ = org.bitcoin.paymentchannel.Protos.Error.getDefaultInstance();
+ private com.google.protobuf.SingleFieldBuilder<
+ org.bitcoin.paymentchannel.Protos.Error, org.bitcoin.paymentchannel.Protos.Error.Builder, org.bitcoin.paymentchannel.Protos.ErrorOrBuilder> errorBuilder_;
+ public boolean hasError() {
+ return ((bitField0_ & 0x00000100) == 0x00000100);
+ }
+ public org.bitcoin.paymentchannel.Protos.Error getError() {
+ if (errorBuilder_ == null) {
+ return error_;
+ } else {
+ return errorBuilder_.getMessage();
+ }
+ }
+ public Builder setError(org.bitcoin.paymentchannel.Protos.Error value) {
+ if (errorBuilder_ == null) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ error_ = value;
+ onChanged();
+ } else {
+ errorBuilder_.setMessage(value);
+ }
+ bitField0_ |= 0x00000100;
+ return this;
+ }
+ public Builder setError(
+ org.bitcoin.paymentchannel.Protos.Error.Builder builderForValue) {
+ if (errorBuilder_ == null) {
+ error_ = builderForValue.build();
+ onChanged();
+ } else {
+ errorBuilder_.setMessage(builderForValue.build());
+ }
+ bitField0_ |= 0x00000100;
+ return this;
+ }
+ public Builder mergeError(org.bitcoin.paymentchannel.Protos.Error value) {
+ if (errorBuilder_ == null) {
+ if (((bitField0_ & 0x00000100) == 0x00000100) &&
+ error_ != org.bitcoin.paymentchannel.Protos.Error.getDefaultInstance()) {
+ error_ =
+ org.bitcoin.paymentchannel.Protos.Error.newBuilder(error_).mergeFrom(value).buildPartial();
+ } else {
+ error_ = value;
+ }
+ onChanged();
+ } else {
+ errorBuilder_.mergeFrom(value);
+ }
+ bitField0_ |= 0x00000100;
+ return this;
+ }
+ public Builder clearError() {
+ if (errorBuilder_ == null) {
+ error_ = org.bitcoin.paymentchannel.Protos.Error.getDefaultInstance();
+ onChanged();
+ } else {
+ errorBuilder_.clear();
+ }
+ bitField0_ = (bitField0_ & ~0x00000100);
+ return this;
+ }
+ public org.bitcoin.paymentchannel.Protos.Error.Builder getErrorBuilder() {
+ bitField0_ |= 0x00000100;
+ onChanged();
+ return getErrorFieldBuilder().getBuilder();
+ }
+ public org.bitcoin.paymentchannel.Protos.ErrorOrBuilder getErrorOrBuilder() {
+ if (errorBuilder_ != null) {
+ return errorBuilder_.getMessageOrBuilder();
+ } else {
+ return error_;
+ }
+ }
+ private com.google.protobuf.SingleFieldBuilder<
+ org.bitcoin.paymentchannel.Protos.Error, org.bitcoin.paymentchannel.Protos.Error.Builder, org.bitcoin.paymentchannel.Protos.ErrorOrBuilder>
+ getErrorFieldBuilder() {
+ if (errorBuilder_ == null) {
+ errorBuilder_ = new com.google.protobuf.SingleFieldBuilder<
+ org.bitcoin.paymentchannel.Protos.Error, org.bitcoin.paymentchannel.Protos.Error.Builder, org.bitcoin.paymentchannel.Protos.ErrorOrBuilder>(
+ error_,
+ getParentForChildren(),
+ isClean());
+ error_ = null;
+ }
+ return errorBuilder_;
+ }
+
+ // @@protoc_insertion_point(builder_scope:paymentchannels.TwoWayChannelMessage)
+ }
+
+ static {
+ defaultInstance = new TwoWayChannelMessage(true);
+ defaultInstance.initFields();
+ }
+
+ // @@protoc_insertion_point(class_scope:paymentchannels.TwoWayChannelMessage)
+ }
+
+ public interface ClientVersionOrBuilder
+ extends com.google.protobuf.MessageOrBuilder {
+
+ // required int32 major = 1;
+ boolean hasMajor();
+ int getMajor();
+
+ // optional int32 minor = 2 [default = 0];
+ boolean hasMinor();
+ int getMinor();
+
+ // optional bytes previous_channel_contract_hash = 3;
+ boolean hasPreviousChannelContractHash();
+ com.google.protobuf.ByteString getPreviousChannelContractHash();
+ }
+ public static final class ClientVersion extends
+ com.google.protobuf.GeneratedMessage
+ implements ClientVersionOrBuilder {
+ // Use ClientVersion.newBuilder() to construct.
+ private ClientVersion(Builder builder) {
+ super(builder);
+ }
+ private ClientVersion(boolean noInit) {}
+
+ private static final ClientVersion defaultInstance;
+ public static ClientVersion getDefaultInstance() {
+ return defaultInstance;
+ }
+
+ public ClientVersion getDefaultInstanceForType() {
+ return defaultInstance;
+ }
+
+ public static final com.google.protobuf.Descriptors.Descriptor
+ getDescriptor() {
+ return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_ClientVersion_descriptor;
+ }
+
+ protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
+ internalGetFieldAccessorTable() {
+ return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_ClientVersion_fieldAccessorTable;
+ }
+
+ private int bitField0_;
+ // required int32 major = 1;
+ public static final int MAJOR_FIELD_NUMBER = 1;
+ private int major_;
+ public boolean hasMajor() {
+ return ((bitField0_ & 0x00000001) == 0x00000001);
+ }
+ public int getMajor() {
+ return major_;
+ }
+
+ // optional int32 minor = 2 [default = 0];
+ public static final int MINOR_FIELD_NUMBER = 2;
+ private int minor_;
+ public boolean hasMinor() {
+ return ((bitField0_ & 0x00000002) == 0x00000002);
+ }
+ public int getMinor() {
+ return minor_;
+ }
+
+ // optional bytes previous_channel_contract_hash = 3;
+ public static final int PREVIOUS_CHANNEL_CONTRACT_HASH_FIELD_NUMBER = 3;
+ private com.google.protobuf.ByteString previousChannelContractHash_;
+ public boolean hasPreviousChannelContractHash() {
+ return ((bitField0_ & 0x00000004) == 0x00000004);
+ }
+ public com.google.protobuf.ByteString getPreviousChannelContractHash() {
+ return previousChannelContractHash_;
+ }
+
+ private void initFields() {
+ major_ = 0;
+ minor_ = 0;
+ previousChannelContractHash_ = com.google.protobuf.ByteString.EMPTY;
+ }
+ private byte memoizedIsInitialized = -1;
+ public final boolean isInitialized() {
+ byte isInitialized = memoizedIsInitialized;
+ if (isInitialized != -1) return isInitialized == 1;
+
+ if (!hasMajor()) {
+ memoizedIsInitialized = 0;
+ return false;
+ }
+ memoizedIsInitialized = 1;
+ return true;
+ }
+
+ public void writeTo(com.google.protobuf.CodedOutputStream output)
+ throws java.io.IOException {
+ getSerializedSize();
+ if (((bitField0_ & 0x00000001) == 0x00000001)) {
+ output.writeInt32(1, major_);
+ }
+ if (((bitField0_ & 0x00000002) == 0x00000002)) {
+ output.writeInt32(2, minor_);
+ }
+ if (((bitField0_ & 0x00000004) == 0x00000004)) {
+ output.writeBytes(3, previousChannelContractHash_);
+ }
+ getUnknownFields().writeTo(output);
+ }
+
+ private int memoizedSerializedSize = -1;
+ public int getSerializedSize() {
+ int size = memoizedSerializedSize;
+ if (size != -1) return size;
+
+ size = 0;
+ if (((bitField0_ & 0x00000001) == 0x00000001)) {
+ size += com.google.protobuf.CodedOutputStream
+ .computeInt32Size(1, major_);
+ }
+ if (((bitField0_ & 0x00000002) == 0x00000002)) {
+ size += com.google.protobuf.CodedOutputStream
+ .computeInt32Size(2, minor_);
+ }
+ if (((bitField0_ & 0x00000004) == 0x00000004)) {
+ size += com.google.protobuf.CodedOutputStream
+ .computeBytesSize(3, previousChannelContractHash_);
+ }
+ size += getUnknownFields().getSerializedSize();
+ memoizedSerializedSize = size;
+ return size;
+ }
+
+ private static final long serialVersionUID = 0L;
+ @java.lang.Override
+ protected java.lang.Object writeReplace()
+ throws java.io.ObjectStreamException {
+ return super.writeReplace();
+ }
+
+ public static org.bitcoin.paymentchannel.Protos.ClientVersion parseFrom(
+ com.google.protobuf.ByteString data)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data).buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.ClientVersion parseFrom(
+ com.google.protobuf.ByteString data,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data, extensionRegistry)
+ .buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.ClientVersion parseFrom(byte[] data)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data).buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.ClientVersion parseFrom(
+ byte[] data,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data, extensionRegistry)
+ .buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.ClientVersion parseFrom(java.io.InputStream input)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input).buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.ClientVersion parseFrom(
+ java.io.InputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input, extensionRegistry)
+ .buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.ClientVersion parseDelimitedFrom(java.io.InputStream input)
+ throws java.io.IOException {
+ Builder builder = newBuilder();
+ if (builder.mergeDelimitedFrom(input)) {
+ return builder.buildParsed();
+ } else {
+ return null;
+ }
+ }
+ public static org.bitcoin.paymentchannel.Protos.ClientVersion parseDelimitedFrom(
+ java.io.InputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ Builder builder = newBuilder();
+ if (builder.mergeDelimitedFrom(input, extensionRegistry)) {
+ return builder.buildParsed();
+ } else {
+ return null;
+ }
+ }
+ public static org.bitcoin.paymentchannel.Protos.ClientVersion parseFrom(
+ com.google.protobuf.CodedInputStream input)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input).buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.ClientVersion parseFrom(
+ com.google.protobuf.CodedInputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input, extensionRegistry)
+ .buildParsed();
+ }
+
+ public static Builder newBuilder() { return Builder.create(); }
+ public Builder newBuilderForType() { return newBuilder(); }
+ public static Builder newBuilder(org.bitcoin.paymentchannel.Protos.ClientVersion prototype) {
+ return newBuilder().mergeFrom(prototype);
+ }
+ public Builder toBuilder() { return newBuilder(this); }
+
+ @java.lang.Override
+ protected Builder newBuilderForType(
+ com.google.protobuf.GeneratedMessage.BuilderParent parent) {
+ Builder builder = new Builder(parent);
+ return builder;
+ }
+ public static final class Builder extends
+ com.google.protobuf.GeneratedMessage.Builder
+ implements org.bitcoin.paymentchannel.Protos.ClientVersionOrBuilder {
+ public static final com.google.protobuf.Descriptors.Descriptor
+ getDescriptor() {
+ return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_ClientVersion_descriptor;
+ }
+
+ protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
+ internalGetFieldAccessorTable() {
+ return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_ClientVersion_fieldAccessorTable;
+ }
+
+ // Construct using org.bitcoin.paymentchannel.Protos.ClientVersion.newBuilder()
+ private Builder() {
+ maybeForceBuilderInitialization();
+ }
+
+ private Builder(BuilderParent parent) {
+ super(parent);
+ maybeForceBuilderInitialization();
+ }
+ private void maybeForceBuilderInitialization() {
+ if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
+ }
+ }
+ private static Builder create() {
+ return new Builder();
+ }
+
+ public Builder clear() {
+ super.clear();
+ major_ = 0;
+ bitField0_ = (bitField0_ & ~0x00000001);
+ minor_ = 0;
+ bitField0_ = (bitField0_ & ~0x00000002);
+ previousChannelContractHash_ = com.google.protobuf.ByteString.EMPTY;
+ bitField0_ = (bitField0_ & ~0x00000004);
+ return this;
+ }
+
+ public Builder clone() {
+ return create().mergeFrom(buildPartial());
+ }
+
+ public com.google.protobuf.Descriptors.Descriptor
+ getDescriptorForType() {
+ return org.bitcoin.paymentchannel.Protos.ClientVersion.getDescriptor();
+ }
+
+ public org.bitcoin.paymentchannel.Protos.ClientVersion getDefaultInstanceForType() {
+ return org.bitcoin.paymentchannel.Protos.ClientVersion.getDefaultInstance();
+ }
+
+ public org.bitcoin.paymentchannel.Protos.ClientVersion build() {
+ org.bitcoin.paymentchannel.Protos.ClientVersion result = buildPartial();
+ if (!result.isInitialized()) {
+ throw newUninitializedMessageException(result);
+ }
+ return result;
+ }
+
+ private org.bitcoin.paymentchannel.Protos.ClientVersion buildParsed()
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ org.bitcoin.paymentchannel.Protos.ClientVersion result = buildPartial();
+ if (!result.isInitialized()) {
+ throw newUninitializedMessageException(
+ result).asInvalidProtocolBufferException();
+ }
+ return result;
+ }
+
+ public org.bitcoin.paymentchannel.Protos.ClientVersion buildPartial() {
+ org.bitcoin.paymentchannel.Protos.ClientVersion result = new org.bitcoin.paymentchannel.Protos.ClientVersion(this);
+ int from_bitField0_ = bitField0_;
+ int to_bitField0_ = 0;
+ if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
+ to_bitField0_ |= 0x00000001;
+ }
+ result.major_ = major_;
+ if (((from_bitField0_ & 0x00000002) == 0x00000002)) {
+ to_bitField0_ |= 0x00000002;
+ }
+ result.minor_ = minor_;
+ if (((from_bitField0_ & 0x00000004) == 0x00000004)) {
+ to_bitField0_ |= 0x00000004;
+ }
+ result.previousChannelContractHash_ = previousChannelContractHash_;
+ result.bitField0_ = to_bitField0_;
+ onBuilt();
+ return result;
+ }
+
+ public Builder mergeFrom(com.google.protobuf.Message other) {
+ if (other instanceof org.bitcoin.paymentchannel.Protos.ClientVersion) {
+ return mergeFrom((org.bitcoin.paymentchannel.Protos.ClientVersion)other);
+ } else {
+ super.mergeFrom(other);
+ return this;
+ }
+ }
+
+ public Builder mergeFrom(org.bitcoin.paymentchannel.Protos.ClientVersion other) {
+ if (other == org.bitcoin.paymentchannel.Protos.ClientVersion.getDefaultInstance()) return this;
+ if (other.hasMajor()) {
+ setMajor(other.getMajor());
+ }
+ if (other.hasMinor()) {
+ setMinor(other.getMinor());
+ }
+ if (other.hasPreviousChannelContractHash()) {
+ setPreviousChannelContractHash(other.getPreviousChannelContractHash());
+ }
+ this.mergeUnknownFields(other.getUnknownFields());
+ return this;
+ }
+
+ public final boolean isInitialized() {
+ if (!hasMajor()) {
+
+ return false;
+ }
+ return true;
+ }
+
+ public Builder mergeFrom(
+ com.google.protobuf.CodedInputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+ com.google.protobuf.UnknownFieldSet.newBuilder(
+ this.getUnknownFields());
+ while (true) {
+ int tag = input.readTag();
+ switch (tag) {
+ case 0:
+ this.setUnknownFields(unknownFields.build());
+ onChanged();
+ return this;
+ default: {
+ if (!parseUnknownField(input, unknownFields,
+ extensionRegistry, tag)) {
+ this.setUnknownFields(unknownFields.build());
+ onChanged();
+ return this;
+ }
+ break;
+ }
+ case 8: {
+ bitField0_ |= 0x00000001;
+ major_ = input.readInt32();
+ break;
+ }
+ case 16: {
+ bitField0_ |= 0x00000002;
+ minor_ = input.readInt32();
+ break;
+ }
+ case 26: {
+ bitField0_ |= 0x00000004;
+ previousChannelContractHash_ = input.readBytes();
+ break;
+ }
+ }
+ }
+ }
+
+ private int bitField0_;
+
+ // required int32 major = 1;
+ private int major_ ;
+ public boolean hasMajor() {
+ return ((bitField0_ & 0x00000001) == 0x00000001);
+ }
+ public int getMajor() {
+ return major_;
+ }
+ public Builder setMajor(int value) {
+ bitField0_ |= 0x00000001;
+ major_ = value;
+ onChanged();
+ return this;
+ }
+ public Builder clearMajor() {
+ bitField0_ = (bitField0_ & ~0x00000001);
+ major_ = 0;
+ onChanged();
+ return this;
+ }
+
+ // optional int32 minor = 2 [default = 0];
+ private int minor_ ;
+ public boolean hasMinor() {
+ return ((bitField0_ & 0x00000002) == 0x00000002);
+ }
+ public int getMinor() {
+ return minor_;
+ }
+ public Builder setMinor(int value) {
+ bitField0_ |= 0x00000002;
+ minor_ = value;
+ onChanged();
+ return this;
+ }
+ public Builder clearMinor() {
+ bitField0_ = (bitField0_ & ~0x00000002);
+ minor_ = 0;
+ onChanged();
+ return this;
+ }
+
+ // optional bytes previous_channel_contract_hash = 3;
+ private com.google.protobuf.ByteString previousChannelContractHash_ = com.google.protobuf.ByteString.EMPTY;
+ public boolean hasPreviousChannelContractHash() {
+ return ((bitField0_ & 0x00000004) == 0x00000004);
+ }
+ public com.google.protobuf.ByteString getPreviousChannelContractHash() {
+ return previousChannelContractHash_;
+ }
+ public Builder setPreviousChannelContractHash(com.google.protobuf.ByteString value) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ bitField0_ |= 0x00000004;
+ previousChannelContractHash_ = value;
+ onChanged();
+ return this;
+ }
+ public Builder clearPreviousChannelContractHash() {
+ bitField0_ = (bitField0_ & ~0x00000004);
+ previousChannelContractHash_ = getDefaultInstance().getPreviousChannelContractHash();
+ onChanged();
+ return this;
+ }
+
+ // @@protoc_insertion_point(builder_scope:paymentchannels.ClientVersion)
+ }
+
+ static {
+ defaultInstance = new ClientVersion(true);
+ defaultInstance.initFields();
+ }
+
+ // @@protoc_insertion_point(class_scope:paymentchannels.ClientVersion)
+ }
+
+ public interface ServerVersionOrBuilder
+ extends com.google.protobuf.MessageOrBuilder {
+
+ // required int32 major = 1;
+ boolean hasMajor();
+ int getMajor();
+
+ // optional int32 minor = 2 [default = 0];
+ boolean hasMinor();
+ int getMinor();
+ }
+ public static final class ServerVersion extends
+ com.google.protobuf.GeneratedMessage
+ implements ServerVersionOrBuilder {
+ // Use ServerVersion.newBuilder() to construct.
+ private ServerVersion(Builder builder) {
+ super(builder);
+ }
+ private ServerVersion(boolean noInit) {}
+
+ private static final ServerVersion defaultInstance;
+ public static ServerVersion getDefaultInstance() {
+ return defaultInstance;
+ }
+
+ public ServerVersion getDefaultInstanceForType() {
+ return defaultInstance;
+ }
+
+ public static final com.google.protobuf.Descriptors.Descriptor
+ getDescriptor() {
+ return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_ServerVersion_descriptor;
+ }
+
+ protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
+ internalGetFieldAccessorTable() {
+ return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_ServerVersion_fieldAccessorTable;
+ }
+
+ private int bitField0_;
+ // required int32 major = 1;
+ public static final int MAJOR_FIELD_NUMBER = 1;
+ private int major_;
+ public boolean hasMajor() {
+ return ((bitField0_ & 0x00000001) == 0x00000001);
+ }
+ public int getMajor() {
+ return major_;
+ }
+
+ // optional int32 minor = 2 [default = 0];
+ public static final int MINOR_FIELD_NUMBER = 2;
+ private int minor_;
+ public boolean hasMinor() {
+ return ((bitField0_ & 0x00000002) == 0x00000002);
+ }
+ public int getMinor() {
+ return minor_;
+ }
+
+ private void initFields() {
+ major_ = 0;
+ minor_ = 0;
+ }
+ private byte memoizedIsInitialized = -1;
+ public final boolean isInitialized() {
+ byte isInitialized = memoizedIsInitialized;
+ if (isInitialized != -1) return isInitialized == 1;
+
+ if (!hasMajor()) {
+ memoizedIsInitialized = 0;
+ return false;
+ }
+ memoizedIsInitialized = 1;
+ return true;
+ }
+
+ public void writeTo(com.google.protobuf.CodedOutputStream output)
+ throws java.io.IOException {
+ getSerializedSize();
+ if (((bitField0_ & 0x00000001) == 0x00000001)) {
+ output.writeInt32(1, major_);
+ }
+ if (((bitField0_ & 0x00000002) == 0x00000002)) {
+ output.writeInt32(2, minor_);
+ }
+ getUnknownFields().writeTo(output);
+ }
+
+ private int memoizedSerializedSize = -1;
+ public int getSerializedSize() {
+ int size = memoizedSerializedSize;
+ if (size != -1) return size;
+
+ size = 0;
+ if (((bitField0_ & 0x00000001) == 0x00000001)) {
+ size += com.google.protobuf.CodedOutputStream
+ .computeInt32Size(1, major_);
+ }
+ if (((bitField0_ & 0x00000002) == 0x00000002)) {
+ size += com.google.protobuf.CodedOutputStream
+ .computeInt32Size(2, minor_);
+ }
+ size += getUnknownFields().getSerializedSize();
+ memoizedSerializedSize = size;
+ return size;
+ }
+
+ private static final long serialVersionUID = 0L;
+ @java.lang.Override
+ protected java.lang.Object writeReplace()
+ throws java.io.ObjectStreamException {
+ return super.writeReplace();
+ }
+
+ public static org.bitcoin.paymentchannel.Protos.ServerVersion parseFrom(
+ com.google.protobuf.ByteString data)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data).buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.ServerVersion parseFrom(
+ com.google.protobuf.ByteString data,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data, extensionRegistry)
+ .buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.ServerVersion parseFrom(byte[] data)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data).buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.ServerVersion parseFrom(
+ byte[] data,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data, extensionRegistry)
+ .buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.ServerVersion parseFrom(java.io.InputStream input)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input).buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.ServerVersion parseFrom(
+ java.io.InputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input, extensionRegistry)
+ .buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.ServerVersion parseDelimitedFrom(java.io.InputStream input)
+ throws java.io.IOException {
+ Builder builder = newBuilder();
+ if (builder.mergeDelimitedFrom(input)) {
+ return builder.buildParsed();
+ } else {
+ return null;
+ }
+ }
+ public static org.bitcoin.paymentchannel.Protos.ServerVersion parseDelimitedFrom(
+ java.io.InputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ Builder builder = newBuilder();
+ if (builder.mergeDelimitedFrom(input, extensionRegistry)) {
+ return builder.buildParsed();
+ } else {
+ return null;
+ }
+ }
+ public static org.bitcoin.paymentchannel.Protos.ServerVersion parseFrom(
+ com.google.protobuf.CodedInputStream input)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input).buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.ServerVersion parseFrom(
+ com.google.protobuf.CodedInputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input, extensionRegistry)
+ .buildParsed();
+ }
+
+ public static Builder newBuilder() { return Builder.create(); }
+ public Builder newBuilderForType() { return newBuilder(); }
+ public static Builder newBuilder(org.bitcoin.paymentchannel.Protos.ServerVersion prototype) {
+ return newBuilder().mergeFrom(prototype);
+ }
+ public Builder toBuilder() { return newBuilder(this); }
+
+ @java.lang.Override
+ protected Builder newBuilderForType(
+ com.google.protobuf.GeneratedMessage.BuilderParent parent) {
+ Builder builder = new Builder(parent);
+ return builder;
+ }
+ public static final class Builder extends
+ com.google.protobuf.GeneratedMessage.Builder
+ implements org.bitcoin.paymentchannel.Protos.ServerVersionOrBuilder {
+ public static final com.google.protobuf.Descriptors.Descriptor
+ getDescriptor() {
+ return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_ServerVersion_descriptor;
+ }
+
+ protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
+ internalGetFieldAccessorTable() {
+ return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_ServerVersion_fieldAccessorTable;
+ }
+
+ // Construct using org.bitcoin.paymentchannel.Protos.ServerVersion.newBuilder()
+ private Builder() {
+ maybeForceBuilderInitialization();
+ }
+
+ private Builder(BuilderParent parent) {
+ super(parent);
+ maybeForceBuilderInitialization();
+ }
+ private void maybeForceBuilderInitialization() {
+ if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
+ }
+ }
+ private static Builder create() {
+ return new Builder();
+ }
+
+ public Builder clear() {
+ super.clear();
+ major_ = 0;
+ bitField0_ = (bitField0_ & ~0x00000001);
+ minor_ = 0;
+ bitField0_ = (bitField0_ & ~0x00000002);
+ return this;
+ }
+
+ public Builder clone() {
+ return create().mergeFrom(buildPartial());
+ }
+
+ public com.google.protobuf.Descriptors.Descriptor
+ getDescriptorForType() {
+ return org.bitcoin.paymentchannel.Protos.ServerVersion.getDescriptor();
+ }
+
+ public org.bitcoin.paymentchannel.Protos.ServerVersion getDefaultInstanceForType() {
+ return org.bitcoin.paymentchannel.Protos.ServerVersion.getDefaultInstance();
+ }
+
+ public org.bitcoin.paymentchannel.Protos.ServerVersion build() {
+ org.bitcoin.paymentchannel.Protos.ServerVersion result = buildPartial();
+ if (!result.isInitialized()) {
+ throw newUninitializedMessageException(result);
+ }
+ return result;
+ }
+
+ private org.bitcoin.paymentchannel.Protos.ServerVersion buildParsed()
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ org.bitcoin.paymentchannel.Protos.ServerVersion result = buildPartial();
+ if (!result.isInitialized()) {
+ throw newUninitializedMessageException(
+ result).asInvalidProtocolBufferException();
+ }
+ return result;
+ }
+
+ public org.bitcoin.paymentchannel.Protos.ServerVersion buildPartial() {
+ org.bitcoin.paymentchannel.Protos.ServerVersion result = new org.bitcoin.paymentchannel.Protos.ServerVersion(this);
+ int from_bitField0_ = bitField0_;
+ int to_bitField0_ = 0;
+ if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
+ to_bitField0_ |= 0x00000001;
+ }
+ result.major_ = major_;
+ if (((from_bitField0_ & 0x00000002) == 0x00000002)) {
+ to_bitField0_ |= 0x00000002;
+ }
+ result.minor_ = minor_;
+ result.bitField0_ = to_bitField0_;
+ onBuilt();
+ return result;
+ }
+
+ public Builder mergeFrom(com.google.protobuf.Message other) {
+ if (other instanceof org.bitcoin.paymentchannel.Protos.ServerVersion) {
+ return mergeFrom((org.bitcoin.paymentchannel.Protos.ServerVersion)other);
+ } else {
+ super.mergeFrom(other);
+ return this;
+ }
+ }
+
+ public Builder mergeFrom(org.bitcoin.paymentchannel.Protos.ServerVersion other) {
+ if (other == org.bitcoin.paymentchannel.Protos.ServerVersion.getDefaultInstance()) return this;
+ if (other.hasMajor()) {
+ setMajor(other.getMajor());
+ }
+ if (other.hasMinor()) {
+ setMinor(other.getMinor());
+ }
+ this.mergeUnknownFields(other.getUnknownFields());
+ return this;
+ }
+
+ public final boolean isInitialized() {
+ if (!hasMajor()) {
+
+ return false;
+ }
+ return true;
+ }
+
+ public Builder mergeFrom(
+ com.google.protobuf.CodedInputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+ com.google.protobuf.UnknownFieldSet.newBuilder(
+ this.getUnknownFields());
+ while (true) {
+ int tag = input.readTag();
+ switch (tag) {
+ case 0:
+ this.setUnknownFields(unknownFields.build());
+ onChanged();
+ return this;
+ default: {
+ if (!parseUnknownField(input, unknownFields,
+ extensionRegistry, tag)) {
+ this.setUnknownFields(unknownFields.build());
+ onChanged();
+ return this;
+ }
+ break;
+ }
+ case 8: {
+ bitField0_ |= 0x00000001;
+ major_ = input.readInt32();
+ break;
+ }
+ case 16: {
+ bitField0_ |= 0x00000002;
+ minor_ = input.readInt32();
+ break;
+ }
+ }
+ }
+ }
+
+ private int bitField0_;
+
+ // required int32 major = 1;
+ private int major_ ;
+ public boolean hasMajor() {
+ return ((bitField0_ & 0x00000001) == 0x00000001);
+ }
+ public int getMajor() {
+ return major_;
+ }
+ public Builder setMajor(int value) {
+ bitField0_ |= 0x00000001;
+ major_ = value;
+ onChanged();
+ return this;
+ }
+ public Builder clearMajor() {
+ bitField0_ = (bitField0_ & ~0x00000001);
+ major_ = 0;
+ onChanged();
+ return this;
+ }
+
+ // optional int32 minor = 2 [default = 0];
+ private int minor_ ;
+ public boolean hasMinor() {
+ return ((bitField0_ & 0x00000002) == 0x00000002);
+ }
+ public int getMinor() {
+ return minor_;
+ }
+ public Builder setMinor(int value) {
+ bitField0_ |= 0x00000002;
+ minor_ = value;
+ onChanged();
+ return this;
+ }
+ public Builder clearMinor() {
+ bitField0_ = (bitField0_ & ~0x00000002);
+ minor_ = 0;
+ onChanged();
+ return this;
+ }
+
+ // @@protoc_insertion_point(builder_scope:paymentchannels.ServerVersion)
+ }
+
+ static {
+ defaultInstance = new ServerVersion(true);
+ defaultInstance.initFields();
+ }
+
+ // @@protoc_insertion_point(class_scope:paymentchannels.ServerVersion)
+ }
+
+ public interface InitiateOrBuilder
+ extends com.google.protobuf.MessageOrBuilder {
+
+ // required bytes multisig_key = 1;
+ boolean hasMultisigKey();
+ com.google.protobuf.ByteString getMultisigKey();
+
+ // required uint64 min_accepted_channel_size = 2;
+ boolean hasMinAcceptedChannelSize();
+ long getMinAcceptedChannelSize();
+
+ // required uint64 expire_time_secs = 3;
+ boolean hasExpireTimeSecs();
+ long getExpireTimeSecs();
+ }
+ public static final class Initiate extends
+ com.google.protobuf.GeneratedMessage
+ implements InitiateOrBuilder {
+ // Use Initiate.newBuilder() to construct.
+ private Initiate(Builder builder) {
+ super(builder);
+ }
+ private Initiate(boolean noInit) {}
+
+ private static final Initiate defaultInstance;
+ public static Initiate getDefaultInstance() {
+ return defaultInstance;
+ }
+
+ public Initiate getDefaultInstanceForType() {
+ return defaultInstance;
+ }
+
+ public static final com.google.protobuf.Descriptors.Descriptor
+ getDescriptor() {
+ return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_Initiate_descriptor;
+ }
+
+ protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
+ internalGetFieldAccessorTable() {
+ return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_Initiate_fieldAccessorTable;
+ }
+
+ private int bitField0_;
+ // required bytes multisig_key = 1;
+ public static final int MULTISIG_KEY_FIELD_NUMBER = 1;
+ private com.google.protobuf.ByteString multisigKey_;
+ public boolean hasMultisigKey() {
+ return ((bitField0_ & 0x00000001) == 0x00000001);
+ }
+ public com.google.protobuf.ByteString getMultisigKey() {
+ return multisigKey_;
+ }
+
+ // required uint64 min_accepted_channel_size = 2;
+ public static final int MIN_ACCEPTED_CHANNEL_SIZE_FIELD_NUMBER = 2;
+ private long minAcceptedChannelSize_;
+ public boolean hasMinAcceptedChannelSize() {
+ return ((bitField0_ & 0x00000002) == 0x00000002);
+ }
+ public long getMinAcceptedChannelSize() {
+ return minAcceptedChannelSize_;
+ }
+
+ // required uint64 expire_time_secs = 3;
+ public static final int EXPIRE_TIME_SECS_FIELD_NUMBER = 3;
+ private long expireTimeSecs_;
+ public boolean hasExpireTimeSecs() {
+ return ((bitField0_ & 0x00000004) == 0x00000004);
+ }
+ public long getExpireTimeSecs() {
+ return expireTimeSecs_;
+ }
+
+ private void initFields() {
+ multisigKey_ = com.google.protobuf.ByteString.EMPTY;
+ minAcceptedChannelSize_ = 0L;
+ expireTimeSecs_ = 0L;
+ }
+ private byte memoizedIsInitialized = -1;
+ public final boolean isInitialized() {
+ byte isInitialized = memoizedIsInitialized;
+ if (isInitialized != -1) return isInitialized == 1;
+
+ if (!hasMultisigKey()) {
+ memoizedIsInitialized = 0;
+ return false;
+ }
+ if (!hasMinAcceptedChannelSize()) {
+ memoizedIsInitialized = 0;
+ return false;
+ }
+ if (!hasExpireTimeSecs()) {
+ memoizedIsInitialized = 0;
+ return false;
+ }
+ memoizedIsInitialized = 1;
+ return true;
+ }
+
+ public void writeTo(com.google.protobuf.CodedOutputStream output)
+ throws java.io.IOException {
+ getSerializedSize();
+ if (((bitField0_ & 0x00000001) == 0x00000001)) {
+ output.writeBytes(1, multisigKey_);
+ }
+ if (((bitField0_ & 0x00000002) == 0x00000002)) {
+ output.writeUInt64(2, minAcceptedChannelSize_);
+ }
+ if (((bitField0_ & 0x00000004) == 0x00000004)) {
+ output.writeUInt64(3, expireTimeSecs_);
+ }
+ getUnknownFields().writeTo(output);
+ }
+
+ private int memoizedSerializedSize = -1;
+ public int getSerializedSize() {
+ int size = memoizedSerializedSize;
+ if (size != -1) return size;
+
+ size = 0;
+ if (((bitField0_ & 0x00000001) == 0x00000001)) {
+ size += com.google.protobuf.CodedOutputStream
+ .computeBytesSize(1, multisigKey_);
+ }
+ if (((bitField0_ & 0x00000002) == 0x00000002)) {
+ size += com.google.protobuf.CodedOutputStream
+ .computeUInt64Size(2, minAcceptedChannelSize_);
+ }
+ if (((bitField0_ & 0x00000004) == 0x00000004)) {
+ size += com.google.protobuf.CodedOutputStream
+ .computeUInt64Size(3, expireTimeSecs_);
+ }
+ size += getUnknownFields().getSerializedSize();
+ memoizedSerializedSize = size;
+ return size;
+ }
+
+ private static final long serialVersionUID = 0L;
+ @java.lang.Override
+ protected java.lang.Object writeReplace()
+ throws java.io.ObjectStreamException {
+ return super.writeReplace();
+ }
+
+ public static org.bitcoin.paymentchannel.Protos.Initiate parseFrom(
+ com.google.protobuf.ByteString data)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data).buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.Initiate parseFrom(
+ com.google.protobuf.ByteString data,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data, extensionRegistry)
+ .buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.Initiate parseFrom(byte[] data)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data).buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.Initiate parseFrom(
+ byte[] data,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data, extensionRegistry)
+ .buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.Initiate parseFrom(java.io.InputStream input)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input).buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.Initiate parseFrom(
+ java.io.InputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input, extensionRegistry)
+ .buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.Initiate parseDelimitedFrom(java.io.InputStream input)
+ throws java.io.IOException {
+ Builder builder = newBuilder();
+ if (builder.mergeDelimitedFrom(input)) {
+ return builder.buildParsed();
+ } else {
+ return null;
+ }
+ }
+ public static org.bitcoin.paymentchannel.Protos.Initiate parseDelimitedFrom(
+ java.io.InputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ Builder builder = newBuilder();
+ if (builder.mergeDelimitedFrom(input, extensionRegistry)) {
+ return builder.buildParsed();
+ } else {
+ return null;
+ }
+ }
+ public static org.bitcoin.paymentchannel.Protos.Initiate parseFrom(
+ com.google.protobuf.CodedInputStream input)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input).buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.Initiate parseFrom(
+ com.google.protobuf.CodedInputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input, extensionRegistry)
+ .buildParsed();
+ }
+
+ public static Builder newBuilder() { return Builder.create(); }
+ public Builder newBuilderForType() { return newBuilder(); }
+ public static Builder newBuilder(org.bitcoin.paymentchannel.Protos.Initiate prototype) {
+ return newBuilder().mergeFrom(prototype);
+ }
+ public Builder toBuilder() { return newBuilder(this); }
+
+ @java.lang.Override
+ protected Builder newBuilderForType(
+ com.google.protobuf.GeneratedMessage.BuilderParent parent) {
+ Builder builder = new Builder(parent);
+ return builder;
+ }
+ public static final class Builder extends
+ com.google.protobuf.GeneratedMessage.Builder
+ implements org.bitcoin.paymentchannel.Protos.InitiateOrBuilder {
+ public static final com.google.protobuf.Descriptors.Descriptor
+ getDescriptor() {
+ return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_Initiate_descriptor;
+ }
+
+ protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
+ internalGetFieldAccessorTable() {
+ return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_Initiate_fieldAccessorTable;
+ }
+
+ // Construct using org.bitcoin.paymentchannel.Protos.Initiate.newBuilder()
+ private Builder() {
+ maybeForceBuilderInitialization();
+ }
+
+ private Builder(BuilderParent parent) {
+ super(parent);
+ maybeForceBuilderInitialization();
+ }
+ private void maybeForceBuilderInitialization() {
+ if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
+ }
+ }
+ private static Builder create() {
+ return new Builder();
+ }
+
+ public Builder clear() {
+ super.clear();
+ multisigKey_ = com.google.protobuf.ByteString.EMPTY;
+ bitField0_ = (bitField0_ & ~0x00000001);
+ minAcceptedChannelSize_ = 0L;
+ bitField0_ = (bitField0_ & ~0x00000002);
+ expireTimeSecs_ = 0L;
+ bitField0_ = (bitField0_ & ~0x00000004);
+ return this;
+ }
+
+ public Builder clone() {
+ return create().mergeFrom(buildPartial());
+ }
+
+ public com.google.protobuf.Descriptors.Descriptor
+ getDescriptorForType() {
+ return org.bitcoin.paymentchannel.Protos.Initiate.getDescriptor();
+ }
+
+ public org.bitcoin.paymentchannel.Protos.Initiate getDefaultInstanceForType() {
+ return org.bitcoin.paymentchannel.Protos.Initiate.getDefaultInstance();
+ }
+
+ public org.bitcoin.paymentchannel.Protos.Initiate build() {
+ org.bitcoin.paymentchannel.Protos.Initiate result = buildPartial();
+ if (!result.isInitialized()) {
+ throw newUninitializedMessageException(result);
+ }
+ return result;
+ }
+
+ private org.bitcoin.paymentchannel.Protos.Initiate buildParsed()
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ org.bitcoin.paymentchannel.Protos.Initiate result = buildPartial();
+ if (!result.isInitialized()) {
+ throw newUninitializedMessageException(
+ result).asInvalidProtocolBufferException();
+ }
+ return result;
+ }
+
+ public org.bitcoin.paymentchannel.Protos.Initiate buildPartial() {
+ org.bitcoin.paymentchannel.Protos.Initiate result = new org.bitcoin.paymentchannel.Protos.Initiate(this);
+ int from_bitField0_ = bitField0_;
+ int to_bitField0_ = 0;
+ if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
+ to_bitField0_ |= 0x00000001;
+ }
+ result.multisigKey_ = multisigKey_;
+ if (((from_bitField0_ & 0x00000002) == 0x00000002)) {
+ to_bitField0_ |= 0x00000002;
+ }
+ result.minAcceptedChannelSize_ = minAcceptedChannelSize_;
+ if (((from_bitField0_ & 0x00000004) == 0x00000004)) {
+ to_bitField0_ |= 0x00000004;
+ }
+ result.expireTimeSecs_ = expireTimeSecs_;
+ result.bitField0_ = to_bitField0_;
+ onBuilt();
+ return result;
+ }
+
+ public Builder mergeFrom(com.google.protobuf.Message other) {
+ if (other instanceof org.bitcoin.paymentchannel.Protos.Initiate) {
+ return mergeFrom((org.bitcoin.paymentchannel.Protos.Initiate)other);
+ } else {
+ super.mergeFrom(other);
+ return this;
+ }
+ }
+
+ public Builder mergeFrom(org.bitcoin.paymentchannel.Protos.Initiate other) {
+ if (other == org.bitcoin.paymentchannel.Protos.Initiate.getDefaultInstance()) return this;
+ if (other.hasMultisigKey()) {
+ setMultisigKey(other.getMultisigKey());
+ }
+ if (other.hasMinAcceptedChannelSize()) {
+ setMinAcceptedChannelSize(other.getMinAcceptedChannelSize());
+ }
+ if (other.hasExpireTimeSecs()) {
+ setExpireTimeSecs(other.getExpireTimeSecs());
+ }
+ this.mergeUnknownFields(other.getUnknownFields());
+ return this;
+ }
+
+ public final boolean isInitialized() {
+ if (!hasMultisigKey()) {
+
+ return false;
+ }
+ if (!hasMinAcceptedChannelSize()) {
+
+ return false;
+ }
+ if (!hasExpireTimeSecs()) {
+
+ return false;
+ }
+ return true;
+ }
+
+ public Builder mergeFrom(
+ com.google.protobuf.CodedInputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+ com.google.protobuf.UnknownFieldSet.newBuilder(
+ this.getUnknownFields());
+ while (true) {
+ int tag = input.readTag();
+ switch (tag) {
+ case 0:
+ this.setUnknownFields(unknownFields.build());
+ onChanged();
+ return this;
+ default: {
+ if (!parseUnknownField(input, unknownFields,
+ extensionRegistry, tag)) {
+ this.setUnknownFields(unknownFields.build());
+ onChanged();
+ return this;
+ }
+ break;
+ }
+ case 10: {
+ bitField0_ |= 0x00000001;
+ multisigKey_ = input.readBytes();
+ break;
+ }
+ case 16: {
+ bitField0_ |= 0x00000002;
+ minAcceptedChannelSize_ = input.readUInt64();
+ break;
+ }
+ case 24: {
+ bitField0_ |= 0x00000004;
+ expireTimeSecs_ = input.readUInt64();
+ break;
+ }
+ }
+ }
+ }
+
+ private int bitField0_;
+
+ // required bytes multisig_key = 1;
+ private com.google.protobuf.ByteString multisigKey_ = com.google.protobuf.ByteString.EMPTY;
+ public boolean hasMultisigKey() {
+ return ((bitField0_ & 0x00000001) == 0x00000001);
+ }
+ public com.google.protobuf.ByteString getMultisigKey() {
+ return multisigKey_;
+ }
+ public Builder setMultisigKey(com.google.protobuf.ByteString value) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ bitField0_ |= 0x00000001;
+ multisigKey_ = value;
+ onChanged();
+ return this;
+ }
+ public Builder clearMultisigKey() {
+ bitField0_ = (bitField0_ & ~0x00000001);
+ multisigKey_ = getDefaultInstance().getMultisigKey();
+ onChanged();
+ return this;
+ }
+
+ // required uint64 min_accepted_channel_size = 2;
+ private long minAcceptedChannelSize_ ;
+ public boolean hasMinAcceptedChannelSize() {
+ return ((bitField0_ & 0x00000002) == 0x00000002);
+ }
+ public long getMinAcceptedChannelSize() {
+ return minAcceptedChannelSize_;
+ }
+ public Builder setMinAcceptedChannelSize(long value) {
+ bitField0_ |= 0x00000002;
+ minAcceptedChannelSize_ = value;
+ onChanged();
+ return this;
+ }
+ public Builder clearMinAcceptedChannelSize() {
+ bitField0_ = (bitField0_ & ~0x00000002);
+ minAcceptedChannelSize_ = 0L;
+ onChanged();
+ return this;
+ }
+
+ // required uint64 expire_time_secs = 3;
+ private long expireTimeSecs_ ;
+ public boolean hasExpireTimeSecs() {
+ return ((bitField0_ & 0x00000004) == 0x00000004);
+ }
+ public long getExpireTimeSecs() {
+ return expireTimeSecs_;
+ }
+ public Builder setExpireTimeSecs(long value) {
+ bitField0_ |= 0x00000004;
+ expireTimeSecs_ = value;
+ onChanged();
+ return this;
+ }
+ public Builder clearExpireTimeSecs() {
+ bitField0_ = (bitField0_ & ~0x00000004);
+ expireTimeSecs_ = 0L;
+ onChanged();
+ return this;
+ }
+
+ // @@protoc_insertion_point(builder_scope:paymentchannels.Initiate)
+ }
+
+ static {
+ defaultInstance = new Initiate(true);
+ defaultInstance.initFields();
+ }
+
+ // @@protoc_insertion_point(class_scope:paymentchannels.Initiate)
+ }
+
+ public interface ProvideRefundOrBuilder
+ extends com.google.protobuf.MessageOrBuilder {
+
+ // required bytes multisig_key = 1;
+ boolean hasMultisigKey();
+ com.google.protobuf.ByteString getMultisigKey();
+
+ // required bytes tx = 2;
+ boolean hasTx();
+ com.google.protobuf.ByteString getTx();
+ }
+ public static final class ProvideRefund extends
+ com.google.protobuf.GeneratedMessage
+ implements ProvideRefundOrBuilder {
+ // Use ProvideRefund.newBuilder() to construct.
+ private ProvideRefund(Builder builder) {
+ super(builder);
+ }
+ private ProvideRefund(boolean noInit) {}
+
+ private static final ProvideRefund defaultInstance;
+ public static ProvideRefund getDefaultInstance() {
+ return defaultInstance;
+ }
+
+ public ProvideRefund getDefaultInstanceForType() {
+ return defaultInstance;
+ }
+
+ public static final com.google.protobuf.Descriptors.Descriptor
+ getDescriptor() {
+ return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_ProvideRefund_descriptor;
+ }
+
+ protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
+ internalGetFieldAccessorTable() {
+ return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_ProvideRefund_fieldAccessorTable;
+ }
+
+ private int bitField0_;
+ // required bytes multisig_key = 1;
+ public static final int MULTISIG_KEY_FIELD_NUMBER = 1;
+ private com.google.protobuf.ByteString multisigKey_;
+ public boolean hasMultisigKey() {
+ return ((bitField0_ & 0x00000001) == 0x00000001);
+ }
+ public com.google.protobuf.ByteString getMultisigKey() {
+ return multisigKey_;
+ }
+
+ // required bytes tx = 2;
+ public static final int TX_FIELD_NUMBER = 2;
+ private com.google.protobuf.ByteString tx_;
+ public boolean hasTx() {
+ return ((bitField0_ & 0x00000002) == 0x00000002);
+ }
+ public com.google.protobuf.ByteString getTx() {
+ return tx_;
+ }
+
+ private void initFields() {
+ multisigKey_ = com.google.protobuf.ByteString.EMPTY;
+ tx_ = com.google.protobuf.ByteString.EMPTY;
+ }
+ private byte memoizedIsInitialized = -1;
+ public final boolean isInitialized() {
+ byte isInitialized = memoizedIsInitialized;
+ if (isInitialized != -1) return isInitialized == 1;
+
+ if (!hasMultisigKey()) {
+ memoizedIsInitialized = 0;
+ return false;
+ }
+ if (!hasTx()) {
+ memoizedIsInitialized = 0;
+ return false;
+ }
+ memoizedIsInitialized = 1;
+ return true;
+ }
+
+ public void writeTo(com.google.protobuf.CodedOutputStream output)
+ throws java.io.IOException {
+ getSerializedSize();
+ if (((bitField0_ & 0x00000001) == 0x00000001)) {
+ output.writeBytes(1, multisigKey_);
+ }
+ if (((bitField0_ & 0x00000002) == 0x00000002)) {
+ output.writeBytes(2, tx_);
+ }
+ getUnknownFields().writeTo(output);
+ }
+
+ private int memoizedSerializedSize = -1;
+ public int getSerializedSize() {
+ int size = memoizedSerializedSize;
+ if (size != -1) return size;
+
+ size = 0;
+ if (((bitField0_ & 0x00000001) == 0x00000001)) {
+ size += com.google.protobuf.CodedOutputStream
+ .computeBytesSize(1, multisigKey_);
+ }
+ if (((bitField0_ & 0x00000002) == 0x00000002)) {
+ size += com.google.protobuf.CodedOutputStream
+ .computeBytesSize(2, tx_);
+ }
+ size += getUnknownFields().getSerializedSize();
+ memoizedSerializedSize = size;
+ return size;
+ }
+
+ private static final long serialVersionUID = 0L;
+ @java.lang.Override
+ protected java.lang.Object writeReplace()
+ throws java.io.ObjectStreamException {
+ return super.writeReplace();
+ }
+
+ public static org.bitcoin.paymentchannel.Protos.ProvideRefund parseFrom(
+ com.google.protobuf.ByteString data)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data).buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.ProvideRefund parseFrom(
+ com.google.protobuf.ByteString data,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data, extensionRegistry)
+ .buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.ProvideRefund parseFrom(byte[] data)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data).buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.ProvideRefund parseFrom(
+ byte[] data,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data, extensionRegistry)
+ .buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.ProvideRefund parseFrom(java.io.InputStream input)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input).buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.ProvideRefund parseFrom(
+ java.io.InputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input, extensionRegistry)
+ .buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.ProvideRefund parseDelimitedFrom(java.io.InputStream input)
+ throws java.io.IOException {
+ Builder builder = newBuilder();
+ if (builder.mergeDelimitedFrom(input)) {
+ return builder.buildParsed();
+ } else {
+ return null;
+ }
+ }
+ public static org.bitcoin.paymentchannel.Protos.ProvideRefund parseDelimitedFrom(
+ java.io.InputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ Builder builder = newBuilder();
+ if (builder.mergeDelimitedFrom(input, extensionRegistry)) {
+ return builder.buildParsed();
+ } else {
+ return null;
+ }
+ }
+ public static org.bitcoin.paymentchannel.Protos.ProvideRefund parseFrom(
+ com.google.protobuf.CodedInputStream input)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input).buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.ProvideRefund parseFrom(
+ com.google.protobuf.CodedInputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input, extensionRegistry)
+ .buildParsed();
+ }
+
+ public static Builder newBuilder() { return Builder.create(); }
+ public Builder newBuilderForType() { return newBuilder(); }
+ public static Builder newBuilder(org.bitcoin.paymentchannel.Protos.ProvideRefund prototype) {
+ return newBuilder().mergeFrom(prototype);
+ }
+ public Builder toBuilder() { return newBuilder(this); }
+
+ @java.lang.Override
+ protected Builder newBuilderForType(
+ com.google.protobuf.GeneratedMessage.BuilderParent parent) {
+ Builder builder = new Builder(parent);
+ return builder;
+ }
+ public static final class Builder extends
+ com.google.protobuf.GeneratedMessage.Builder
+ implements org.bitcoin.paymentchannel.Protos.ProvideRefundOrBuilder {
+ public static final com.google.protobuf.Descriptors.Descriptor
+ getDescriptor() {
+ return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_ProvideRefund_descriptor;
+ }
+
+ protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
+ internalGetFieldAccessorTable() {
+ return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_ProvideRefund_fieldAccessorTable;
+ }
+
+ // Construct using org.bitcoin.paymentchannel.Protos.ProvideRefund.newBuilder()
+ private Builder() {
+ maybeForceBuilderInitialization();
+ }
+
+ private Builder(BuilderParent parent) {
+ super(parent);
+ maybeForceBuilderInitialization();
+ }
+ private void maybeForceBuilderInitialization() {
+ if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
+ }
+ }
+ private static Builder create() {
+ return new Builder();
+ }
+
+ public Builder clear() {
+ super.clear();
+ multisigKey_ = com.google.protobuf.ByteString.EMPTY;
+ bitField0_ = (bitField0_ & ~0x00000001);
+ tx_ = com.google.protobuf.ByteString.EMPTY;
+ bitField0_ = (bitField0_ & ~0x00000002);
+ return this;
+ }
+
+ public Builder clone() {
+ return create().mergeFrom(buildPartial());
+ }
+
+ public com.google.protobuf.Descriptors.Descriptor
+ getDescriptorForType() {
+ return org.bitcoin.paymentchannel.Protos.ProvideRefund.getDescriptor();
+ }
+
+ public org.bitcoin.paymentchannel.Protos.ProvideRefund getDefaultInstanceForType() {
+ return org.bitcoin.paymentchannel.Protos.ProvideRefund.getDefaultInstance();
+ }
+
+ public org.bitcoin.paymentchannel.Protos.ProvideRefund build() {
+ org.bitcoin.paymentchannel.Protos.ProvideRefund result = buildPartial();
+ if (!result.isInitialized()) {
+ throw newUninitializedMessageException(result);
+ }
+ return result;
+ }
+
+ private org.bitcoin.paymentchannel.Protos.ProvideRefund buildParsed()
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ org.bitcoin.paymentchannel.Protos.ProvideRefund result = buildPartial();
+ if (!result.isInitialized()) {
+ throw newUninitializedMessageException(
+ result).asInvalidProtocolBufferException();
+ }
+ return result;
+ }
+
+ public org.bitcoin.paymentchannel.Protos.ProvideRefund buildPartial() {
+ org.bitcoin.paymentchannel.Protos.ProvideRefund result = new org.bitcoin.paymentchannel.Protos.ProvideRefund(this);
+ int from_bitField0_ = bitField0_;
+ int to_bitField0_ = 0;
+ if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
+ to_bitField0_ |= 0x00000001;
+ }
+ result.multisigKey_ = multisigKey_;
+ if (((from_bitField0_ & 0x00000002) == 0x00000002)) {
+ to_bitField0_ |= 0x00000002;
+ }
+ result.tx_ = tx_;
+ result.bitField0_ = to_bitField0_;
+ onBuilt();
+ return result;
+ }
+
+ public Builder mergeFrom(com.google.protobuf.Message other) {
+ if (other instanceof org.bitcoin.paymentchannel.Protos.ProvideRefund) {
+ return mergeFrom((org.bitcoin.paymentchannel.Protos.ProvideRefund)other);
+ } else {
+ super.mergeFrom(other);
+ return this;
+ }
+ }
+
+ public Builder mergeFrom(org.bitcoin.paymentchannel.Protos.ProvideRefund other) {
+ if (other == org.bitcoin.paymentchannel.Protos.ProvideRefund.getDefaultInstance()) return this;
+ if (other.hasMultisigKey()) {
+ setMultisigKey(other.getMultisigKey());
+ }
+ if (other.hasTx()) {
+ setTx(other.getTx());
+ }
+ this.mergeUnknownFields(other.getUnknownFields());
+ return this;
+ }
+
+ public final boolean isInitialized() {
+ if (!hasMultisigKey()) {
+
+ return false;
+ }
+ if (!hasTx()) {
+
+ return false;
+ }
+ return true;
+ }
+
+ public Builder mergeFrom(
+ com.google.protobuf.CodedInputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+ com.google.protobuf.UnknownFieldSet.newBuilder(
+ this.getUnknownFields());
+ while (true) {
+ int tag = input.readTag();
+ switch (tag) {
+ case 0:
+ this.setUnknownFields(unknownFields.build());
+ onChanged();
+ return this;
+ default: {
+ if (!parseUnknownField(input, unknownFields,
+ extensionRegistry, tag)) {
+ this.setUnknownFields(unknownFields.build());
+ onChanged();
+ return this;
+ }
+ break;
+ }
+ case 10: {
+ bitField0_ |= 0x00000001;
+ multisigKey_ = input.readBytes();
+ break;
+ }
+ case 18: {
+ bitField0_ |= 0x00000002;
+ tx_ = input.readBytes();
+ break;
+ }
+ }
+ }
+ }
+
+ private int bitField0_;
+
+ // required bytes multisig_key = 1;
+ private com.google.protobuf.ByteString multisigKey_ = com.google.protobuf.ByteString.EMPTY;
+ public boolean hasMultisigKey() {
+ return ((bitField0_ & 0x00000001) == 0x00000001);
+ }
+ public com.google.protobuf.ByteString getMultisigKey() {
+ return multisigKey_;
+ }
+ public Builder setMultisigKey(com.google.protobuf.ByteString value) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ bitField0_ |= 0x00000001;
+ multisigKey_ = value;
+ onChanged();
+ return this;
+ }
+ public Builder clearMultisigKey() {
+ bitField0_ = (bitField0_ & ~0x00000001);
+ multisigKey_ = getDefaultInstance().getMultisigKey();
+ onChanged();
+ return this;
+ }
+
+ // required bytes tx = 2;
+ private com.google.protobuf.ByteString tx_ = com.google.protobuf.ByteString.EMPTY;
+ public boolean hasTx() {
+ return ((bitField0_ & 0x00000002) == 0x00000002);
+ }
+ public com.google.protobuf.ByteString getTx() {
+ return tx_;
+ }
+ public Builder setTx(com.google.protobuf.ByteString value) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ bitField0_ |= 0x00000002;
+ tx_ = value;
+ onChanged();
+ return this;
+ }
+ public Builder clearTx() {
+ bitField0_ = (bitField0_ & ~0x00000002);
+ tx_ = getDefaultInstance().getTx();
+ onChanged();
+ return this;
+ }
+
+ // @@protoc_insertion_point(builder_scope:paymentchannels.ProvideRefund)
+ }
+
+ static {
+ defaultInstance = new ProvideRefund(true);
+ defaultInstance.initFields();
+ }
+
+ // @@protoc_insertion_point(class_scope:paymentchannels.ProvideRefund)
+ }
+
+ public interface ReturnRefundOrBuilder
+ extends com.google.protobuf.MessageOrBuilder {
+
+ // required bytes signature = 1;
+ boolean hasSignature();
+ com.google.protobuf.ByteString getSignature();
+ }
+ public static final class ReturnRefund extends
+ com.google.protobuf.GeneratedMessage
+ implements ReturnRefundOrBuilder {
+ // Use ReturnRefund.newBuilder() to construct.
+ private ReturnRefund(Builder builder) {
+ super(builder);
+ }
+ private ReturnRefund(boolean noInit) {}
+
+ private static final ReturnRefund defaultInstance;
+ public static ReturnRefund getDefaultInstance() {
+ return defaultInstance;
+ }
+
+ public ReturnRefund getDefaultInstanceForType() {
+ return defaultInstance;
+ }
+
+ public static final com.google.protobuf.Descriptors.Descriptor
+ getDescriptor() {
+ return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_ReturnRefund_descriptor;
+ }
+
+ protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
+ internalGetFieldAccessorTable() {
+ return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_ReturnRefund_fieldAccessorTable;
+ }
+
+ private int bitField0_;
+ // required bytes signature = 1;
+ public static final int SIGNATURE_FIELD_NUMBER = 1;
+ private com.google.protobuf.ByteString signature_;
+ public boolean hasSignature() {
+ return ((bitField0_ & 0x00000001) == 0x00000001);
+ }
+ public com.google.protobuf.ByteString getSignature() {
+ return signature_;
+ }
+
+ private void initFields() {
+ signature_ = com.google.protobuf.ByteString.EMPTY;
+ }
+ private byte memoizedIsInitialized = -1;
+ public final boolean isInitialized() {
+ byte isInitialized = memoizedIsInitialized;
+ if (isInitialized != -1) return isInitialized == 1;
+
+ if (!hasSignature()) {
+ memoizedIsInitialized = 0;
+ return false;
+ }
+ memoizedIsInitialized = 1;
+ return true;
+ }
+
+ public void writeTo(com.google.protobuf.CodedOutputStream output)
+ throws java.io.IOException {
+ getSerializedSize();
+ if (((bitField0_ & 0x00000001) == 0x00000001)) {
+ output.writeBytes(1, signature_);
+ }
+ getUnknownFields().writeTo(output);
+ }
+
+ private int memoizedSerializedSize = -1;
+ public int getSerializedSize() {
+ int size = memoizedSerializedSize;
+ if (size != -1) return size;
+
+ size = 0;
+ if (((bitField0_ & 0x00000001) == 0x00000001)) {
+ size += com.google.protobuf.CodedOutputStream
+ .computeBytesSize(1, signature_);
+ }
+ size += getUnknownFields().getSerializedSize();
+ memoizedSerializedSize = size;
+ return size;
+ }
+
+ private static final long serialVersionUID = 0L;
+ @java.lang.Override
+ protected java.lang.Object writeReplace()
+ throws java.io.ObjectStreamException {
+ return super.writeReplace();
+ }
+
+ public static org.bitcoin.paymentchannel.Protos.ReturnRefund parseFrom(
+ com.google.protobuf.ByteString data)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data).buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.ReturnRefund parseFrom(
+ com.google.protobuf.ByteString data,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data, extensionRegistry)
+ .buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.ReturnRefund parseFrom(byte[] data)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data).buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.ReturnRefund parseFrom(
+ byte[] data,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data, extensionRegistry)
+ .buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.ReturnRefund parseFrom(java.io.InputStream input)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input).buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.ReturnRefund parseFrom(
+ java.io.InputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input, extensionRegistry)
+ .buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.ReturnRefund parseDelimitedFrom(java.io.InputStream input)
+ throws java.io.IOException {
+ Builder builder = newBuilder();
+ if (builder.mergeDelimitedFrom(input)) {
+ return builder.buildParsed();
+ } else {
+ return null;
+ }
+ }
+ public static org.bitcoin.paymentchannel.Protos.ReturnRefund parseDelimitedFrom(
+ java.io.InputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ Builder builder = newBuilder();
+ if (builder.mergeDelimitedFrom(input, extensionRegistry)) {
+ return builder.buildParsed();
+ } else {
+ return null;
+ }
+ }
+ public static org.bitcoin.paymentchannel.Protos.ReturnRefund parseFrom(
+ com.google.protobuf.CodedInputStream input)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input).buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.ReturnRefund parseFrom(
+ com.google.protobuf.CodedInputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input, extensionRegistry)
+ .buildParsed();
+ }
+
+ public static Builder newBuilder() { return Builder.create(); }
+ public Builder newBuilderForType() { return newBuilder(); }
+ public static Builder newBuilder(org.bitcoin.paymentchannel.Protos.ReturnRefund prototype) {
+ return newBuilder().mergeFrom(prototype);
+ }
+ public Builder toBuilder() { return newBuilder(this); }
+
+ @java.lang.Override
+ protected Builder newBuilderForType(
+ com.google.protobuf.GeneratedMessage.BuilderParent parent) {
+ Builder builder = new Builder(parent);
+ return builder;
+ }
+ public static final class Builder extends
+ com.google.protobuf.GeneratedMessage.Builder
+ implements org.bitcoin.paymentchannel.Protos.ReturnRefundOrBuilder {
+ public static final com.google.protobuf.Descriptors.Descriptor
+ getDescriptor() {
+ return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_ReturnRefund_descriptor;
+ }
+
+ protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
+ internalGetFieldAccessorTable() {
+ return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_ReturnRefund_fieldAccessorTable;
+ }
+
+ // Construct using org.bitcoin.paymentchannel.Protos.ReturnRefund.newBuilder()
+ private Builder() {
+ maybeForceBuilderInitialization();
+ }
+
+ private Builder(BuilderParent parent) {
+ super(parent);
+ maybeForceBuilderInitialization();
+ }
+ private void maybeForceBuilderInitialization() {
+ if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
+ }
+ }
+ private static Builder create() {
+ return new Builder();
+ }
+
+ public Builder clear() {
+ super.clear();
+ signature_ = com.google.protobuf.ByteString.EMPTY;
+ bitField0_ = (bitField0_ & ~0x00000001);
+ return this;
+ }
+
+ public Builder clone() {
+ return create().mergeFrom(buildPartial());
+ }
+
+ public com.google.protobuf.Descriptors.Descriptor
+ getDescriptorForType() {
+ return org.bitcoin.paymentchannel.Protos.ReturnRefund.getDescriptor();
+ }
+
+ public org.bitcoin.paymentchannel.Protos.ReturnRefund getDefaultInstanceForType() {
+ return org.bitcoin.paymentchannel.Protos.ReturnRefund.getDefaultInstance();
+ }
+
+ public org.bitcoin.paymentchannel.Protos.ReturnRefund build() {
+ org.bitcoin.paymentchannel.Protos.ReturnRefund result = buildPartial();
+ if (!result.isInitialized()) {
+ throw newUninitializedMessageException(result);
+ }
+ return result;
+ }
+
+ private org.bitcoin.paymentchannel.Protos.ReturnRefund buildParsed()
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ org.bitcoin.paymentchannel.Protos.ReturnRefund result = buildPartial();
+ if (!result.isInitialized()) {
+ throw newUninitializedMessageException(
+ result).asInvalidProtocolBufferException();
+ }
+ return result;
+ }
+
+ public org.bitcoin.paymentchannel.Protos.ReturnRefund buildPartial() {
+ org.bitcoin.paymentchannel.Protos.ReturnRefund result = new org.bitcoin.paymentchannel.Protos.ReturnRefund(this);
+ int from_bitField0_ = bitField0_;
+ int to_bitField0_ = 0;
+ if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
+ to_bitField0_ |= 0x00000001;
+ }
+ result.signature_ = signature_;
+ result.bitField0_ = to_bitField0_;
+ onBuilt();
+ return result;
+ }
+
+ public Builder mergeFrom(com.google.protobuf.Message other) {
+ if (other instanceof org.bitcoin.paymentchannel.Protos.ReturnRefund) {
+ return mergeFrom((org.bitcoin.paymentchannel.Protos.ReturnRefund)other);
+ } else {
+ super.mergeFrom(other);
+ return this;
+ }
+ }
+
+ public Builder mergeFrom(org.bitcoin.paymentchannel.Protos.ReturnRefund other) {
+ if (other == org.bitcoin.paymentchannel.Protos.ReturnRefund.getDefaultInstance()) return this;
+ if (other.hasSignature()) {
+ setSignature(other.getSignature());
+ }
+ this.mergeUnknownFields(other.getUnknownFields());
+ return this;
+ }
+
+ public final boolean isInitialized() {
+ if (!hasSignature()) {
+
+ return false;
+ }
+ return true;
+ }
+
+ public Builder mergeFrom(
+ com.google.protobuf.CodedInputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+ com.google.protobuf.UnknownFieldSet.newBuilder(
+ this.getUnknownFields());
+ while (true) {
+ int tag = input.readTag();
+ switch (tag) {
+ case 0:
+ this.setUnknownFields(unknownFields.build());
+ onChanged();
+ return this;
+ default: {
+ if (!parseUnknownField(input, unknownFields,
+ extensionRegistry, tag)) {
+ this.setUnknownFields(unknownFields.build());
+ onChanged();
+ return this;
+ }
+ break;
+ }
+ case 10: {
+ bitField0_ |= 0x00000001;
+ signature_ = input.readBytes();
+ break;
+ }
+ }
+ }
+ }
+
+ private int bitField0_;
+
+ // required bytes signature = 1;
+ private com.google.protobuf.ByteString signature_ = com.google.protobuf.ByteString.EMPTY;
+ public boolean hasSignature() {
+ return ((bitField0_ & 0x00000001) == 0x00000001);
+ }
+ public com.google.protobuf.ByteString getSignature() {
+ return signature_;
+ }
+ public Builder setSignature(com.google.protobuf.ByteString value) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ bitField0_ |= 0x00000001;
+ signature_ = value;
+ onChanged();
+ return this;
+ }
+ public Builder clearSignature() {
+ bitField0_ = (bitField0_ & ~0x00000001);
+ signature_ = getDefaultInstance().getSignature();
+ onChanged();
+ return this;
+ }
+
+ // @@protoc_insertion_point(builder_scope:paymentchannels.ReturnRefund)
+ }
+
+ static {
+ defaultInstance = new ReturnRefund(true);
+ defaultInstance.initFields();
+ }
+
+ // @@protoc_insertion_point(class_scope:paymentchannels.ReturnRefund)
+ }
+
+ public interface ProvideContractOrBuilder
+ extends com.google.protobuf.MessageOrBuilder {
+
+ // required bytes tx = 1;
+ boolean hasTx();
+ com.google.protobuf.ByteString getTx();
+ }
+ public static final class ProvideContract extends
+ com.google.protobuf.GeneratedMessage
+ implements ProvideContractOrBuilder {
+ // Use ProvideContract.newBuilder() to construct.
+ private ProvideContract(Builder builder) {
+ super(builder);
+ }
+ private ProvideContract(boolean noInit) {}
+
+ private static final ProvideContract defaultInstance;
+ public static ProvideContract getDefaultInstance() {
+ return defaultInstance;
+ }
+
+ public ProvideContract getDefaultInstanceForType() {
+ return defaultInstance;
+ }
+
+ public static final com.google.protobuf.Descriptors.Descriptor
+ getDescriptor() {
+ return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_ProvideContract_descriptor;
+ }
+
+ protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
+ internalGetFieldAccessorTable() {
+ return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_ProvideContract_fieldAccessorTable;
+ }
+
+ private int bitField0_;
+ // required bytes tx = 1;
+ public static final int TX_FIELD_NUMBER = 1;
+ private com.google.protobuf.ByteString tx_;
+ public boolean hasTx() {
+ return ((bitField0_ & 0x00000001) == 0x00000001);
+ }
+ public com.google.protobuf.ByteString getTx() {
+ return tx_;
+ }
+
+ private void initFields() {
+ tx_ = com.google.protobuf.ByteString.EMPTY;
+ }
+ private byte memoizedIsInitialized = -1;
+ public final boolean isInitialized() {
+ byte isInitialized = memoizedIsInitialized;
+ if (isInitialized != -1) return isInitialized == 1;
+
+ if (!hasTx()) {
+ memoizedIsInitialized = 0;
+ return false;
+ }
+ memoizedIsInitialized = 1;
+ return true;
+ }
+
+ public void writeTo(com.google.protobuf.CodedOutputStream output)
+ throws java.io.IOException {
+ getSerializedSize();
+ if (((bitField0_ & 0x00000001) == 0x00000001)) {
+ output.writeBytes(1, tx_);
+ }
+ getUnknownFields().writeTo(output);
+ }
+
+ private int memoizedSerializedSize = -1;
+ public int getSerializedSize() {
+ int size = memoizedSerializedSize;
+ if (size != -1) return size;
+
+ size = 0;
+ if (((bitField0_ & 0x00000001) == 0x00000001)) {
+ size += com.google.protobuf.CodedOutputStream
+ .computeBytesSize(1, tx_);
+ }
+ size += getUnknownFields().getSerializedSize();
+ memoizedSerializedSize = size;
+ return size;
+ }
+
+ private static final long serialVersionUID = 0L;
+ @java.lang.Override
+ protected java.lang.Object writeReplace()
+ throws java.io.ObjectStreamException {
+ return super.writeReplace();
+ }
+
+ public static org.bitcoin.paymentchannel.Protos.ProvideContract parseFrom(
+ com.google.protobuf.ByteString data)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data).buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.ProvideContract parseFrom(
+ com.google.protobuf.ByteString data,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data, extensionRegistry)
+ .buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.ProvideContract parseFrom(byte[] data)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data).buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.ProvideContract parseFrom(
+ byte[] data,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data, extensionRegistry)
+ .buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.ProvideContract parseFrom(java.io.InputStream input)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input).buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.ProvideContract parseFrom(
+ java.io.InputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input, extensionRegistry)
+ .buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.ProvideContract parseDelimitedFrom(java.io.InputStream input)
+ throws java.io.IOException {
+ Builder builder = newBuilder();
+ if (builder.mergeDelimitedFrom(input)) {
+ return builder.buildParsed();
+ } else {
+ return null;
+ }
+ }
+ public static org.bitcoin.paymentchannel.Protos.ProvideContract parseDelimitedFrom(
+ java.io.InputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ Builder builder = newBuilder();
+ if (builder.mergeDelimitedFrom(input, extensionRegistry)) {
+ return builder.buildParsed();
+ } else {
+ return null;
+ }
+ }
+ public static org.bitcoin.paymentchannel.Protos.ProvideContract parseFrom(
+ com.google.protobuf.CodedInputStream input)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input).buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.ProvideContract parseFrom(
+ com.google.protobuf.CodedInputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input, extensionRegistry)
+ .buildParsed();
+ }
+
+ public static Builder newBuilder() { return Builder.create(); }
+ public Builder newBuilderForType() { return newBuilder(); }
+ public static Builder newBuilder(org.bitcoin.paymentchannel.Protos.ProvideContract prototype) {
+ return newBuilder().mergeFrom(prototype);
+ }
+ public Builder toBuilder() { return newBuilder(this); }
+
+ @java.lang.Override
+ protected Builder newBuilderForType(
+ com.google.protobuf.GeneratedMessage.BuilderParent parent) {
+ Builder builder = new Builder(parent);
+ return builder;
+ }
+ public static final class Builder extends
+ com.google.protobuf.GeneratedMessage.Builder
+ implements org.bitcoin.paymentchannel.Protos.ProvideContractOrBuilder {
+ public static final com.google.protobuf.Descriptors.Descriptor
+ getDescriptor() {
+ return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_ProvideContract_descriptor;
+ }
+
+ protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
+ internalGetFieldAccessorTable() {
+ return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_ProvideContract_fieldAccessorTable;
+ }
+
+ // Construct using org.bitcoin.paymentchannel.Protos.ProvideContract.newBuilder()
+ private Builder() {
+ maybeForceBuilderInitialization();
+ }
+
+ private Builder(BuilderParent parent) {
+ super(parent);
+ maybeForceBuilderInitialization();
+ }
+ private void maybeForceBuilderInitialization() {
+ if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
+ }
+ }
+ private static Builder create() {
+ return new Builder();
+ }
+
+ public Builder clear() {
+ super.clear();
+ tx_ = com.google.protobuf.ByteString.EMPTY;
+ bitField0_ = (bitField0_ & ~0x00000001);
+ return this;
+ }
+
+ public Builder clone() {
+ return create().mergeFrom(buildPartial());
+ }
+
+ public com.google.protobuf.Descriptors.Descriptor
+ getDescriptorForType() {
+ return org.bitcoin.paymentchannel.Protos.ProvideContract.getDescriptor();
+ }
+
+ public org.bitcoin.paymentchannel.Protos.ProvideContract getDefaultInstanceForType() {
+ return org.bitcoin.paymentchannel.Protos.ProvideContract.getDefaultInstance();
+ }
+
+ public org.bitcoin.paymentchannel.Protos.ProvideContract build() {
+ org.bitcoin.paymentchannel.Protos.ProvideContract result = buildPartial();
+ if (!result.isInitialized()) {
+ throw newUninitializedMessageException(result);
+ }
+ return result;
+ }
+
+ private org.bitcoin.paymentchannel.Protos.ProvideContract buildParsed()
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ org.bitcoin.paymentchannel.Protos.ProvideContract result = buildPartial();
+ if (!result.isInitialized()) {
+ throw newUninitializedMessageException(
+ result).asInvalidProtocolBufferException();
+ }
+ return result;
+ }
+
+ public org.bitcoin.paymentchannel.Protos.ProvideContract buildPartial() {
+ org.bitcoin.paymentchannel.Protos.ProvideContract result = new org.bitcoin.paymentchannel.Protos.ProvideContract(this);
+ int from_bitField0_ = bitField0_;
+ int to_bitField0_ = 0;
+ if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
+ to_bitField0_ |= 0x00000001;
+ }
+ result.tx_ = tx_;
+ result.bitField0_ = to_bitField0_;
+ onBuilt();
+ return result;
+ }
+
+ public Builder mergeFrom(com.google.protobuf.Message other) {
+ if (other instanceof org.bitcoin.paymentchannel.Protos.ProvideContract) {
+ return mergeFrom((org.bitcoin.paymentchannel.Protos.ProvideContract)other);
+ } else {
+ super.mergeFrom(other);
+ return this;
+ }
+ }
+
+ public Builder mergeFrom(org.bitcoin.paymentchannel.Protos.ProvideContract other) {
+ if (other == org.bitcoin.paymentchannel.Protos.ProvideContract.getDefaultInstance()) return this;
+ if (other.hasTx()) {
+ setTx(other.getTx());
+ }
+ this.mergeUnknownFields(other.getUnknownFields());
+ return this;
+ }
+
+ public final boolean isInitialized() {
+ if (!hasTx()) {
+
+ return false;
+ }
+ return true;
+ }
+
+ public Builder mergeFrom(
+ com.google.protobuf.CodedInputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+ com.google.protobuf.UnknownFieldSet.newBuilder(
+ this.getUnknownFields());
+ while (true) {
+ int tag = input.readTag();
+ switch (tag) {
+ case 0:
+ this.setUnknownFields(unknownFields.build());
+ onChanged();
+ return this;
+ default: {
+ if (!parseUnknownField(input, unknownFields,
+ extensionRegistry, tag)) {
+ this.setUnknownFields(unknownFields.build());
+ onChanged();
+ return this;
+ }
+ break;
+ }
+ case 10: {
+ bitField0_ |= 0x00000001;
+ tx_ = input.readBytes();
+ break;
+ }
+ }
+ }
+ }
+
+ private int bitField0_;
+
+ // required bytes tx = 1;
+ private com.google.protobuf.ByteString tx_ = com.google.protobuf.ByteString.EMPTY;
+ public boolean hasTx() {
+ return ((bitField0_ & 0x00000001) == 0x00000001);
+ }
+ public com.google.protobuf.ByteString getTx() {
+ return tx_;
+ }
+ public Builder setTx(com.google.protobuf.ByteString value) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ bitField0_ |= 0x00000001;
+ tx_ = value;
+ onChanged();
+ return this;
+ }
+ public Builder clearTx() {
+ bitField0_ = (bitField0_ & ~0x00000001);
+ tx_ = getDefaultInstance().getTx();
+ onChanged();
+ return this;
+ }
+
+ // @@protoc_insertion_point(builder_scope:paymentchannels.ProvideContract)
+ }
+
+ static {
+ defaultInstance = new ProvideContract(true);
+ defaultInstance.initFields();
+ }
+
+ // @@protoc_insertion_point(class_scope:paymentchannels.ProvideContract)
+ }
+
+ public interface UpdatePaymentOrBuilder
+ extends com.google.protobuf.MessageOrBuilder {
+
+ // required uint64 client_change_value = 1;
+ boolean hasClientChangeValue();
+ long getClientChangeValue();
+
+ // required bytes signature = 2;
+ boolean hasSignature();
+ com.google.protobuf.ByteString getSignature();
+ }
+ public static final class UpdatePayment extends
+ com.google.protobuf.GeneratedMessage
+ implements UpdatePaymentOrBuilder {
+ // Use UpdatePayment.newBuilder() to construct.
+ private UpdatePayment(Builder builder) {
+ super(builder);
+ }
+ private UpdatePayment(boolean noInit) {}
+
+ private static final UpdatePayment defaultInstance;
+ public static UpdatePayment getDefaultInstance() {
+ return defaultInstance;
+ }
+
+ public UpdatePayment getDefaultInstanceForType() {
+ return defaultInstance;
+ }
+
+ public static final com.google.protobuf.Descriptors.Descriptor
+ getDescriptor() {
+ return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_UpdatePayment_descriptor;
+ }
+
+ protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
+ internalGetFieldAccessorTable() {
+ return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_UpdatePayment_fieldAccessorTable;
+ }
+
+ private int bitField0_;
+ // required uint64 client_change_value = 1;
+ public static final int CLIENT_CHANGE_VALUE_FIELD_NUMBER = 1;
+ private long clientChangeValue_;
+ public boolean hasClientChangeValue() {
+ return ((bitField0_ & 0x00000001) == 0x00000001);
+ }
+ public long getClientChangeValue() {
+ return clientChangeValue_;
+ }
+
+ // required bytes signature = 2;
+ public static final int SIGNATURE_FIELD_NUMBER = 2;
+ private com.google.protobuf.ByteString signature_;
+ public boolean hasSignature() {
+ return ((bitField0_ & 0x00000002) == 0x00000002);
+ }
+ public com.google.protobuf.ByteString getSignature() {
+ return signature_;
+ }
+
+ private void initFields() {
+ clientChangeValue_ = 0L;
+ signature_ = com.google.protobuf.ByteString.EMPTY;
+ }
+ private byte memoizedIsInitialized = -1;
+ public final boolean isInitialized() {
+ byte isInitialized = memoizedIsInitialized;
+ if (isInitialized != -1) return isInitialized == 1;
+
+ if (!hasClientChangeValue()) {
+ memoizedIsInitialized = 0;
+ return false;
+ }
+ if (!hasSignature()) {
+ memoizedIsInitialized = 0;
+ return false;
+ }
+ memoizedIsInitialized = 1;
+ return true;
+ }
+
+ public void writeTo(com.google.protobuf.CodedOutputStream output)
+ throws java.io.IOException {
+ getSerializedSize();
+ if (((bitField0_ & 0x00000001) == 0x00000001)) {
+ output.writeUInt64(1, clientChangeValue_);
+ }
+ if (((bitField0_ & 0x00000002) == 0x00000002)) {
+ output.writeBytes(2, signature_);
+ }
+ getUnknownFields().writeTo(output);
+ }
+
+ private int memoizedSerializedSize = -1;
+ public int getSerializedSize() {
+ int size = memoizedSerializedSize;
+ if (size != -1) return size;
+
+ size = 0;
+ if (((bitField0_ & 0x00000001) == 0x00000001)) {
+ size += com.google.protobuf.CodedOutputStream
+ .computeUInt64Size(1, clientChangeValue_);
+ }
+ if (((bitField0_ & 0x00000002) == 0x00000002)) {
+ size += com.google.protobuf.CodedOutputStream
+ .computeBytesSize(2, signature_);
+ }
+ size += getUnknownFields().getSerializedSize();
+ memoizedSerializedSize = size;
+ return size;
+ }
+
+ private static final long serialVersionUID = 0L;
+ @java.lang.Override
+ protected java.lang.Object writeReplace()
+ throws java.io.ObjectStreamException {
+ return super.writeReplace();
+ }
+
+ public static org.bitcoin.paymentchannel.Protos.UpdatePayment parseFrom(
+ com.google.protobuf.ByteString data)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data).buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.UpdatePayment parseFrom(
+ com.google.protobuf.ByteString data,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data, extensionRegistry)
+ .buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.UpdatePayment parseFrom(byte[] data)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data).buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.UpdatePayment parseFrom(
+ byte[] data,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data, extensionRegistry)
+ .buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.UpdatePayment parseFrom(java.io.InputStream input)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input).buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.UpdatePayment parseFrom(
+ java.io.InputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input, extensionRegistry)
+ .buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.UpdatePayment parseDelimitedFrom(java.io.InputStream input)
+ throws java.io.IOException {
+ Builder builder = newBuilder();
+ if (builder.mergeDelimitedFrom(input)) {
+ return builder.buildParsed();
+ } else {
+ return null;
+ }
+ }
+ public static org.bitcoin.paymentchannel.Protos.UpdatePayment parseDelimitedFrom(
+ java.io.InputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ Builder builder = newBuilder();
+ if (builder.mergeDelimitedFrom(input, extensionRegistry)) {
+ return builder.buildParsed();
+ } else {
+ return null;
+ }
+ }
+ public static org.bitcoin.paymentchannel.Protos.UpdatePayment parseFrom(
+ com.google.protobuf.CodedInputStream input)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input).buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.UpdatePayment parseFrom(
+ com.google.protobuf.CodedInputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input, extensionRegistry)
+ .buildParsed();
+ }
+
+ public static Builder newBuilder() { return Builder.create(); }
+ public Builder newBuilderForType() { return newBuilder(); }
+ public static Builder newBuilder(org.bitcoin.paymentchannel.Protos.UpdatePayment prototype) {
+ return newBuilder().mergeFrom(prototype);
+ }
+ public Builder toBuilder() { return newBuilder(this); }
+
+ @java.lang.Override
+ protected Builder newBuilderForType(
+ com.google.protobuf.GeneratedMessage.BuilderParent parent) {
+ Builder builder = new Builder(parent);
+ return builder;
+ }
+ public static final class Builder extends
+ com.google.protobuf.GeneratedMessage.Builder
+ implements org.bitcoin.paymentchannel.Protos.UpdatePaymentOrBuilder {
+ public static final com.google.protobuf.Descriptors.Descriptor
+ getDescriptor() {
+ return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_UpdatePayment_descriptor;
+ }
+
+ protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
+ internalGetFieldAccessorTable() {
+ return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_UpdatePayment_fieldAccessorTable;
+ }
+
+ // Construct using org.bitcoin.paymentchannel.Protos.UpdatePayment.newBuilder()
+ private Builder() {
+ maybeForceBuilderInitialization();
+ }
+
+ private Builder(BuilderParent parent) {
+ super(parent);
+ maybeForceBuilderInitialization();
+ }
+ private void maybeForceBuilderInitialization() {
+ if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
+ }
+ }
+ private static Builder create() {
+ return new Builder();
+ }
+
+ public Builder clear() {
+ super.clear();
+ clientChangeValue_ = 0L;
+ bitField0_ = (bitField0_ & ~0x00000001);
+ signature_ = com.google.protobuf.ByteString.EMPTY;
+ bitField0_ = (bitField0_ & ~0x00000002);
+ return this;
+ }
+
+ public Builder clone() {
+ return create().mergeFrom(buildPartial());
+ }
+
+ public com.google.protobuf.Descriptors.Descriptor
+ getDescriptorForType() {
+ return org.bitcoin.paymentchannel.Protos.UpdatePayment.getDescriptor();
+ }
+
+ public org.bitcoin.paymentchannel.Protos.UpdatePayment getDefaultInstanceForType() {
+ return org.bitcoin.paymentchannel.Protos.UpdatePayment.getDefaultInstance();
+ }
+
+ public org.bitcoin.paymentchannel.Protos.UpdatePayment build() {
+ org.bitcoin.paymentchannel.Protos.UpdatePayment result = buildPartial();
+ if (!result.isInitialized()) {
+ throw newUninitializedMessageException(result);
+ }
+ return result;
+ }
+
+ private org.bitcoin.paymentchannel.Protos.UpdatePayment buildParsed()
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ org.bitcoin.paymentchannel.Protos.UpdatePayment result = buildPartial();
+ if (!result.isInitialized()) {
+ throw newUninitializedMessageException(
+ result).asInvalidProtocolBufferException();
+ }
+ return result;
+ }
+
+ public org.bitcoin.paymentchannel.Protos.UpdatePayment buildPartial() {
+ org.bitcoin.paymentchannel.Protos.UpdatePayment result = new org.bitcoin.paymentchannel.Protos.UpdatePayment(this);
+ int from_bitField0_ = bitField0_;
+ int to_bitField0_ = 0;
+ if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
+ to_bitField0_ |= 0x00000001;
+ }
+ result.clientChangeValue_ = clientChangeValue_;
+ if (((from_bitField0_ & 0x00000002) == 0x00000002)) {
+ to_bitField0_ |= 0x00000002;
+ }
+ result.signature_ = signature_;
+ result.bitField0_ = to_bitField0_;
+ onBuilt();
+ return result;
+ }
+
+ public Builder mergeFrom(com.google.protobuf.Message other) {
+ if (other instanceof org.bitcoin.paymentchannel.Protos.UpdatePayment) {
+ return mergeFrom((org.bitcoin.paymentchannel.Protos.UpdatePayment)other);
+ } else {
+ super.mergeFrom(other);
+ return this;
+ }
+ }
+
+ public Builder mergeFrom(org.bitcoin.paymentchannel.Protos.UpdatePayment other) {
+ if (other == org.bitcoin.paymentchannel.Protos.UpdatePayment.getDefaultInstance()) return this;
+ if (other.hasClientChangeValue()) {
+ setClientChangeValue(other.getClientChangeValue());
+ }
+ if (other.hasSignature()) {
+ setSignature(other.getSignature());
+ }
+ this.mergeUnknownFields(other.getUnknownFields());
+ return this;
+ }
+
+ public final boolean isInitialized() {
+ if (!hasClientChangeValue()) {
+
+ return false;
+ }
+ if (!hasSignature()) {
+
+ return false;
+ }
+ return true;
+ }
+
+ public Builder mergeFrom(
+ com.google.protobuf.CodedInputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+ com.google.protobuf.UnknownFieldSet.newBuilder(
+ this.getUnknownFields());
+ while (true) {
+ int tag = input.readTag();
+ switch (tag) {
+ case 0:
+ this.setUnknownFields(unknownFields.build());
+ onChanged();
+ return this;
+ default: {
+ if (!parseUnknownField(input, unknownFields,
+ extensionRegistry, tag)) {
+ this.setUnknownFields(unknownFields.build());
+ onChanged();
+ return this;
+ }
+ break;
+ }
+ case 8: {
+ bitField0_ |= 0x00000001;
+ clientChangeValue_ = input.readUInt64();
+ break;
+ }
+ case 18: {
+ bitField0_ |= 0x00000002;
+ signature_ = input.readBytes();
+ break;
+ }
+ }
+ }
+ }
+
+ private int bitField0_;
+
+ // required uint64 client_change_value = 1;
+ private long clientChangeValue_ ;
+ public boolean hasClientChangeValue() {
+ return ((bitField0_ & 0x00000001) == 0x00000001);
+ }
+ public long getClientChangeValue() {
+ return clientChangeValue_;
+ }
+ public Builder setClientChangeValue(long value) {
+ bitField0_ |= 0x00000001;
+ clientChangeValue_ = value;
+ onChanged();
+ return this;
+ }
+ public Builder clearClientChangeValue() {
+ bitField0_ = (bitField0_ & ~0x00000001);
+ clientChangeValue_ = 0L;
+ onChanged();
+ return this;
+ }
+
+ // required bytes signature = 2;
+ private com.google.protobuf.ByteString signature_ = com.google.protobuf.ByteString.EMPTY;
+ public boolean hasSignature() {
+ return ((bitField0_ & 0x00000002) == 0x00000002);
+ }
+ public com.google.protobuf.ByteString getSignature() {
+ return signature_;
+ }
+ public Builder setSignature(com.google.protobuf.ByteString value) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ bitField0_ |= 0x00000002;
+ signature_ = value;
+ onChanged();
+ return this;
+ }
+ public Builder clearSignature() {
+ bitField0_ = (bitField0_ & ~0x00000002);
+ signature_ = getDefaultInstance().getSignature();
+ onChanged();
+ return this;
+ }
+
+ // @@protoc_insertion_point(builder_scope:paymentchannels.UpdatePayment)
+ }
+
+ static {
+ defaultInstance = new UpdatePayment(true);
+ defaultInstance.initFields();
+ }
+
+ // @@protoc_insertion_point(class_scope:paymentchannels.UpdatePayment)
+ }
+
+ public interface ErrorOrBuilder
+ extends com.google.protobuf.MessageOrBuilder {
+
+ // optional .paymentchannels.Error.ErrorCode code = 1 [default = OTHER];
+ boolean hasCode();
+ org.bitcoin.paymentchannel.Protos.Error.ErrorCode getCode();
+
+ // optional string explanation = 2;
+ boolean hasExplanation();
+ String getExplanation();
+ }
+ public static final class Error extends
+ com.google.protobuf.GeneratedMessage
+ implements ErrorOrBuilder {
+ // Use Error.newBuilder() to construct.
+ private Error(Builder builder) {
+ super(builder);
+ }
+ private Error(boolean noInit) {}
+
+ private static final Error defaultInstance;
+ public static Error getDefaultInstance() {
+ return defaultInstance;
+ }
+
+ public Error getDefaultInstanceForType() {
+ return defaultInstance;
+ }
+
+ public static final com.google.protobuf.Descriptors.Descriptor
+ getDescriptor() {
+ return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_Error_descriptor;
+ }
+
+ protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
+ internalGetFieldAccessorTable() {
+ return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_Error_fieldAccessorTable;
+ }
+
+ public enum ErrorCode
+ implements com.google.protobuf.ProtocolMessageEnum {
+ TIMEOUT(0, 1),
+ SYNTAX_ERROR(1, 2),
+ NO_ACCEPTABLE_VERSION(2, 3),
+ BAD_TRANSACTION(3, 4),
+ TIME_WINDOW_TOO_LARGE(4, 5),
+ CHANNEL_VALUE_TOO_LARGE(5, 6),
+ OTHER(6, 7),
+ ;
+
+ public static final int TIMEOUT_VALUE = 1;
+ public static final int SYNTAX_ERROR_VALUE = 2;
+ public static final int NO_ACCEPTABLE_VERSION_VALUE = 3;
+ public static final int BAD_TRANSACTION_VALUE = 4;
+ public static final int TIME_WINDOW_TOO_LARGE_VALUE = 5;
+ public static final int CHANNEL_VALUE_TOO_LARGE_VALUE = 6;
+ public static final int OTHER_VALUE = 7;
+
+
+ public final int getNumber() { return value; }
+
+ public static ErrorCode valueOf(int value) {
+ switch (value) {
+ case 1: return TIMEOUT;
+ case 2: return SYNTAX_ERROR;
+ case 3: return NO_ACCEPTABLE_VERSION;
+ case 4: return BAD_TRANSACTION;
+ case 5: return TIME_WINDOW_TOO_LARGE;
+ case 6: return CHANNEL_VALUE_TOO_LARGE;
+ case 7: return OTHER;
+ default: return null;
+ }
+ }
+
+ public static com.google.protobuf.Internal.EnumLiteMap
+ internalGetValueMap() {
+ return internalValueMap;
+ }
+ private static com.google.protobuf.Internal.EnumLiteMap
+ internalValueMap =
+ new com.google.protobuf.Internal.EnumLiteMap() {
+ public ErrorCode findValueByNumber(int number) {
+ return ErrorCode.valueOf(number);
+ }
+ };
+
+ public final com.google.protobuf.Descriptors.EnumValueDescriptor
+ getValueDescriptor() {
+ return getDescriptor().getValues().get(index);
+ }
+ public final com.google.protobuf.Descriptors.EnumDescriptor
+ getDescriptorForType() {
+ return getDescriptor();
+ }
+ public static final com.google.protobuf.Descriptors.EnumDescriptor
+ getDescriptor() {
+ return org.bitcoin.paymentchannel.Protos.Error.getDescriptor().getEnumTypes().get(0);
+ }
+
+ private static final ErrorCode[] VALUES = {
+ TIMEOUT, SYNTAX_ERROR, NO_ACCEPTABLE_VERSION, BAD_TRANSACTION, TIME_WINDOW_TOO_LARGE, CHANNEL_VALUE_TOO_LARGE, OTHER,
+ };
+
+ public static ErrorCode valueOf(
+ com.google.protobuf.Descriptors.EnumValueDescriptor desc) {
+ if (desc.getType() != getDescriptor()) {
+ throw new java.lang.IllegalArgumentException(
+ "EnumValueDescriptor is not for this type.");
+ }
+ return VALUES[desc.getIndex()];
+ }
+
+ private final int index;
+ private final int value;
+
+ private ErrorCode(int index, int value) {
+ this.index = index;
+ this.value = value;
+ }
+
+ // @@protoc_insertion_point(enum_scope:paymentchannels.Error.ErrorCode)
+ }
+
+ private int bitField0_;
+ // optional .paymentchannels.Error.ErrorCode code = 1 [default = OTHER];
+ public static final int CODE_FIELD_NUMBER = 1;
+ private org.bitcoin.paymentchannel.Protos.Error.ErrorCode code_;
+ public boolean hasCode() {
+ return ((bitField0_ & 0x00000001) == 0x00000001);
+ }
+ public org.bitcoin.paymentchannel.Protos.Error.ErrorCode getCode() {
+ return code_;
+ }
+
+ // optional string explanation = 2;
+ public static final int EXPLANATION_FIELD_NUMBER = 2;
+ private java.lang.Object explanation_;
+ public boolean hasExplanation() {
+ return ((bitField0_ & 0x00000002) == 0x00000002);
+ }
+ public String getExplanation() {
+ java.lang.Object ref = explanation_;
+ if (ref instanceof String) {
+ return (String) ref;
+ } else {
+ com.google.protobuf.ByteString bs =
+ (com.google.protobuf.ByteString) ref;
+ String s = bs.toStringUtf8();
+ if (com.google.protobuf.Internal.isValidUtf8(bs)) {
+ explanation_ = s;
+ }
+ return s;
+ }
+ }
+ private com.google.protobuf.ByteString getExplanationBytes() {
+ java.lang.Object ref = explanation_;
+ if (ref instanceof String) {
+ com.google.protobuf.ByteString b =
+ com.google.protobuf.ByteString.copyFromUtf8((String) ref);
+ explanation_ = b;
+ return b;
+ } else {
+ return (com.google.protobuf.ByteString) ref;
+ }
+ }
+
+ private void initFields() {
+ code_ = org.bitcoin.paymentchannel.Protos.Error.ErrorCode.OTHER;
+ explanation_ = "";
+ }
+ private byte memoizedIsInitialized = -1;
+ public final boolean isInitialized() {
+ byte isInitialized = memoizedIsInitialized;
+ if (isInitialized != -1) return isInitialized == 1;
+
+ memoizedIsInitialized = 1;
+ return true;
+ }
+
+ public void writeTo(com.google.protobuf.CodedOutputStream output)
+ throws java.io.IOException {
+ getSerializedSize();
+ if (((bitField0_ & 0x00000001) == 0x00000001)) {
+ output.writeEnum(1, code_.getNumber());
+ }
+ if (((bitField0_ & 0x00000002) == 0x00000002)) {
+ output.writeBytes(2, getExplanationBytes());
+ }
+ getUnknownFields().writeTo(output);
+ }
+
+ private int memoizedSerializedSize = -1;
+ public int getSerializedSize() {
+ int size = memoizedSerializedSize;
+ if (size != -1) return size;
+
+ size = 0;
+ if (((bitField0_ & 0x00000001) == 0x00000001)) {
+ size += com.google.protobuf.CodedOutputStream
+ .computeEnumSize(1, code_.getNumber());
+ }
+ if (((bitField0_ & 0x00000002) == 0x00000002)) {
+ size += com.google.protobuf.CodedOutputStream
+ .computeBytesSize(2, getExplanationBytes());
+ }
+ size += getUnknownFields().getSerializedSize();
+ memoizedSerializedSize = size;
+ return size;
+ }
+
+ private static final long serialVersionUID = 0L;
+ @java.lang.Override
+ protected java.lang.Object writeReplace()
+ throws java.io.ObjectStreamException {
+ return super.writeReplace();
+ }
+
+ public static org.bitcoin.paymentchannel.Protos.Error parseFrom(
+ com.google.protobuf.ByteString data)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data).buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.Error parseFrom(
+ com.google.protobuf.ByteString data,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data, extensionRegistry)
+ .buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.Error parseFrom(byte[] data)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data).buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.Error parseFrom(
+ byte[] data,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ return newBuilder().mergeFrom(data, extensionRegistry)
+ .buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.Error parseFrom(java.io.InputStream input)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input).buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.Error parseFrom(
+ java.io.InputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input, extensionRegistry)
+ .buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.Error parseDelimitedFrom(java.io.InputStream input)
+ throws java.io.IOException {
+ Builder builder = newBuilder();
+ if (builder.mergeDelimitedFrom(input)) {
+ return builder.buildParsed();
+ } else {
+ return null;
+ }
+ }
+ public static org.bitcoin.paymentchannel.Protos.Error parseDelimitedFrom(
+ java.io.InputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ Builder builder = newBuilder();
+ if (builder.mergeDelimitedFrom(input, extensionRegistry)) {
+ return builder.buildParsed();
+ } else {
+ return null;
+ }
+ }
+ public static org.bitcoin.paymentchannel.Protos.Error parseFrom(
+ com.google.protobuf.CodedInputStream input)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input).buildParsed();
+ }
+ public static org.bitcoin.paymentchannel.Protos.Error parseFrom(
+ com.google.protobuf.CodedInputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ return newBuilder().mergeFrom(input, extensionRegistry)
+ .buildParsed();
+ }
+
+ public static Builder newBuilder() { return Builder.create(); }
+ public Builder newBuilderForType() { return newBuilder(); }
+ public static Builder newBuilder(org.bitcoin.paymentchannel.Protos.Error prototype) {
+ return newBuilder().mergeFrom(prototype);
+ }
+ public Builder toBuilder() { return newBuilder(this); }
+
+ @java.lang.Override
+ protected Builder newBuilderForType(
+ com.google.protobuf.GeneratedMessage.BuilderParent parent) {
+ Builder builder = new Builder(parent);
+ return builder;
+ }
+ public static final class Builder extends
+ com.google.protobuf.GeneratedMessage.Builder
+ implements org.bitcoin.paymentchannel.Protos.ErrorOrBuilder {
+ public static final com.google.protobuf.Descriptors.Descriptor
+ getDescriptor() {
+ return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_Error_descriptor;
+ }
+
+ protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
+ internalGetFieldAccessorTable() {
+ return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_Error_fieldAccessorTable;
+ }
+
+ // Construct using org.bitcoin.paymentchannel.Protos.Error.newBuilder()
+ private Builder() {
+ maybeForceBuilderInitialization();
+ }
+
+ private Builder(BuilderParent parent) {
+ super(parent);
+ maybeForceBuilderInitialization();
+ }
+ private void maybeForceBuilderInitialization() {
+ if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
+ }
+ }
+ private static Builder create() {
+ return new Builder();
+ }
+
+ public Builder clear() {
+ super.clear();
+ code_ = org.bitcoin.paymentchannel.Protos.Error.ErrorCode.OTHER;
+ bitField0_ = (bitField0_ & ~0x00000001);
+ explanation_ = "";
+ bitField0_ = (bitField0_ & ~0x00000002);
+ return this;
+ }
+
+ public Builder clone() {
+ return create().mergeFrom(buildPartial());
+ }
+
+ public com.google.protobuf.Descriptors.Descriptor
+ getDescriptorForType() {
+ return org.bitcoin.paymentchannel.Protos.Error.getDescriptor();
+ }
+
+ public org.bitcoin.paymentchannel.Protos.Error getDefaultInstanceForType() {
+ return org.bitcoin.paymentchannel.Protos.Error.getDefaultInstance();
+ }
+
+ public org.bitcoin.paymentchannel.Protos.Error build() {
+ org.bitcoin.paymentchannel.Protos.Error result = buildPartial();
+ if (!result.isInitialized()) {
+ throw newUninitializedMessageException(result);
+ }
+ return result;
+ }
+
+ private org.bitcoin.paymentchannel.Protos.Error buildParsed()
+ throws com.google.protobuf.InvalidProtocolBufferException {
+ org.bitcoin.paymentchannel.Protos.Error result = buildPartial();
+ if (!result.isInitialized()) {
+ throw newUninitializedMessageException(
+ result).asInvalidProtocolBufferException();
+ }
+ return result;
+ }
+
+ public org.bitcoin.paymentchannel.Protos.Error buildPartial() {
+ org.bitcoin.paymentchannel.Protos.Error result = new org.bitcoin.paymentchannel.Protos.Error(this);
+ int from_bitField0_ = bitField0_;
+ int to_bitField0_ = 0;
+ if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
+ to_bitField0_ |= 0x00000001;
+ }
+ result.code_ = code_;
+ if (((from_bitField0_ & 0x00000002) == 0x00000002)) {
+ to_bitField0_ |= 0x00000002;
+ }
+ result.explanation_ = explanation_;
+ result.bitField0_ = to_bitField0_;
+ onBuilt();
+ return result;
+ }
+
+ public Builder mergeFrom(com.google.protobuf.Message other) {
+ if (other instanceof org.bitcoin.paymentchannel.Protos.Error) {
+ return mergeFrom((org.bitcoin.paymentchannel.Protos.Error)other);
+ } else {
+ super.mergeFrom(other);
+ return this;
+ }
+ }
+
+ public Builder mergeFrom(org.bitcoin.paymentchannel.Protos.Error other) {
+ if (other == org.bitcoin.paymentchannel.Protos.Error.getDefaultInstance()) return this;
+ if (other.hasCode()) {
+ setCode(other.getCode());
+ }
+ if (other.hasExplanation()) {
+ setExplanation(other.getExplanation());
+ }
+ this.mergeUnknownFields(other.getUnknownFields());
+ return this;
+ }
+
+ public final boolean isInitialized() {
+ return true;
+ }
+
+ public Builder mergeFrom(
+ com.google.protobuf.CodedInputStream input,
+ com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+ throws java.io.IOException {
+ com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+ com.google.protobuf.UnknownFieldSet.newBuilder(
+ this.getUnknownFields());
+ while (true) {
+ int tag = input.readTag();
+ switch (tag) {
+ case 0:
+ this.setUnknownFields(unknownFields.build());
+ onChanged();
+ return this;
+ default: {
+ if (!parseUnknownField(input, unknownFields,
+ extensionRegistry, tag)) {
+ this.setUnknownFields(unknownFields.build());
+ onChanged();
+ return this;
+ }
+ break;
+ }
+ case 8: {
+ int rawValue = input.readEnum();
+ org.bitcoin.paymentchannel.Protos.Error.ErrorCode value = org.bitcoin.paymentchannel.Protos.Error.ErrorCode.valueOf(rawValue);
+ if (value == null) {
+ unknownFields.mergeVarintField(1, rawValue);
+ } else {
+ bitField0_ |= 0x00000001;
+ code_ = value;
+ }
+ break;
+ }
+ case 18: {
+ bitField0_ |= 0x00000002;
+ explanation_ = input.readBytes();
+ break;
+ }
+ }
+ }
+ }
+
+ private int bitField0_;
+
+ // optional .paymentchannels.Error.ErrorCode code = 1 [default = OTHER];
+ private org.bitcoin.paymentchannel.Protos.Error.ErrorCode code_ = org.bitcoin.paymentchannel.Protos.Error.ErrorCode.OTHER;
+ public boolean hasCode() {
+ return ((bitField0_ & 0x00000001) == 0x00000001);
+ }
+ public org.bitcoin.paymentchannel.Protos.Error.ErrorCode getCode() {
+ return code_;
+ }
+ public Builder setCode(org.bitcoin.paymentchannel.Protos.Error.ErrorCode value) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ bitField0_ |= 0x00000001;
+ code_ = value;
+ onChanged();
+ return this;
+ }
+ public Builder clearCode() {
+ bitField0_ = (bitField0_ & ~0x00000001);
+ code_ = org.bitcoin.paymentchannel.Protos.Error.ErrorCode.OTHER;
+ onChanged();
+ return this;
+ }
+
+ // optional string explanation = 2;
+ private java.lang.Object explanation_ = "";
+ public boolean hasExplanation() {
+ return ((bitField0_ & 0x00000002) == 0x00000002);
+ }
+ public String getExplanation() {
+ java.lang.Object ref = explanation_;
+ if (!(ref instanceof String)) {
+ String s = ((com.google.protobuf.ByteString) ref).toStringUtf8();
+ explanation_ = s;
+ return s;
+ } else {
+ return (String) ref;
+ }
+ }
+ public Builder setExplanation(String value) {
+ if (value == null) {
+ throw new NullPointerException();
+ }
+ bitField0_ |= 0x00000002;
+ explanation_ = value;
+ onChanged();
+ return this;
+ }
+ public Builder clearExplanation() {
+ bitField0_ = (bitField0_ & ~0x00000002);
+ explanation_ = getDefaultInstance().getExplanation();
+ onChanged();
+ return this;
+ }
+ void setExplanation(com.google.protobuf.ByteString value) {
+ bitField0_ |= 0x00000002;
+ explanation_ = value;
+ onChanged();
+ }
+
+ // @@protoc_insertion_point(builder_scope:paymentchannels.Error)
+ }
+
+ static {
+ defaultInstance = new Error(true);
+ defaultInstance.initFields();
+ }
+
+ // @@protoc_insertion_point(class_scope:paymentchannels.Error)
+ }
+
+ private static com.google.protobuf.Descriptors.Descriptor
+ internal_static_paymentchannels_TwoWayChannelMessage_descriptor;
+ private static
+ com.google.protobuf.GeneratedMessage.FieldAccessorTable
+ internal_static_paymentchannels_TwoWayChannelMessage_fieldAccessorTable;
+ private static com.google.protobuf.Descriptors.Descriptor
+ internal_static_paymentchannels_ClientVersion_descriptor;
+ private static
+ com.google.protobuf.GeneratedMessage.FieldAccessorTable
+ internal_static_paymentchannels_ClientVersion_fieldAccessorTable;
+ private static com.google.protobuf.Descriptors.Descriptor
+ internal_static_paymentchannels_ServerVersion_descriptor;
+ private static
+ com.google.protobuf.GeneratedMessage.FieldAccessorTable
+ internal_static_paymentchannels_ServerVersion_fieldAccessorTable;
+ private static com.google.protobuf.Descriptors.Descriptor
+ internal_static_paymentchannels_Initiate_descriptor;
+ private static
+ com.google.protobuf.GeneratedMessage.FieldAccessorTable
+ internal_static_paymentchannels_Initiate_fieldAccessorTable;
+ private static com.google.protobuf.Descriptors.Descriptor
+ internal_static_paymentchannels_ProvideRefund_descriptor;
+ private static
+ com.google.protobuf.GeneratedMessage.FieldAccessorTable
+ internal_static_paymentchannels_ProvideRefund_fieldAccessorTable;
+ private static com.google.protobuf.Descriptors.Descriptor
+ internal_static_paymentchannels_ReturnRefund_descriptor;
+ private static
+ com.google.protobuf.GeneratedMessage.FieldAccessorTable
+ internal_static_paymentchannels_ReturnRefund_fieldAccessorTable;
+ private static com.google.protobuf.Descriptors.Descriptor
+ internal_static_paymentchannels_ProvideContract_descriptor;
+ private static
+ com.google.protobuf.GeneratedMessage.FieldAccessorTable
+ internal_static_paymentchannels_ProvideContract_fieldAccessorTable;
+ private static com.google.protobuf.Descriptors.Descriptor
+ internal_static_paymentchannels_UpdatePayment_descriptor;
+ private static
+ com.google.protobuf.GeneratedMessage.FieldAccessorTable
+ internal_static_paymentchannels_UpdatePayment_fieldAccessorTable;
+ private static com.google.protobuf.Descriptors.Descriptor
+ internal_static_paymentchannels_Error_descriptor;
+ private static
+ com.google.protobuf.GeneratedMessage.FieldAccessorTable
+ internal_static_paymentchannels_Error_fieldAccessorTable;
+
+ public static com.google.protobuf.Descriptors.FileDescriptor
+ getDescriptor() {
+ return descriptor;
+ }
+ private static com.google.protobuf.Descriptors.FileDescriptor
+ descriptor;
+ static {
+ java.lang.String[] descriptorData = {
+ "\n\024paymentchannel.proto\022\017paymentchannels\"" +
+ "\274\005\n\024TwoWayChannelMessage\022?\n\004type\030\001 \002(\01621" +
+ ".paymentchannels.TwoWayChannelMessage.Me" +
+ "ssageType\0226\n\016client_version\030\002 \001(\0132\036.paym" +
+ "entchannels.ClientVersion\0226\n\016server_vers" +
+ "ion\030\003 \001(\0132\036.paymentchannels.ServerVersio" +
+ "n\022+\n\010initiate\030\004 \001(\0132\031.paymentchannels.In" +
+ "itiate\0226\n\016provide_refund\030\005 \001(\0132\036.payment" +
+ "channels.ProvideRefund\0224\n\rreturn_refund\030" +
+ "\006 \001(\0132\035.paymentchannels.ReturnRefund\022:\n\020",
+ "provide_contract\030\007 \001(\0132 .paymentchannels" +
+ ".ProvideContract\0226\n\016update_payment\030\010 \001(\013" +
+ "2\036.paymentchannels.UpdatePayment\022%\n\005erro" +
+ "r\030\n \001(\0132\026.paymentchannels.Error\"\274\001\n\013Mess" +
+ "ageType\022\022\n\016CLIENT_VERSION\020\001\022\022\n\016SERVER_VE" +
+ "RSION\020\002\022\014\n\010INITIATE\020\003\022\022\n\016PROVIDE_REFUND\020" +
+ "\004\022\021\n\rRETURN_REFUND\020\005\022\024\n\020PROVIDE_CONTRACT" +
+ "\020\006\022\020\n\014CHANNEL_OPEN\020\007\022\022\n\016UPDATE_PAYMENT\020\010" +
+ "\022\t\n\005CLOSE\020\t\022\t\n\005ERROR\020\n\"X\n\rClientVersion\022" +
+ "\r\n\005major\030\001 \002(\005\022\020\n\005minor\030\002 \001(\005:\0010\022&\n\036prev",
+ "ious_channel_contract_hash\030\003 \001(\014\"0\n\rServ" +
+ "erVersion\022\r\n\005major\030\001 \002(\005\022\020\n\005minor\030\002 \001(\005:" +
+ "\0010\"]\n\010Initiate\022\024\n\014multisig_key\030\001 \002(\014\022!\n\031" +
+ "min_accepted_channel_size\030\002 \002(\004\022\030\n\020expir" +
+ "e_time_secs\030\003 \002(\004\"1\n\rProvideRefund\022\024\n\014mu" +
+ "ltisig_key\030\001 \002(\014\022\n\n\002tx\030\002 \002(\014\"!\n\014ReturnRe" +
+ "fund\022\021\n\tsignature\030\001 \002(\014\"\035\n\017ProvideContra" +
+ "ct\022\n\n\002tx\030\001 \002(\014\"?\n\rUpdatePayment\022\033\n\023clien" +
+ "t_change_value\030\001 \002(\004\022\021\n\tsignature\030\002 \002(\014\"" +
+ "\363\001\n\005Error\0225\n\004code\030\001 \001(\0162 .paymentchannel",
+ "s.Error.ErrorCode:\005OTHER\022\023\n\013explanation\030" +
+ "\002 \001(\t\"\235\001\n\tErrorCode\022\013\n\007TIMEOUT\020\001\022\020\n\014SYNT" +
+ "AX_ERROR\020\002\022\031\n\025NO_ACCEPTABLE_VERSION\020\003\022\023\n" +
+ "\017BAD_TRANSACTION\020\004\022\031\n\025TIME_WINDOW_TOO_LA" +
+ "RGE\020\005\022\033\n\027CHANNEL_VALUE_TOO_LARGE\020\006\022\t\n\005OT" +
+ "HER\020\007B$\n\032org.bitcoin.paymentchannelB\006Pro" +
+ "tos"
+ };
+ com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
+ new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
+ public com.google.protobuf.ExtensionRegistry assignDescriptors(
+ com.google.protobuf.Descriptors.FileDescriptor root) {
+ descriptor = root;
+ internal_static_paymentchannels_TwoWayChannelMessage_descriptor =
+ getDescriptor().getMessageTypes().get(0);
+ internal_static_paymentchannels_TwoWayChannelMessage_fieldAccessorTable = new
+ com.google.protobuf.GeneratedMessage.FieldAccessorTable(
+ internal_static_paymentchannels_TwoWayChannelMessage_descriptor,
+ new java.lang.String[] { "Type", "ClientVersion", "ServerVersion", "Initiate", "ProvideRefund", "ReturnRefund", "ProvideContract", "UpdatePayment", "Error", },
+ org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage.class,
+ org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage.Builder.class);
+ internal_static_paymentchannels_ClientVersion_descriptor =
+ getDescriptor().getMessageTypes().get(1);
+ internal_static_paymentchannels_ClientVersion_fieldAccessorTable = new
+ com.google.protobuf.GeneratedMessage.FieldAccessorTable(
+ internal_static_paymentchannels_ClientVersion_descriptor,
+ new java.lang.String[] { "Major", "Minor", "PreviousChannelContractHash", },
+ org.bitcoin.paymentchannel.Protos.ClientVersion.class,
+ org.bitcoin.paymentchannel.Protos.ClientVersion.Builder.class);
+ internal_static_paymentchannels_ServerVersion_descriptor =
+ getDescriptor().getMessageTypes().get(2);
+ internal_static_paymentchannels_ServerVersion_fieldAccessorTable = new
+ com.google.protobuf.GeneratedMessage.FieldAccessorTable(
+ internal_static_paymentchannels_ServerVersion_descriptor,
+ new java.lang.String[] { "Major", "Minor", },
+ org.bitcoin.paymentchannel.Protos.ServerVersion.class,
+ org.bitcoin.paymentchannel.Protos.ServerVersion.Builder.class);
+ internal_static_paymentchannels_Initiate_descriptor =
+ getDescriptor().getMessageTypes().get(3);
+ internal_static_paymentchannels_Initiate_fieldAccessorTable = new
+ com.google.protobuf.GeneratedMessage.FieldAccessorTable(
+ internal_static_paymentchannels_Initiate_descriptor,
+ new java.lang.String[] { "MultisigKey", "MinAcceptedChannelSize", "ExpireTimeSecs", },
+ org.bitcoin.paymentchannel.Protos.Initiate.class,
+ org.bitcoin.paymentchannel.Protos.Initiate.Builder.class);
+ internal_static_paymentchannels_ProvideRefund_descriptor =
+ getDescriptor().getMessageTypes().get(4);
+ internal_static_paymentchannels_ProvideRefund_fieldAccessorTable = new
+ com.google.protobuf.GeneratedMessage.FieldAccessorTable(
+ internal_static_paymentchannels_ProvideRefund_descriptor,
+ new java.lang.String[] { "MultisigKey", "Tx", },
+ org.bitcoin.paymentchannel.Protos.ProvideRefund.class,
+ org.bitcoin.paymentchannel.Protos.ProvideRefund.Builder.class);
+ internal_static_paymentchannels_ReturnRefund_descriptor =
+ getDescriptor().getMessageTypes().get(5);
+ internal_static_paymentchannels_ReturnRefund_fieldAccessorTable = new
+ com.google.protobuf.GeneratedMessage.FieldAccessorTable(
+ internal_static_paymentchannels_ReturnRefund_descriptor,
+ new java.lang.String[] { "Signature", },
+ org.bitcoin.paymentchannel.Protos.ReturnRefund.class,
+ org.bitcoin.paymentchannel.Protos.ReturnRefund.Builder.class);
+ internal_static_paymentchannels_ProvideContract_descriptor =
+ getDescriptor().getMessageTypes().get(6);
+ internal_static_paymentchannels_ProvideContract_fieldAccessorTable = new
+ com.google.protobuf.GeneratedMessage.FieldAccessorTable(
+ internal_static_paymentchannels_ProvideContract_descriptor,
+ new java.lang.String[] { "Tx", },
+ org.bitcoin.paymentchannel.Protos.ProvideContract.class,
+ org.bitcoin.paymentchannel.Protos.ProvideContract.Builder.class);
+ internal_static_paymentchannels_UpdatePayment_descriptor =
+ getDescriptor().getMessageTypes().get(7);
+ internal_static_paymentchannels_UpdatePayment_fieldAccessorTable = new
+ com.google.protobuf.GeneratedMessage.FieldAccessorTable(
+ internal_static_paymentchannels_UpdatePayment_descriptor,
+ new java.lang.String[] { "ClientChangeValue", "Signature", },
+ org.bitcoin.paymentchannel.Protos.UpdatePayment.class,
+ org.bitcoin.paymentchannel.Protos.UpdatePayment.Builder.class);
+ internal_static_paymentchannels_Error_descriptor =
+ getDescriptor().getMessageTypes().get(8);
+ internal_static_paymentchannels_Error_fieldAccessorTable = new
+ com.google.protobuf.GeneratedMessage.FieldAccessorTable(
+ internal_static_paymentchannels_Error_descriptor,
+ new java.lang.String[] { "Code", "Explanation", },
+ org.bitcoin.paymentchannel.Protos.Error.class,
+ org.bitcoin.paymentchannel.Protos.Error.Builder.class);
+ return null;
+ }
+ };
+ com.google.protobuf.Descriptors.FileDescriptor
+ .internalBuildGeneratedFileFrom(descriptorData,
+ new com.google.protobuf.Descriptors.FileDescriptor[] {
+ }, assigner);
+ }
+
+ // @@protoc_insertion_point(outer_class_scope)
+}
diff --git a/core/src/paymentchannel.proto b/core/src/paymentchannel.proto
new file mode 100644
index 00000000..4cccd087
--- /dev/null
+++ b/core/src/paymentchannel.proto
@@ -0,0 +1,211 @@
+/** 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.
+ */
+
+/*
+ * Authors: Mike Hearn, Matt Corallo
+ */
+
+/* Notes:
+ * - Endianness: All byte arrays that represent numbers (such as hashes and private keys) are Big Endian
+ * - To regenerate after editing, run mvn clean package -DupdateProtobuf
+ */
+
+package paymentchannels;
+
+option java_package = "org.bitcoin.paymentchannel";
+option java_outer_classname = "Protos";
+
+
+// The connection should be a standard TLS connection and all messages sent over this socket are
+// serialized TwoWayChannelMessages prefixed with 2-byte size in big-endian (smaller than or
+// equal to 32767 bytes in size)
+message TwoWayChannelMessage {
+ enum MessageType {
+ CLIENT_VERSION = 1;
+ SERVER_VERSION = 2;
+ INITIATE = 3;
+ PROVIDE_REFUND = 4;
+ RETURN_REFUND = 5;
+ PROVIDE_CONTRACT = 6;
+ // Note that there are no optional fields set for CHANNEL_OPEN, it is sent from the
+ // secondary to the primary to indicate that the provided contract was received,
+ // verified, and broadcast successfully and the primary can now provide UPDATE messages
+ // at will to begin paying secondary. If the channel is interrupted after the
+ // CHANNEL_OPEN message (ie closed without an explicit CLOSE or ERROR) the primary may
+ // reopen the channel by setting the contract transaction hash in its CLIENT_VERSION
+ // message.
+ CHANNEL_OPEN = 7;
+ UPDATE_PAYMENT = 8;
+ // Note that there are no optional fields set for CLOSE, it is sent by either party to
+ // indicate that the channel is now closed and no further updates can happen. After this,
+ // the secondary takes the most recent signature it received in an UPDATE_PAYMENT and
+ // uses it to create a valid transaction, which it then broadcasts on the network.
+ CLOSE = 9;
+
+ // Used to indicate an error condition.
+ // Both parties should make an effort to send either an ERROR or a CLOSE immediately
+ // before closing the socket (unless they just received an ERROR or a CLOSE)
+ ERROR = 10;
+ };
+
+ // This is required so if a new message type is added in future, old software aborts trying
+ // to read the message as early as possible. If the message doesn't parse, the socket should
+ // be closed.
+ required MessageType type = 1;
+
+ // Now one optional field for each message. Only the field specified by type should be read.
+ optional ClientVersion client_version = 2;
+ optional ServerVersion server_version = 3;
+ optional Initiate initiate = 4;
+ optional ProvideRefund provide_refund = 5;
+ optional ReturnRefund return_refund = 6;
+ optional ProvideContract provide_contract = 7;
+ optional UpdatePayment update_payment = 8;
+
+ optional Error error = 10;
+}
+
+// Sent by primary to secondary on opening the connection. If anything is received before this is
+// sent, the socket is closed.
+message ClientVersion {
+ required int32 major = 1;
+ optional int32 minor = 2 [default = 0];
+
+ // The hash of the multisig contract of a previous channel. This indicates that the primary
+ // wishes to reopen the given channel. If the server is willing to reopen it, it simply
+ // responds with a SERVER_VERSION and then immediately sends a CHANNEL_OPEN, it otherwise
+ // follows SERVER_VERSION with an Initiate representing a new channel
+ optional bytes previous_channel_contract_hash = 3;
+}
+
+// Send by secondary to primary upon receiving the ClientVersion message. If it is willing to
+// speak the given major version, it sends back the same major version and the minor version it
+// speaks. If it is not, it may send back a lower major version representing the highest version
+// it is willing to speak, or sends a NO_ACCEPTABLE_VERSION Error. If the secondary sends back a
+// lower major version, the secondary should either expect to continue with that version, or
+// should immediately close the connection with a NO_ACCEPTABLE_VERSION Error. Backwards
+// incompatible changes to the protocol bump the major version. Extensions bump the minor version
+message ServerVersion {
+ required int32 major = 1;
+ optional int32 minor = 2 [default = 0];
+}
+
+// Sent from secondary to primary once version nego is done.
+message Initiate {
+ // This must be a raw pubkey in regular ECDSA form. Both compressed and non-compressed forms
+ // are accepted. It is used only in the creation of the multisig contract, as outputs are
+ // created entirely by the secondary
+ required bytes multisig_key = 1;
+
+ // Once a channel is exhausted a new one must be set up. So secondary indicates the minimum
+ // size it's willing to accept here. This can be lower to trade off resources against
+ // security but shouldn't be so low the transactions get rejected by the network as spam.
+ // Zero isn't a sensible value to have here, so we make the field required.
+ required uint64 min_accepted_channel_size = 2;
+
+ // Rough UNIX time for when the channel expires. This is determined by the block header
+ // timestamps which can be very inaccurate when miners use the obsolete RollNTime hack.
+ // Channels could also be specified in terms of block heights but then how do you know the
+ // current chain height if you don't have internet access? Trust secondary? Probably opens up
+ // attack vectors. We can assume primary has an independent clock, however. If primary
+ // considers this value too far off (eg more than a day), it may send an ERROR and close the
+ // channel.
+ required uint64 expire_time_secs = 3;
+}
+
+// Sent from primary to secondary after Initiate to begin the refund transaction signing.
+message ProvideRefund {
+ // This must be a raw pubkey in regular ECDSA form. Both compressed and non-compressed forms
+ // are accepted. It is only used in the creation of the multisig contract.
+ required bytes multisig_key = 1;
+
+ // The serialized bytes of the return transaction in Satoshi format.
+ // * It must have exactly one input which spends the multisig output (see ProvideContract for
+ // details of exactly what that output must look like). This output must have a sequence
+ // number of 0.
+ // * It must have the lock time set to a time after the min_time_window_secs (from the
+ // Initiate message).
+ // * It must have exactly one output which goes back to the primary. This output's
+ // scriptPubKey will be reused to create payment transactions.
+ required bytes tx = 2;
+}
+
+// Sent from secondary to primary after it has done initial verification of the refund
+// transaction. Contains the primary's signature which is required to spend the multisig contract
+// to the refund transaction. Must be signed using SIGHASH_NONE|SIGHASH_ANYONECANPAY (and include
+// the postfix type byte) to allow the client to add any outputs/inputs it wants as long as the
+// input's sequence and transaction's nLockTime remain set.
+message ReturnRefund {
+ required bytes signature = 1;
+}
+
+// Sent from the primary to the secondary to complete initialization.
+message ProvideContract {
+ // The serialized bytes of the transaction in Satoshi format.
+ // * It must be signed and completely valid and ready for broadcast (ie it includes the
+ // necessary fees) TODO: tell the client how much fee it needs
+ // * Its first output must be a 2-of-2 multisig output with the first pubkey being the
+ // primary's and the second being the secondary's (ie the script must be exactly "OP_2
+ // ProvideRefund.multisig_key Initiate.multisig_key OP_2 OP_CHECKMULTISIG")
+ required bytes tx = 1;
+}
+
+// This message can only be used by the primary after it has received a CHANNEL_OPEN message. It
+// creates a new payment transaction. Note that we don't resubmit the entire TX, this is to avoid
+// (re)parsing bugs and overhead. The payment transaction is created by the primary by:
+// * Adding an input which spends the multisig contract
+// * Setting this input's scriptSig to the given signature and a new signature created by the
+// primary (the primary should ensure the signature provided correctly spends the multisig
+// contract)
+// * Adding an output who's scriptPubKey is the same as the refund output (the only output) in
+// the refund transaction
+// * Setting this output's value to client_change_value (which must be lower than the most recent
+// client_change_value and lower than the multisig contract's output value)
+// * Adding any number of additional outputs as desired (leaving sufficient fee, if necessary)
+// * Adding any number of additional inputs as desired (eg to add more fee)
+message UpdatePayment {
+ // The value which is sent back to the primary. The rest of the multisig output is left for
+ // the secondary to do with as they wish.
+ required uint64 client_change_value = 1;
+ // A SIGHASH_SINGLE|SIGHASH_ANYONECANPAY signature (including the postfix type byte) which
+ // spends the primary's part of the multisig contract's output. This signature only covers
+ // the primary's refund output and thus the secondary is free to do what they wish with their
+ // part of the multisig output.
+ required bytes signature = 2;
+}
+
+
+// An Error can be sent by either party at any time
+// Both parties should make an effort to send either an ERROR or a CLOSE immediately before
+// closing the socket (unless they just received an ERROR or a CLOSE)
+message Error {
+ enum ErrorCode {
+ TIMEOUT = 1; // Protocol timeout occurred (one party hung).
+ SYNTAX_ERROR = 2; // Generic error indicating some message was not properly
+ // formatted or was out of order.
+ NO_ACCEPTABLE_VERSION = 3; // We don't speak the version the other side asked for.
+ BAD_TRANSACTION = 4; // A provided transaction was not in the proper structure
+ // (wrong inputs/outputs, sequence, lock time, signature,
+ // etc)
+ TIME_WINDOW_TOO_LARGE = 5; // The expire time specified by the secondary was too large
+ // for the primary
+ CHANNEL_VALUE_TOO_LARGE = 6; // The minimum channel value specified by the secondary was
+ // too large for the primary
+
+ OTHER = 7;
+ };
+ optional ErrorCode code = 1 [default=OTHER];
+ optional string explanation = 2; // NOT SAFE FOR HTML WITHOUT ESCAPING
+}
\ No newline at end of file
diff --git a/core/src/test/java/com/google/bitcoin/protocols/channels/ChannelConnectionTest.java b/core/src/test/java/com/google/bitcoin/protocols/channels/ChannelConnectionTest.java
new file mode 100644
index 00000000..81d34aa0
--- /dev/null
+++ b/core/src/test/java/com/google/bitcoin/protocols/channels/ChannelConnectionTest.java
@@ -0,0 +1,1260 @@
+/*
+ * 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 com.google.bitcoin.protocols.channels;
+
+import java.io.File;
+import java.math.BigInteger;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import javax.annotation.Nullable;
+
+import com.google.bitcoin.core.*;
+import com.google.bitcoin.protocols.niowrapper.ProtobufParser;
+import com.google.bitcoin.protocols.niowrapper.ProtobufParserFactory;
+import com.google.bitcoin.protocols.niowrapper.ProtobufServer;
+import com.google.bitcoin.utils.Locks;
+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.easymock.Capture;
+import org.easymock.IMocksControl;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static com.google.bitcoin.protocols.channels.PaymentChannelCloseException.CloseReason;
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.*;
+
+public class ChannelConnectionTest extends TestWithWallet {
+ private Wallet serverWallet;
+ private AtomicBoolean fail;
+
+ private interface PaymentChannelClientReceiver {
+ void receiveMessage(Protos.TwoWayChannelMessage msg);
+ void connectionOpen();
+ void connectionClosed();
+ void close();
+ }
+ private class PaymentChannelClientReceiverImpl implements PaymentChannelClientReceiver {
+ private PaymentChannelClient client;
+ public PaymentChannelClientReceiverImpl(PaymentChannelClient client) { this.client = client; }
+ public void receiveMessage(Protos.TwoWayChannelMessage msg) { client.receiveMessage(msg); }
+ public void connectionOpen() { client.connectionOpen(); }
+ public void connectionClosed() { client.connectionClosed(); }
+ public void close() { client.close(); }
+ }
+ private PaymentChannelClientReceiver sendClient;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ sendMoneyToWallet(Utils.COIN, AbstractBlockChain.NewBlockType.BEST_CHAIN);
+ sendMoneyToWallet(Utils.COIN, AbstractBlockChain.NewBlockType.BEST_CHAIN);
+ wallet.addExtension(new StoredPaymentChannelClientStates(new TransactionBroadcaster() {
+ @Override
+ public ListenableFuture broadcastTransaction(Transaction tx) {
+ fail();
+ return null;
+ }
+ }, wallet));
+ chain = new BlockChain(params, wallet, blockStore); // Recreate chain as sendMoneyToWallet will confuse it
+ serverWallet = new Wallet(params);
+ serverWallet.addKey(new ECKey());
+ chain.addWallet(serverWallet);
+ // Use an atomic boolean to indicate failure because fail()/assert*() dont work in network threads
+ fail = new AtomicBoolean(false);
+ // Because there are no separate threads in the tests here (we call back into client/server in server/client
+ // handlers), we have lots of lock cycles. A normal user shouldn't have this issue as they are probably not both
+ // client+server running in the same thread.
+ Locks.warnOnLockCycles();
+ }
+
+ @After
+ public void checkFail() {
+ assertFalse(fail.get());
+ Locks.throwOnLockCycles();
+ }
+
+ @Test
+ public void testSimpleChannel() throws Exception {
+ // Test without any issues
+
+ // Set up a mock peergroup.
+ IMocksControl control = createStrictControl();
+ PeerGroup mockPeerGroup = control.createMock(PeerGroup.class);
+ // We'll broadcast two txns: multisig contract and close transaction.
+ SettableFuture multiSigFuture = SettableFuture.create();
+ SettableFuture closeFuture = SettableFuture.create();
+ final Capture broadcastMultiSig = new Capture();
+ Capture broadcastClose = new Capture();
+ expect(mockPeerGroup.broadcastTransaction(capture(broadcastMultiSig))).andReturn(multiSigFuture);
+ expect(mockPeerGroup.broadcastTransaction(capture(broadcastClose))).andReturn(closeFuture);
+ control.replay();
+
+ final SettableFuture> serverCloseFuture = SettableFuture.create();
+ final SettableFuture channelOpenFuture = SettableFuture.create();
+ final SettableFuture twoCentsReceivedFuture = SettableFuture.create();
+ final PaymentChannelServerListener server = new PaymentChannelServerListener(mockPeerGroup, serverWallet, 1, Utils.COIN,
+ new PaymentChannelServerListener.HandlerFactory() {
+ @Nullable
+ @Override
+ public ServerConnectionEventHandler onNewConnection(SocketAddress clientAddress) {
+ return new ServerConnectionEventHandler() {
+ @Override
+ public void channelOpen(Sha256Hash channelId) {
+ if (!channelId.equals(broadcastMultiSig.getValue().getHash()))
+ fail.set(true);
+ channelOpenFuture.set(null);
+ }
+
+ @Override
+ public void paymentIncrease(BigInteger by, BigInteger to) {
+ if (to.equals(Utils.CENT.shiftLeft(1)))
+ twoCentsReceivedFuture.set(null);
+ }
+
+ @Override
+ public void channelClosed(CloseReason reason) {
+ serverCloseFuture.set(null);
+ }
+ };
+ }
+ });
+ server.bindAndStart(4243);
+
+ PaymentChannelClientConnection client = new PaymentChannelClientConnection(new InetSocketAddress("localhost", 4243), 1, wallet, myKey, Utils.COIN, "");
+
+ while (!broadcastMultiSig.hasCaptured())
+ Thread.sleep(100);
+ multiSigFuture.set(broadcastMultiSig.getValue());
+
+ client.getChannelOpenFuture().get();
+ assertTrue(channelOpenFuture.isDone());
+
+ // Set up an autosave listener to make sure the server is saving the wallet after each payment increase
+ final AtomicInteger autoSaveCount = new AtomicInteger(0);
+ File tempFile = File.createTempFile("channel_connection_test", ".wallet");
+ tempFile.deleteOnExit();
+ serverWallet.autosaveToFile(tempFile, 0, TimeUnit.SECONDS, new Wallet.AutosaveEventListener() {
+ @Override
+ public boolean caughtException(Throwable t) {
+ fail.set(true);
+ return false;
+ }
+
+ @Override
+ public void onBeforeAutoSave(File tempFile) {
+ autoSaveCount.incrementAndGet();
+ }
+
+ @Override public void onAfterAutoSave(File newlySavedFile) { }
+ });
+ assertEquals(0, autoSaveCount.get());
+
+ Thread.sleep(1250); // No timeouts once the channel is open
+ client.incrementPayment(Utils.CENT);
+ while (autoSaveCount.get() != 1)
+ Thread.sleep(100);
+ client.incrementPayment(Utils.CENT);
+ while (autoSaveCount.get() != 2)
+ Thread.sleep(100);
+ twoCentsReceivedFuture.get();
+ client.incrementPayment(Utils.CENT);
+ while (autoSaveCount.get() != 3)
+ Thread.sleep(100);
+
+ StoredPaymentChannelServerStates channels = (StoredPaymentChannelServerStates)serverWallet.getExtensions().get(StoredPaymentChannelServerStates.EXTENSION_ID);
+ StoredServerChannel storedServerChannel = channels.getChannel(broadcastMultiSig.getValue().getHash());
+ PaymentChannelServerState serverState;
+ synchronized (storedServerChannel) {
+ serverState = storedServerChannel.getState(serverWallet, mockPeerGroup);
+ }
+
+ client.close();
+ client.close();
+
+ while (serverState.getState() != PaymentChannelServerState.State.CLOSING)
+ Thread.sleep(100);
+
+ client.close();
+
+ closeFuture.set(broadcastClose.getValue());
+
+ if (!serverState.getBestValueToMe().equals(Utils.CENT.multiply(BigInteger.valueOf(3))) || !serverState.getFeePaid().equals(BigInteger.ZERO))
+ fail();
+
+ assertTrue(channels.mapChannels.isEmpty());
+
+ control.verify();
+ server.close();
+ server.close();
+ }
+
+ @Test
+ public void testServerErrorHandling() throws Exception {
+ // Gives the server crap and checks proper error responses are sent
+
+ // Set up a mock peergroup.
+ IMocksControl control = createStrictControl();
+ PeerGroup mockPeerGroup = control.createMock(PeerGroup.class);
+ control.replay();
+
+ final PaymentChannelServer server = new PaymentChannelServer(mockPeerGroup, serverWallet, Utils.COIN,
+ new PaymentChannelServer.ServerConnection() {
+ @Override
+ public void sendToClient(Protos.TwoWayChannelMessage msg) {
+ sendClient.receiveMessage(msg);
+ }
+
+ @Override
+ public void destroyConnection(CloseReason reason) {
+ if (reason != CloseReason.NO_ACCEPTABLE_VERSION)
+ fail.set(true);
+ sendClient.connectionClosed();
+ }
+
+ @Override public void channelOpen(Sha256Hash contractHash) { fail.set(true); }
+ @Override public void paymentIncrease(BigInteger by, BigInteger to) { fail.set(true); }
+ });
+
+ // Make sure we get back NO_ACCEPTABLE_VERSION if we send a version message that is very high
+ final SettableFuture inactiveFuture = SettableFuture.create();
+ sendClient = new PaymentChannelClientReceiver() {
+ @Override
+ public void receiveMessage(Protos.TwoWayChannelMessage msg) {
+ if (msg.getType() != Protos.TwoWayChannelMessage.MessageType.ERROR ||
+ !msg.hasError() || msg.getError().getCode() != Protos.Error.ErrorCode.NO_ACCEPTABLE_VERSION)
+ fail.set(true);
+ inactiveFuture.set(null);
+ }
+
+ @Override
+ public void connectionOpen() {
+ Protos.ClientVersion.Builder versionNegotiationBuilder = Protos.ClientVersion.newBuilder();
+ versionNegotiationBuilder.setMajor(10);
+ versionNegotiationBuilder.setMinor(42);
+ server.receiveMessage(Protos.TwoWayChannelMessage.newBuilder()
+ .setType(Protos.TwoWayChannelMessage.MessageType.CLIENT_VERSION)
+ .setClientVersion(versionNegotiationBuilder)
+ .build());
+ }
+
+ @Override public void connectionClosed() { }
+ @Override public void close() { }
+ };
+ server.connectionOpen();
+ sendClient.connectionOpen();
+ inactiveFuture.get();
+
+ // Make sure we get back SYNTAX_ERROR if we send messages in the wrong order
+ final SettableFuture inactiveFuture2 = SettableFuture.create();
+ final PaymentChannelServer server2 = new PaymentChannelServer(mockPeerGroup, serverWallet, Utils.COIN,
+ new PaymentChannelServer.ServerConnection() {
+ @Override
+ public void sendToClient(Protos.TwoWayChannelMessage msg) {
+ sendClient.receiveMessage(msg);
+ }
+
+ @Override
+ public void destroyConnection(CloseReason reason) {
+ if (reason != CloseReason.REMOTE_SENT_INVALID_MESSAGE)
+ fail.set(true);
+ sendClient.connectionClosed();
+ }
+
+ @Override public void channelOpen(Sha256Hash contractHash) { fail.set(true); }
+ @Override public void paymentIncrease(BigInteger by, BigInteger to) { fail.set(true); }
+ });
+ sendClient = new PaymentChannelClientReceiver() {
+ int step = 0;
+ @Override
+ public void receiveMessage(Protos.TwoWayChannelMessage msg) {
+ if ((step != 0 || msg.getType() != Protos.TwoWayChannelMessage.MessageType.SERVER_VERSION) &&
+ (step != 1 || msg.getType() != Protos.TwoWayChannelMessage.MessageType.INITIATE) &&
+ (step != 2 || msg.getType() != Protos.TwoWayChannelMessage.MessageType.ERROR || msg.getError().getCode() != Protos.Error.ErrorCode.SYNTAX_ERROR))
+ fail.set(true);
+ step++;
+ if (step == 2) {
+ Protos.UpdatePayment.Builder updatePaymentBuilder = Protos.UpdatePayment.newBuilder()
+ .setClientChangeValue(0).setSignature(ByteString.EMPTY);
+ server2.receiveMessage(Protos.TwoWayChannelMessage.newBuilder()
+ .setType(Protos.TwoWayChannelMessage.MessageType.UPDATE_PAYMENT)
+ .setUpdatePayment(updatePaymentBuilder)
+ .build());
+ } else if (step == 3)
+ inactiveFuture2.set(null);
+ }
+
+ @Override
+ public void connectionOpen() {
+ Protos.ClientVersion.Builder versionNegotiationBuilder = Protos.ClientVersion.newBuilder()
+ .setMajor(0).setMinor(42);
+ server2.receiveMessage(Protos.TwoWayChannelMessage.newBuilder()
+ .setType(Protos.TwoWayChannelMessage.MessageType.CLIENT_VERSION)
+ .setClientVersion(versionNegotiationBuilder)
+ .build());
+ }
+ @Override public void connectionClosed() { }
+ @Override public void close() { }
+ };
+ server2.connectionOpen();
+ sendClient.connectionOpen();
+ inactiveFuture2.get();
+
+ // Make sure we get back a BAD_TRANSACTION if we send crap for a refund transaction
+ final SettableFuture inactiveFuture3 = SettableFuture.create();
+ final PaymentChannelServer server3 = new PaymentChannelServer(mockPeerGroup, serverWallet, Utils.COIN,
+ new PaymentChannelServer.ServerConnection() {
+ @Override
+ public void sendToClient(Protos.TwoWayChannelMessage msg) {
+ sendClient.receiveMessage(msg);
+ }
+
+ @Override
+ public void destroyConnection(CloseReason reason) {
+ if (reason != CloseReason.REMOTE_SENT_INVALID_MESSAGE)
+ fail.set(true);
+ sendClient.connectionClosed();
+ }
+
+ @Override public void channelOpen(Sha256Hash contractHash) { fail.set(true); }
+ @Override public void paymentIncrease(BigInteger by, BigInteger to) { fail.set(true); }
+ });
+ sendClient = new PaymentChannelClientReceiver() {
+ int step = 0;
+ @Override
+ public void receiveMessage(Protos.TwoWayChannelMessage msg) {
+ if ((step != 0 || msg.getType() != Protos.TwoWayChannelMessage.MessageType.SERVER_VERSION) &&
+ (step != 1 || msg.getType() != Protos.TwoWayChannelMessage.MessageType.INITIATE) &&
+ (step != 2 || msg.getType() != Protos.TwoWayChannelMessage.MessageType.ERROR || msg.getError().getCode() != Protos.Error.ErrorCode.BAD_TRANSACTION))
+ fail.set(true);
+ step++;
+ if (step == 2) {
+ Protos.ProvideRefund.Builder provideRefundBuilder = Protos.ProvideRefund.newBuilder()
+ .setMultisigKey(ByteString.EMPTY).setTx(ByteString.EMPTY);
+ server3.receiveMessage(Protos.TwoWayChannelMessage.newBuilder()
+ .setType(Protos.TwoWayChannelMessage.MessageType.PROVIDE_REFUND)
+ .setProvideRefund(provideRefundBuilder)
+ .build());
+ } else if (step == 3)
+ inactiveFuture3.set(null);
+ }
+
+ @Override
+ public void connectionOpen() {
+ Protos.ClientVersion.Builder versionNegotiationBuilder = Protos.ClientVersion.newBuilder()
+ .setMajor(0).setMinor(42);
+ server3.receiveMessage(Protos.TwoWayChannelMessage.newBuilder()
+ .setType(Protos.TwoWayChannelMessage.MessageType.CLIENT_VERSION)
+ .setClientVersion(versionNegotiationBuilder)
+ .build());
+ }
+ @Override public void connectionClosed() { }
+ @Override public void close() { }
+ };
+ server3.connectionOpen();
+ sendClient.connectionOpen();
+ inactiveFuture3.get();
+
+ // Make sure the server closes the socket on CLOSE
+ final SettableFuture inactiveFuture4 = SettableFuture.create();
+ final PaymentChannelServer server4 = new PaymentChannelServer(mockPeerGroup, serverWallet, Utils.COIN,
+ new PaymentChannelServer.ServerConnection() {
+ @Override
+ public void sendToClient(Protos.TwoWayChannelMessage msg) {
+ sendClient.receiveMessage(msg);
+ }
+
+ @Override
+ public void destroyConnection(CloseReason reason) {
+ if (reason != CloseReason.CLIENT_REQUESTED_CLOSE)
+ fail.set(true);
+ sendClient.connectionClosed();
+ }
+
+ @Override public void channelOpen(Sha256Hash contractHash) { fail.set(true); }
+ @Override public void paymentIncrease(BigInteger by, BigInteger to) { fail.set(true); }
+ });
+ sendClient = new PaymentChannelClientReceiver() {
+ int step = 0;
+ @Override
+ public void receiveMessage(Protos.TwoWayChannelMessage msg) {
+ // Server may send SERVER_VERSION + INITIATE in one go, so we could get both
+ if ((step != 0 || msg.getType() != Protos.TwoWayChannelMessage.MessageType.SERVER_VERSION) &&
+ (step != 1 || msg.getType() != Protos.TwoWayChannelMessage.MessageType.INITIATE))
+ fail.set(true);
+ step++;
+ server4.receiveMessage(Protos.TwoWayChannelMessage.newBuilder()
+ .setType(Protos.TwoWayChannelMessage.MessageType.CLOSE)
+ .build());
+ }
+
+ @Override
+ public void connectionOpen() {
+ Protos.ClientVersion.Builder versionNegotiationBuilder = Protos.ClientVersion.newBuilder()
+ .setMajor(0).setMinor(42);
+ server4.receiveMessage(Protos.TwoWayChannelMessage.newBuilder()
+ .setType(Protos.TwoWayChannelMessage.MessageType.CLIENT_VERSION)
+ .setClientVersion(versionNegotiationBuilder)
+ .build());
+ }
+
+ @Override
+ public void connectionClosed() {
+ inactiveFuture4.set(null);
+ }
+ @Override public void close() { }
+ };
+ server4.connectionOpen();
+ sendClient.connectionOpen();
+ inactiveFuture4.get();
+
+ // Make sure the server closes the socket on ERROR
+ final SettableFuture inactiveFuture5 = SettableFuture.create();
+ final PaymentChannelServer server5 = new PaymentChannelServer(mockPeerGroup, serverWallet, Utils.COIN,
+ new PaymentChannelServer.ServerConnection() {
+ @Override
+ public void sendToClient(Protos.TwoWayChannelMessage msg) {
+ sendClient.receiveMessage(msg);
+ }
+
+ @Override
+ public void destroyConnection(CloseReason reason) {
+ if (reason != CloseReason.REMOTE_SENT_ERROR)
+ fail.set(true);
+ sendClient.connectionClosed();
+ }
+
+ @Override public void channelOpen(Sha256Hash contractHash) { fail.set(true); }
+ @Override public void paymentIncrease(BigInteger by, BigInteger to) { fail.set(true); }
+ });
+ sendClient = new PaymentChannelClientReceiver() {
+ int step = 0;
+ @Override
+ public void receiveMessage(Protos.TwoWayChannelMessage msg) {
+ // Server may send SERVER_VERSION + INITIATE in one go, so we could get both
+ if ((step != 0 || msg.getType() != Protos.TwoWayChannelMessage.MessageType.SERVER_VERSION) &&
+ (step != 1 || msg.getType() != Protos.TwoWayChannelMessage.MessageType.INITIATE))
+ fail.set(true);
+ server5.receiveMessage(Protos.TwoWayChannelMessage.newBuilder()
+ .setType(Protos.TwoWayChannelMessage.MessageType.ERROR)
+ .setError(Protos.Error.newBuilder().setCode(Protos.Error.ErrorCode.TIMEOUT))
+ .build());
+ step++;
+ }
+
+ @Override
+ public void connectionOpen() {
+ Protos.ClientVersion.Builder versionNegotiationBuilder = Protos.ClientVersion.newBuilder()
+ .setMajor(0).setMinor(42);
+ server5.receiveMessage(Protos.TwoWayChannelMessage.newBuilder()
+ .setType(Protos.TwoWayChannelMessage.MessageType.CLIENT_VERSION)
+ .setClientVersion(versionNegotiationBuilder)
+ .build());
+ }
+
+ @Override
+ public void connectionClosed() {
+ inactiveFuture5.set(null);
+ }
+ @Override public void close() { }
+ };
+ server5.connectionOpen();
+ sendClient.connectionOpen();
+ inactiveFuture5.get();
+
+ control.verify();
+ }
+
+ @Test
+ public void testChannelResume() throws Exception {
+ // Tests various aspects of channel resuming
+
+ // Set up a mock peergroup.
+ IMocksControl control = createStrictControl();
+ final PeerGroup mockPeerGroup = control.createMock(PeerGroup.class);
+ final SettableFuture multiSigFuture = SettableFuture.create();
+ final SettableFuture multiSigFuture2 = SettableFuture.create();
+ SettableFuture closeFuture = SettableFuture.create();
+ SettableFuture closeFuture2 = SettableFuture.create();
+ final Capture broadcastMultiSig = new Capture();
+ final Capture broadcastMultiSig2 = new Capture();
+ Capture broadcastClose = new Capture();
+ Capture broadcastClose2 = new Capture();
+ expect(mockPeerGroup.broadcastTransaction(capture(broadcastMultiSig))).andReturn(multiSigFuture);
+ expect(mockPeerGroup.broadcastTransaction(capture(broadcastMultiSig2))).andReturn(multiSigFuture2);
+ expect(mockPeerGroup.broadcastTransaction(capture(broadcastClose))).andReturn(closeFuture);
+ expect(mockPeerGroup.broadcastTransaction(capture(broadcastClose2))).andReturn(closeFuture2);
+ control.replay();
+
+ // Use a mock clock
+ Utils.rollMockClock(0);
+
+ StoredPaymentChannelClientStates clientStoredChannels = (StoredPaymentChannelClientStates)wallet.getExtensions().get(StoredPaymentChannelClientStates.EXTENSION_ID);
+
+
+ // Check that server-side will reject incorrectly formatted hashes
+ final SettableFuture server1VersionSent = SettableFuture.create();
+ final SettableFuture server1InitiateSent = SettableFuture.create();
+ PaymentChannelServer server1 = new PaymentChannelServer(mockPeerGroup, serverWallet, Utils.COIN,
+ new PaymentChannelServer.ServerConnection() {
+ @Override
+ public void sendToClient(Protos.TwoWayChannelMessage msg) {
+ if (!server1VersionSent.isDone()) {
+ assertEquals(Protos.TwoWayChannelMessage.MessageType.SERVER_VERSION, msg.getType());
+ server1VersionSent.set(null);
+ return;
+ }
+ assertTrue(!server1InitiateSent.isDone() && msg.getType() == Protos.TwoWayChannelMessage.MessageType.INITIATE);
+ server1InitiateSent.set(null);
+ }
+ @Override public void destroyConnection(CloseReason reason) { fail(); }
+ @Override public void channelOpen(Sha256Hash contractHash) { fail(); }
+ @Override public void paymentIncrease(BigInteger by, BigInteger to) { fail(); }
+ });
+ server1.connectionOpen();
+ server1.receiveMessage(Protos.TwoWayChannelMessage.newBuilder()
+ .setType(Protos.TwoWayChannelMessage.MessageType.CLIENT_VERSION)
+ .setClientVersion(Protos.ClientVersion.newBuilder()
+ .setPreviousChannelContractHash(ByteString.copyFrom(new byte[]{0x00, 0x01}))
+ .setMajor(0).setMinor(42))
+ .build());
+
+ assertTrue(server1InitiateSent.isDone());
+
+ // Now open a normal channel
+ final SettableFuture client2OpenFuture = SettableFuture.create();
+ final SettableFuture client2CloseFuture = SettableFuture.create();
+ final SettableFuture server2PaymentFuture = SettableFuture.create();
+ final SettableFuture server2ContractHashFuture = SettableFuture.create();
+ final PaymentChannelServer server2 = new PaymentChannelServer(mockPeerGroup, serverWallet, Utils.COIN, new PaymentChannelServer.ServerConnection() {
+ @Override public void sendToClient(Protos.TwoWayChannelMessage msg) { sendClient.receiveMessage(msg); }
+
+ @Override
+ public void destroyConnection(CloseReason reason) {
+ assertEquals(CloseReason.SERVER_REQUESTED_CLOSE, reason);
+ }
+
+ @Override
+ public void channelOpen(Sha256Hash contractHash) {
+ server2ContractHashFuture.set(contractHash);
+ }
+
+ @Override
+ public void paymentIncrease(BigInteger by, BigInteger to) {
+ assertTrue(by.equals(Utils.CENT) && to.equals(Utils.CENT));
+ server2PaymentFuture.set(null);
+ }
+ });
+
+ PaymentChannelClient client2 = new PaymentChannelClient(wallet, myKey, Utils.COIN, Sha256Hash.create(new byte[] {}),
+ new PaymentChannelClient.ClientConnection() {
+ @Override public void sendToServer(Protos.TwoWayChannelMessage msg) { server2.receiveMessage(msg); }
+
+ @Override
+ public void destroyConnection(CloseReason reason) {
+ assertEquals(CloseReason.SERVER_REQUESTED_CLOSE, reason);
+ client2CloseFuture.set(null);
+ sendClient.connectionClosed();
+ }
+
+ @Override
+ public void channelOpen() {
+ client2OpenFuture.set(null);
+ }
+ });
+ sendClient = new PaymentChannelClientReceiverImpl(client2);
+ server2.connectionOpen();
+ client2.connectionOpen();
+
+ multiSigFuture.set(broadcastMultiSig.getValue());
+ assertTrue(client2OpenFuture.isDone() && server2ContractHashFuture.isDone());
+ assertEquals(broadcastMultiSig.getValue().getHash(), server2ContractHashFuture.get());
+
+ client2.incrementPayment(Utils.CENT);
+ assertTrue(server2PaymentFuture.isDone());
+
+ server2.close();
+ server2.connectionClosed();
+ assertFalse(client2.connectionOpen);
+ assertTrue(client2CloseFuture.isDone());
+ // There is now an open channel worth COIN-CENT with id Sha256.create(new byte[] {})
+
+ assertEquals(1, clientStoredChannels.mapChannels.size());
+
+ // Check that server-side won't attempt to reopen a nonexistent channel
+ final SettableFuture server3VersionSent = SettableFuture.create();
+ final SettableFuture server3InitiateSent = SettableFuture.create();
+ PaymentChannelServer server3 = new PaymentChannelServer(mockPeerGroup, serverWallet, Utils.COIN,
+ new PaymentChannelServer.ServerConnection() {
+ @Override
+ public void sendToClient(Protos.TwoWayChannelMessage msg) {
+ if (!server3VersionSent.isDone()) {
+ assertTrue(msg.getType() == Protos.TwoWayChannelMessage.MessageType.SERVER_VERSION);
+ server3VersionSent.set(null);
+ return;
+ }
+ assertTrue(!server3InitiateSent.isDone() && msg.getType() == Protos.TwoWayChannelMessage.MessageType.INITIATE);
+ server3InitiateSent.set(null);
+ }
+ @Override public void destroyConnection(CloseReason reason) { fail(); }
+ @Override public void channelOpen(Sha256Hash contractHash) { fail(); }
+ @Override public void paymentIncrease(BigInteger by, BigInteger to) { fail(); }
+ });
+ server3.connectionOpen();
+ server3.receiveMessage(Protos.TwoWayChannelMessage.newBuilder()
+ .setType(Protos.TwoWayChannelMessage.MessageType.CLIENT_VERSION)
+ .setClientVersion(Protos.ClientVersion.newBuilder()
+ .setPreviousChannelContractHash(ByteString.copyFrom(Sha256Hash.create(new byte[]{0x03}).getBytes()))
+ .setMajor(0).setMinor(42))
+ .build());
+
+ assertTrue(server3InitiateSent.isDone());
+
+
+ // Now reopen channel 2
+ final SettableFuture client4OpenFuture = SettableFuture.create();
+ final SettableFuture client4CloseFuture = SettableFuture.create();
+ final SettableFuture server4CloseFuture = SettableFuture.create();
+ final SettableFuture server4PaymentFuture = SettableFuture.create();
+ final PaymentChannelServer server4 = new PaymentChannelServer(mockPeerGroup, serverWallet, Utils.COIN, new PaymentChannelServer.ServerConnection() {
+ @Override public void sendToClient(Protos.TwoWayChannelMessage msg) { sendClient.receiveMessage(msg); }
+
+ @Override
+ public void destroyConnection(CloseReason reason) {
+ assertEquals(CloseReason.CLIENT_REQUESTED_CLOSE, reason);
+ server4CloseFuture.set(null);
+ }
+
+ @Override
+ public void channelOpen(Sha256Hash contractHash) {
+ try {
+ assertEquals(server2ContractHashFuture.get(), contractHash);
+ } catch (Exception e) { fail(); }
+ }
+
+ @Override
+ public void paymentIncrease(BigInteger by, BigInteger to) {
+ assertTrue(by.equals(Utils.CENT) && to.equals(Utils.CENT.shiftLeft(1)));
+ server4PaymentFuture.set(null);
+ }
+ });
+
+ PaymentChannelClient client4 = new PaymentChannelClient(wallet, myKey, Utils.COIN, Sha256Hash.create(new byte[] {}),
+ new PaymentChannelClient.ClientConnection() {
+ @Override public void sendToServer(Protos.TwoWayChannelMessage msg) { server4.receiveMessage(msg); }
+
+ @Override
+ public void destroyConnection(CloseReason reason) {
+ assertEquals(CloseReason.CLIENT_REQUESTED_CLOSE, reason);
+ client4CloseFuture.set(null);
+ }
+
+ @Override
+ public void channelOpen() {
+ client4OpenFuture.set(null);
+ }
+ });
+ sendClient = new PaymentChannelClientReceiverImpl(client4);
+ server4.connectionOpen();
+ client4.connectionOpen();
+
+ assertTrue(client4OpenFuture.isDone());
+
+ client4.incrementPayment(Utils.CENT);
+ assertTrue(server4PaymentFuture.isDone());
+
+ // Now open up a new client with the same id and make sure it doesnt attempt to reopen the channel
+ final SettableFuture client5OpenFuture = SettableFuture.create();
+ final SettableFuture client5CloseFuture = SettableFuture.create();
+ final SettableFuture server5PaymentFuture = SettableFuture.create();
+ final SettableFuture server5ContractHashFuture = SettableFuture.create();
+ final PaymentChannelServer server5 = new PaymentChannelServer(mockPeerGroup, serverWallet, Utils.COIN, new PaymentChannelServer.ServerConnection() {
+ @Override public void sendToClient(Protos.TwoWayChannelMessage msg) { sendClient.receiveMessage(msg); }
+
+ @Override
+ public void destroyConnection(CloseReason reason) {
+ assertEquals(CloseReason.SERVER_REQUESTED_CLOSE, reason);
+ sendClient.connectionClosed();
+ }
+
+ @Override
+ public void channelOpen(Sha256Hash contractHash) {
+ try {
+ assertFalse(server2ContractHashFuture.get().equals(contractHash));
+ } catch (Exception e) { fail(); }
+ server5ContractHashFuture.set(contractHash);
+ }
+
+ @Override
+ public void paymentIncrease(BigInteger by, BigInteger to) {
+ assertTrue(by.equals(Utils.CENT.shiftLeft(1)) && to.equals(Utils.CENT.shiftLeft(1)));
+ server5PaymentFuture.set(null);
+ }
+ });
+ PaymentChannelClient client5 = new PaymentChannelClient(wallet, myKey, Utils.COIN, Sha256Hash.create(new byte[] {}),
+ new PaymentChannelClient.ClientConnection() {
+ @Override public void sendToServer(Protos.TwoWayChannelMessage msg) {
+ if(msg.getType() == Protos.TwoWayChannelMessage.MessageType.CLIENT_VERSION)
+ assertFalse(msg.getClientVersion().hasPreviousChannelContractHash());
+ server5.receiveMessage(msg);
+ }
+
+ @Override
+ public void destroyConnection(CloseReason reason) {
+ assertEquals(CloseReason.SERVER_REQUESTED_CLOSE, reason);
+ client5CloseFuture.set(null);
+ }
+
+ @Override
+ public void channelOpen() {
+ client5OpenFuture.set(null);
+ }
+ });
+ sendClient = new PaymentChannelClientReceiverImpl(client5);
+ server5.connectionOpen();
+ client5.connectionOpen();
+
+ multiSigFuture2.set(broadcastMultiSig2.getValue());
+ assertTrue(client5OpenFuture.isDone() && server5ContractHashFuture.isDone());
+ assertEquals(broadcastMultiSig2.getValue().getHash(), server5ContractHashFuture.get());
+
+ client5.incrementPayment(Utils.CENT.shiftLeft(1));
+ assertTrue(server5PaymentFuture.isDone());
+
+ assertEquals(2, clientStoredChannels.mapChannels.size());
+
+ // Make sure the server won't allow the reopen either
+ // Check that server-side will reject incorrectly formatted hashes
+ final SettableFuture server6VersionSent = SettableFuture.create();
+ final SettableFuture server6InitiateSent = SettableFuture.create();
+ PaymentChannelServer server6 = new PaymentChannelServer(mockPeerGroup, serverWallet, Utils.COIN,
+ new PaymentChannelServer.ServerConnection() {
+ @Override
+ public void sendToClient(Protos.TwoWayChannelMessage msg) {
+ if (!server6VersionSent.isDone()) {
+ assertTrue(msg.getType() == Protos.TwoWayChannelMessage.MessageType.SERVER_VERSION);
+ server6VersionSent.set(null);
+ return;
+ }
+ assertTrue(!server6InitiateSent.isDone() && msg.getType() == Protos.TwoWayChannelMessage.MessageType.INITIATE);
+ server6InitiateSent.set(null);
+ }
+ @Override public void destroyConnection(CloseReason reason) { fail(); }
+ @Override public void channelOpen(Sha256Hash contractHash) { fail(); }
+ @Override public void paymentIncrease(BigInteger by, BigInteger to) { fail(); }
+ });
+ server6.connectionOpen();
+ server6.receiveMessage(Protos.TwoWayChannelMessage.newBuilder()
+ .setType(Protos.TwoWayChannelMessage.MessageType.CLIENT_VERSION)
+ .setClientVersion(Protos.ClientVersion.newBuilder()
+ .setPreviousChannelContractHash(ByteString.copyFrom(broadcastMultiSig2.getValue().getHash().getBytes()))
+ .setMajor(0).setMinor(42))
+ .build());
+
+ assertTrue(server6InitiateSent.isDone());
+
+ // Now close connection 5
+ server5.close();
+ server5.connectionClosed();
+ assertFalse(client5.connectionOpen);
+ assertTrue(client5CloseFuture.isDone());
+
+ // Now open a 4th channel with the same id and make sure it reopens the second (because the 1st is still open)
+ final SettableFuture client7OpenFuture = SettableFuture.create();
+ final SettableFuture client7CloseFuture = SettableFuture.create();
+ final SettableFuture server7CloseFuture = SettableFuture.create();
+ final SettableFuture server7PaymentFuture = SettableFuture.create();
+ final PaymentChannelServer server7 = new PaymentChannelServer(mockPeerGroup, serverWallet, Utils.COIN, new PaymentChannelServer.ServerConnection() {
+ @Override public void sendToClient(Protos.TwoWayChannelMessage msg) { sendClient.receiveMessage(msg); }
+
+ @Override
+ public void destroyConnection(CloseReason reason) {
+ assertEquals(CloseReason.CLIENT_REQUESTED_CLOSE, reason);
+ server7CloseFuture.set(null);
+ }
+
+ @Override
+ public void channelOpen(Sha256Hash contractHash) {
+ try {
+ assertEquals(server5ContractHashFuture.get(), contractHash);
+ } catch (Exception e) { fail(); }
+ }
+
+ @Override
+ public void paymentIncrease(BigInteger by, BigInteger to) {
+ assertTrue(by.equals(Utils.CENT.shiftLeft(1)) && to.equals(Utils.CENT.shiftLeft(2)));
+ server7PaymentFuture.set(null);
+ }
+ });
+
+ PaymentChannelClient client7 = new PaymentChannelClient(wallet, myKey, Utils.COIN, Sha256Hash.create(new byte[] {}),
+ new PaymentChannelClient.ClientConnection() {
+ @Override public void sendToServer(Protos.TwoWayChannelMessage msg) { server7.receiveMessage(msg); }
+
+ @Override
+ public void destroyConnection(CloseReason reason) {
+ assertEquals(CloseReason.CLIENT_REQUESTED_CLOSE, reason);
+ client7CloseFuture.set(null);
+ }
+
+ @Override
+ public void channelOpen() {
+ client7OpenFuture.set(null);
+ }
+ });
+ sendClient = new PaymentChannelClientReceiverImpl(client7);
+ server7.connectionOpen();
+ client7.connectionOpen();
+
+ assertTrue(client7OpenFuture.isDone());
+
+ client7.incrementPayment(Utils.CENT.shiftLeft(1));
+ assertTrue(server7PaymentFuture.isDone());
+
+ assertEquals(2, clientStoredChannels.mapChannels.size());
+
+ client7.close(); // Client-side close to broadcast close tx
+ assertTrue(client7CloseFuture.isDone() && server7CloseFuture.isDone());
+ client7.connectionClosed();
+ server7.connectionClosed();
+ assertFalse(client7.connectionOpen);
+
+ assertFalse(clientStoredChannels.getChannel(Sha256Hash.create(new byte[]{}), broadcastMultiSig2.getValue().getHash()).active);
+ assertTrue(clientStoredChannels.getChannel(Sha256Hash.create(new byte[]{}), broadcastMultiSig.getValue().getHash()).active);
+
+ // Now, finally, close 4
+ sendClient = new PaymentChannelClientReceiverImpl(client4);
+ client4.close(); // Client-side close to broadcast close tx
+ assertTrue(client4CloseFuture.isDone() && server4CloseFuture.isDone());
+ client4.connectionClosed();
+ server4.connectionClosed();
+ assertFalse(client4.connectionOpen);
+
+ assertFalse(clientStoredChannels.getChannel(Sha256Hash.create(new byte[]{}), broadcastMultiSig2.getValue().getHash()).active);
+ assertFalse(clientStoredChannels.getChannel(Sha256Hash.create(new byte[]{}), broadcastMultiSig.getValue().getHash()).active);
+
+ // Now roll the mock clock and recreate the client object so that it removes the channels
+ Utils.rollMockClock(60 * 60 * 24 + 60*5); // Client announces refund 5 minutes after expire time
+ final AtomicInteger broadcastCount = new AtomicInteger();
+ StoredPaymentChannelClientStates newClientStates = new StoredPaymentChannelClientStates(new TransactionBroadcaster() {
+ @Override
+ public ListenableFuture broadcastTransaction(Transaction tx) {
+ broadcastCount.incrementAndGet();
+ return null;
+ }
+ }, wallet);
+ newClientStates.deserializeWalletExtension(wallet, clientStoredChannels.serializeWalletExtension());
+
+ while (broadcastCount.get() < 4)
+ Thread.sleep(100);
+
+ assertTrue(newClientStates.mapChannels.isEmpty());
+
+ StoredPaymentChannelServerStates serverStoredChannels = (StoredPaymentChannelServerStates)serverWallet.getExtensions().get(StoredPaymentChannelServerStates.EXTENSION_ID);
+ assertTrue(serverStoredChannels.mapChannels.isEmpty());
+
+ control.verify();
+ }
+
+ @Test
+ public void testChannelExpire() throws Exception {
+ // Test that channels get properly closed when they expire
+
+ // Set up a mock peergroup.
+ IMocksControl control = createStrictControl();
+ final PeerGroup mockPeerGroup = control.createMock(PeerGroup.class);
+ // We'll broadcast two txns: multisig contract and close transaction.
+ SettableFuture multiSigFuture = SettableFuture.create();
+ SettableFuture paymentFuture = SettableFuture.create();
+ SettableFuture clientMultisigFuture = SettableFuture.create();
+ SettableFuture refundFuture = SettableFuture.create();
+
+ Capture broadcastMultiSig = new Capture();
+ Capture broadcastPayment = new Capture();
+ Capture broadcastClientMultisig = new Capture();
+ Capture broadcastRefund = new Capture();
+
+ expect(mockPeerGroup.broadcastTransaction(capture(broadcastMultiSig))).andReturn(multiSigFuture);
+ expect(mockPeerGroup.broadcastTransaction(capture(broadcastPayment))).andReturn(paymentFuture);
+ expect(mockPeerGroup.broadcastTransaction(capture(broadcastClientMultisig))).andReturn(clientMultisigFuture);
+ expect(mockPeerGroup.broadcastTransaction(capture(broadcastRefund))).andReturn(refundFuture);
+ control.replay();
+
+ // Use a mock clock
+ Utils.rollMockClock(0);
+
+ final SettableFuture serverSecondPaymentProcessedFuture = SettableFuture.create();
+ final SettableFuture serverCloseFuture = SettableFuture.create();
+ final SettableFuture contractHashFuture = SettableFuture.create();
+ final PaymentChannelServer server = new PaymentChannelServer(mockPeerGroup, serverWallet, Utils.COIN,
+ new PaymentChannelServer.ServerConnection() {
+ @Override
+ public void sendToClient(Protos.TwoWayChannelMessage msg) {
+ sendClient.receiveMessage(msg);
+ }
+
+ @Override
+ public void destroyConnection(CloseReason reason) {
+ serverCloseFuture.set(null);
+ sendClient.connectionClosed();
+ }
+
+ @Override
+ public void channelOpen(Sha256Hash contractHash) {
+ contractHashFuture.set(contractHash);
+ }
+
+ @Override
+ public void paymentIncrease(BigInteger by, BigInteger to) {
+ if (to.equals(Utils.CENT.shiftLeft(1)))
+ serverSecondPaymentProcessedFuture.set(null);
+ }
+ });
+
+ final SettableFuture clientChannelOpenFuture = SettableFuture.create();
+ PaymentChannelClient clientConnection = new PaymentChannelClient(wallet, myKey, Utils.COIN, Sha256Hash.create(new byte[] {}),
+ new PaymentChannelClient.ClientConnection() {
+ @Override
+ public void sendToServer(Protos.TwoWayChannelMessage msg) {
+ server.receiveMessage(msg);
+ }
+
+ @Override
+ public void destroyConnection(CloseReason reason) {
+ assertEquals(CloseReason.SERVER_REQUESTED_CLOSE, reason);
+ }
+
+ @Override
+ public void channelOpen() {
+ clientChannelOpenFuture.set(null);
+ }
+ });
+ sendClient = new PaymentChannelClientReceiverImpl(clientConnection);
+ server.connectionOpen();
+ clientConnection.connectionOpen(); // Recurses until channel is open
+
+ multiSigFuture.set(broadcastMultiSig.getValue());
+ assertEquals(contractHashFuture.get(), broadcastMultiSig.getValue().getHash());
+ assertTrue(clientChannelOpenFuture.isDone());
+
+ clientConnection.incrementPayment(Utils.CENT);
+ clientConnection.incrementPayment(Utils.CENT);
+ assertTrue(serverSecondPaymentProcessedFuture.isDone());
+
+ StoredPaymentChannelServerStates channels = (StoredPaymentChannelServerStates)serverWallet.getExtensions().get(StoredPaymentChannelServerStates.EXTENSION_ID);
+ StoredServerChannel storedServerChannel = channels.getChannel(broadcastMultiSig.getValue().getHash());
+ PaymentChannelServerState serverState;
+ synchronized (storedServerChannel) {
+ serverState = storedServerChannel.getState(serverWallet, mockPeerGroup);
+ }
+ assertNotNull(serverState);
+
+ server.close(); // Does not close channels themselves
+ assertTrue(serverCloseFuture.isDone());
+ server.connectionClosed();
+ assertNull(storedServerChannel.connectedHandler);
+ assertFalse(clientConnection.connectionOpen);
+
+ // Now make the channel expire (in the server's eyes)
+ Utils.rollMockClock(60 * 60 * 22 + 60); // Server gives 60 seconds of extra time in the lock time calculation so
+ // that client can have their clock off a bit, and then announces payment
+ // 2 hours before the expire time
+
+ // And make sure the server broadcasts the payment transaction
+ StoredPaymentChannelServerStates newManager = new StoredPaymentChannelServerStates(serverWallet, mockPeerGroup);
+ newManager.deserializeWalletExtension(serverWallet, channels.serializeWalletExtension());
+
+ while (!broadcastPayment.hasCaptured())
+ Thread.sleep(100);
+ paymentFuture.set(broadcastPayment.getValue());
+ assertEquals(Utils.COIN.subtract(Utils.CENT.shiftLeft(1)), broadcastPayment.getValue().getOutput(0).getValue());
+
+ // Now do the same with the client side
+ StoredPaymentChannelClientStates clientChannels = (StoredPaymentChannelClientStates)wallet.getExtensions().get(StoredPaymentChannelClientStates.EXTENSION_ID);
+ clientChannels.channelTimeoutHandler.cancel();
+ StoredClientChannel storedClientChannel = clientChannels.getChannel(Sha256Hash.create(new byte[]{}), broadcastMultiSig.getValue().getHash());
+ assertFalse(storedClientChannel.active);
+
+ Utils.rollMockClock(60 * 60 * 2 + 60*4); // Client announces refund 5 minutes after expire time
+ StoredPaymentChannelClientStates newClientStates = new StoredPaymentChannelClientStates(new TransactionBroadcaster() {
+ @Override
+ public ListenableFuture broadcastTransaction(Transaction tx) {
+ return mockPeerGroup.broadcastTransaction(tx);
+ }
+ }, wallet);
+ newClientStates.deserializeWalletExtension(wallet, clientChannels.serializeWalletExtension());
+ while (!broadcastRefund.hasCaptured())
+ Thread.sleep(100);
+ clientMultisigFuture.set(broadcastClientMultisig.getValue());
+ refundFuture.set(broadcastRefund.getValue());
+
+ assertEquals(broadcastMultiSig.getValue().getHash(), broadcastClientMultisig.getValue().getHash());
+ assertEquals(1, broadcastRefund.getValue().getOutputs().size());
+ assertTrue(broadcastRefund.getValue().isTimeLocked());
+ assertEquals(0, newClientStates.mapChannels.size());
+
+ control.verify();
+ }
+
+ @Test
+ public void testClientUnknownVersion() throws Exception {
+ // Tests client rejects unknown version
+ final SettableFuture serverReceivedError = SettableFuture.create();
+
+ ProtobufServer server = new ProtobufServer(new ProtobufParserFactory() {
+ @Nullable
+ @Override
+ public ProtobufParser getNewParser(InetAddress inetAddress, int port) {
+ return new ProtobufParser(
+ new ProtobufParser.Listener() {
+ @Override
+ public void messageReceived(ProtobufParser parser, Protos.TwoWayChannelMessage msg) {
+ if (msg.getType() != Protos.TwoWayChannelMessage.MessageType.CLIENT_VERSION &&
+ msg.getType() != Protos.TwoWayChannelMessage.MessageType.ERROR)
+ fail.set(true);
+
+ if (msg.getType() == Protos.TwoWayChannelMessage.MessageType.ERROR &&
+ (!msg.hasError() || msg.getError().getCode() != Protos.Error.ErrorCode.NO_ACCEPTABLE_VERSION))
+ fail.set(true);
+ if (msg.getType() == Protos.TwoWayChannelMessage.MessageType.ERROR)
+ serverReceivedError.set(null);
+ else
+ parser.write(Protos.TwoWayChannelMessage.newBuilder()
+ .setServerVersion(Protos.ServerVersion.newBuilder().setMajor(2))
+ .setType(Protos.TwoWayChannelMessage.MessageType.SERVER_VERSION).build());
+ }
+
+ @Override public void connectionOpen(ProtobufParser handler) { }
+ @Override public void connectionClosed(ProtobufParser handler) { }
+ }, Protos.TwoWayChannelMessage.getDefaultInstance(), Short.MAX_VALUE, 1000);
+ }
+ });
+ server.start(new InetSocketAddress("localhost", 4243));
+
+ PaymentChannelClientConnection clientConnection = new PaymentChannelClientConnection(new InetSocketAddress("localhost", 4243), 1, wallet, myKey, Utils.COIN, "");
+ try {
+ clientConnection.getChannelOpenFuture().get();
+ fail();
+ } catch (ExecutionException e) {
+ assertEquals(CloseReason.NO_ACCEPTABLE_VERSION, ((PaymentChannelCloseException)e.getCause()).getCloseReason());
+ }
+ serverReceivedError.get();
+
+ // Double-check that we cant do anything that requires an open channel
+ try {
+ clientConnection.incrementPayment(BigInteger.ONE);
+ } catch (IllegalStateException e) { }
+
+ server.stop();
+ }
+
+ @Test
+ public void testClientTimeWindowTooLarge() throws Exception {
+ // Tests that clients reject too large time windows
+ final SettableFuture serverReceivedError = SettableFuture.create();
+
+ ProtobufServer server = new ProtobufServer(new ProtobufParserFactory() {
+ @Nullable
+ @Override
+ public ProtobufParser getNewParser(InetAddress inetAddress, int port) {
+ return new ProtobufParser(
+ new ProtobufParser.Listener() {
+ @Override
+ public void messageReceived(ProtobufParser parser, Protos.TwoWayChannelMessage msg) {
+ if (msg.getType() != Protos.TwoWayChannelMessage.MessageType.CLIENT_VERSION &&
+ msg.getType() != Protos.TwoWayChannelMessage.MessageType.ERROR)
+ fail.set(true);
+
+ if (msg.getType() == Protos.TwoWayChannelMessage.MessageType.ERROR &&
+ (!msg.hasError() || msg.getError().getCode() != Protos.Error.ErrorCode.TIME_WINDOW_TOO_LARGE))
+ fail.set(true);
+ if (msg.getType() == Protos.TwoWayChannelMessage.MessageType.ERROR)
+ serverReceivedError.set(null);
+
+ parser.write(Protos.TwoWayChannelMessage.newBuilder()
+ .setServerVersion(Protos.ServerVersion.newBuilder().setMajor(0))
+ .setType(Protos.TwoWayChannelMessage.MessageType.SERVER_VERSION).build());
+ parser.write(Protos.TwoWayChannelMessage.newBuilder()
+ .setInitiate(Protos.Initiate.newBuilder().setExpireTimeSecs(Utils.now().getTime() / 1000 + 60 * 60 * 48)
+ .setMinAcceptedChannelSize(100)
+ .setMultisigKey(ByteString.copyFrom(new ECKey().getPubKey())))
+ .setType(Protos.TwoWayChannelMessage.MessageType.INITIATE).build());
+ }
+
+ @Override public void connectionOpen(ProtobufParser handler) { }
+ @Override public void connectionClosed(ProtobufParser handler) { }
+ }, Protos.TwoWayChannelMessage.getDefaultInstance(), Short.MAX_VALUE, 1000);
+ }
+ });
+ server.start(new InetSocketAddress("localhost", 4243));
+
+ PaymentChannelClientConnection clientConnection = new PaymentChannelClientConnection(new InetSocketAddress("localhost", 4243), 1, wallet, myKey, Utils.COIN, "");
+ try {
+ clientConnection.getChannelOpenFuture().get();
+ fail();
+ } catch (ExecutionException e) {
+ assertEquals(CloseReason.TIME_WINDOW_TOO_LARGE, ((PaymentChannelCloseException)e.getCause()).getCloseReason());
+ }
+ serverReceivedError.get();
+
+ server.stop();
+ }
+
+ @Test
+ public void testClientValueTooLarge() throws Exception {
+ // Tests that clients reject too high minimum channel value
+ final SettableFuture serverReceivedError = SettableFuture.create();
+
+ ProtobufServer server = new ProtobufServer(new ProtobufParserFactory() {
+ @Nullable
+ @Override
+ public ProtobufParser getNewParser(InetAddress inetAddress, int port) {
+ return new ProtobufParser(
+ new ProtobufParser.Listener() {
+ @Override
+ public void messageReceived(ProtobufParser parser, Protos.TwoWayChannelMessage msg) {
+ if (msg.getType() != Protos.TwoWayChannelMessage.MessageType.CLIENT_VERSION &&
+ msg.getType() != Protos.TwoWayChannelMessage.MessageType.ERROR)
+ fail.set(true);
+
+ if (msg.getType() == Protos.TwoWayChannelMessage.MessageType.ERROR &&
+ (!msg.hasError() || msg.getError().getCode() != Protos.Error.ErrorCode.CHANNEL_VALUE_TOO_LARGE))
+ fail.set(true);
+ if (msg.getType() == Protos.TwoWayChannelMessage.MessageType.ERROR)
+ serverReceivedError.set(null);
+
+ parser.write(Protos.TwoWayChannelMessage.newBuilder()
+ .setServerVersion(Protos.ServerVersion.newBuilder().setMajor(0))
+ .setType(Protos.TwoWayChannelMessage.MessageType.SERVER_VERSION).build());
+ parser.write(Protos.TwoWayChannelMessage.newBuilder()
+ .setInitiate(Protos.Initiate.newBuilder().setExpireTimeSecs(Utils.now().getTime() / 1000)
+ .setMinAcceptedChannelSize(Utils.COIN.add(BigInteger.ONE).longValue())
+ .setMultisigKey(ByteString.copyFrom(new ECKey().getPubKey())))
+ .setType(Protos.TwoWayChannelMessage.MessageType.INITIATE).build());
+ }
+ @Override public void connectionOpen(ProtobufParser handler) { }
+ @Override public void connectionClosed(ProtobufParser handler) { }
+ }, Protos.TwoWayChannelMessage.getDefaultInstance(), Short.MAX_VALUE, 1000);
+ }
+ });
+ server.start(new InetSocketAddress("localhost", 4243));
+
+ PaymentChannelClientConnection clientConnection = new PaymentChannelClientConnection(new InetSocketAddress("localhost", 4243), 1, wallet, myKey, Utils.COIN, "");
+ try {
+ clientConnection.getChannelOpenFuture().get();
+ fail();
+ } catch (ExecutionException e) {
+ assertEquals(CloseReason.SERVER_REQUESTED_TOO_MUCH_VALUE, ((PaymentChannelCloseException) e.getCause()).getCloseReason());
+ }
+ serverReceivedError.get();
+
+ server.stop();
+ }
+
+ @Test
+ public void testClientResumeNothing() throws Exception {
+ // Tests that clients rejects channels where the server attempts to resume a channel when the client didn't
+ // request one be resumed
+ final SettableFuture serverReceivedError = SettableFuture.create();
+
+ ProtobufServer server = new ProtobufServer(new ProtobufParserFactory() {
+ @Nullable
+ @Override
+ public ProtobufParser getNewParser(InetAddress inetAddress, int port) {
+ return new ProtobufParser(
+ new ProtobufParser.Listener() {
+ @Override
+ public void messageReceived(ProtobufParser parser, Protos.TwoWayChannelMessage msg) {
+ if (msg.getType() != Protos.TwoWayChannelMessage.MessageType.CLIENT_VERSION &&
+ msg.getType() != Protos.TwoWayChannelMessage.MessageType.ERROR)
+ fail.set(true);
+
+ if (msg.getType() == Protos.TwoWayChannelMessage.MessageType.ERROR &&
+ (!msg.hasError() || msg.getError().getCode() != Protos.Error.ErrorCode.SYNTAX_ERROR))
+ fail.set(true);
+ if (msg.getType() == Protos.TwoWayChannelMessage.MessageType.ERROR)
+ serverReceivedError.set(null);
+
+ parser.write(Protos.TwoWayChannelMessage.newBuilder()
+ .setServerVersion(Protos.ServerVersion.newBuilder().setMajor(0))
+ .setType(Protos.TwoWayChannelMessage.MessageType.SERVER_VERSION).build());
+ parser.write(Protos.TwoWayChannelMessage.newBuilder()
+ .setType(Protos.TwoWayChannelMessage.MessageType.CHANNEL_OPEN).build());
+ }
+
+ @Override public void connectionOpen(ProtobufParser handler) { }
+ @Override public void connectionClosed(ProtobufParser handler) { }
+ }, Protos.TwoWayChannelMessage.getDefaultInstance(), Short.MAX_VALUE, 1000);
+ }
+ });
+ server.start(new InetSocketAddress("localhost", 4243));
+
+ InetSocketAddress client = new InetSocketAddress("localhost", 4243);
+ PaymentChannelClientConnection clientConnection = new PaymentChannelClientConnection(client, 1, wallet, myKey, Utils.COIN, "");
+ try {
+ clientConnection.getChannelOpenFuture().get();
+ fail();
+ } catch (ExecutionException e) {
+ assertEquals(CloseReason.REMOTE_SENT_INVALID_MESSAGE, ((PaymentChannelCloseException) e.getCause()).getCloseReason());
+ }
+ serverReceivedError.get();
+
+ server.stop();
+ }
+
+ private Protos.TwoWayChannelMessage nextMsg;
+ @Test
+ public void testClientRandomMessage() throws Exception {
+ // Tests that clients rejects messages it has no idea how to handle
+ final SettableFuture clientReceivedError = SettableFuture.create();
+
+ PaymentChannelClient clientConnection = new PaymentChannelClient(wallet, myKey, Utils.COIN, Sha256Hash.create(new byte[] {}), new PaymentChannelClient.ClientConnection() {
+ @Override
+ public void sendToServer(Protos.TwoWayChannelMessage msg) {
+ nextMsg = msg;
+ }
+
+ @Override
+ public void destroyConnection(CloseReason reason) {
+ clientReceivedError.set(null);
+ }
+
+ @Override
+ public void channelOpen() {
+ fail.set(true);
+ }
+ });
+ clientConnection.connectionOpen();
+ assertEquals(Protos.TwoWayChannelMessage.MessageType.CLIENT_VERSION, nextMsg.getType());
+
+ clientConnection.receiveMessage(Protos.TwoWayChannelMessage.newBuilder()
+ .setType(Protos.TwoWayChannelMessage.MessageType.CLIENT_VERSION).build());
+ assertEquals(Protos.TwoWayChannelMessage.MessageType.ERROR, nextMsg.getType());
+ assertTrue(nextMsg.hasError());
+ assertEquals(Protos.Error.ErrorCode.SYNTAX_ERROR, nextMsg.getError().getCode());
+
+ clientReceivedError.get();
+ }
+}
diff --git a/core/src/test/java/com/google/bitcoin/protocols/channels/PaymentChannelStateTest.java b/core/src/test/java/com/google/bitcoin/protocols/channels/PaymentChannelStateTest.java
new file mode 100644
index 00000000..705fabdc
--- /dev/null
+++ b/core/src/test/java/com/google/bitcoin/protocols/channels/PaymentChannelStateTest.java
@@ -0,0 +1,758 @@
+/*
+ * 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 com.google.bitcoin.protocols.channels;
+
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.concurrent.ExecutionException;
+
+import com.google.bitcoin.core.*;
+import com.google.bitcoin.script.Script;
+import com.google.bitcoin.script.ScriptBuilder;
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
+import org.easymock.Capture;
+import org.easymock.IMocksControl;
+import org.junit.Before;
+import org.junit.Test;
+
+import static com.google.bitcoin.core.TestUtils.createFakeTx;
+import static com.google.bitcoin.core.TestUtils.makeSolvedTestBlock;
+import static org.easymock.EasyMock.*;
+import static org.junit.Assert.*;
+
+public class PaymentChannelStateTest extends TestWithWallet {
+ private ECKey serverKey;
+ private BigInteger halfCoin;
+ private Wallet serverWallet;
+ private PaymentChannelServerState serverState;
+ private PaymentChannelClientState clientState;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ wallet.addExtension(new StoredPaymentChannelClientStates(new TransactionBroadcaster() {
+ @Override
+ public ListenableFuture broadcastTransaction(Transaction tx) {
+ fail();
+ return null;
+ }
+ }, wallet));
+ sendMoneyToWallet(Utils.COIN, AbstractBlockChain.NewBlockType.BEST_CHAIN);
+ chain = new BlockChain(params, wallet, blockStore); // Recreate chain as sendMoneyToWallet will confuse it
+ serverKey = new ECKey();
+ serverWallet = new Wallet(params);
+ serverWallet.addKey(serverKey);
+ chain.addWallet(serverWallet);
+ halfCoin = Utils.toNanoCoins(0, 50);
+ }
+
+ @Test
+ public void stateErrors() throws Exception {
+ PaymentChannelClientState channelState = new PaymentChannelClientState(wallet, myKey, serverKey,
+ Utils.COIN.multiply(BigInteger.TEN), 20);
+ assertEquals(PaymentChannelClientState.State.NEW, channelState.getState());
+ try {
+ channelState.getMultisigContract();
+ fail();
+ } catch (IllegalStateException e) {
+ // Expected.
+ }
+ try {
+ channelState.initiate();
+ fail();
+ } catch (ValueOutOfRangeException e) {
+ assertTrue(e.getMessage().contains("afford"));
+ }
+ }
+
+ @Test
+ public void basic() throws Exception {
+ // Check it all works when things are normal (no attacks, no problems).
+
+ // Set up a mock peergroup.
+ IMocksControl control = createStrictControl();
+ PeerGroup mockPeerGroup = control.createMock(PeerGroup.class);
+ // We'll broadcast two txns: multisig contract and close transaction.
+ SettableFuture multiSigFuture = SettableFuture.create();
+ SettableFuture closeFuture = SettableFuture.create();
+ Capture broadcastMultiSig = new Capture();
+ Capture broadcastClose = new Capture();
+ expect(mockPeerGroup.broadcastTransaction(capture(broadcastMultiSig))).andReturn(multiSigFuture);
+ expect(mockPeerGroup.broadcastTransaction(capture(broadcastClose))).andReturn(closeFuture);
+ control.replay();
+
+ Utils.rollMockClock(0); // Use mock clock
+ final long EXPIRE_TIME = Utils.now().getTime()/1000 + 60*60*24;
+
+ serverState = new PaymentChannelServerState(mockPeerGroup, serverWallet, serverKey, EXPIRE_TIME);
+ assertEquals(PaymentChannelServerState.State.WAITING_FOR_REFUND_TRANSACTION, serverState.getState());
+
+ clientState = new PaymentChannelClientState(wallet, myKey, new ECKey(null, serverKey.getPubKey()), halfCoin, EXPIRE_TIME);
+ assertEquals(PaymentChannelClientState.State.NEW, clientState.getState());
+ clientState.initiate();
+ assertEquals(PaymentChannelClientState.State.INITIATED, clientState.getState());
+
+ // Send the refund tx from client to server and get back the signature.
+ Transaction refund = new Transaction(params, clientState.getIncompleteRefundTransaction().bitcoinSerialize());
+ byte[] refundSig = serverState.provideRefundTransaction(refund, myKey.getPubKey());
+ assertEquals(PaymentChannelServerState.State.WAITING_FOR_MULTISIG_CONTRACT, serverState.getState());
+ // This verifies that the refund can spend the multi-sig output when run.
+ clientState.provideRefundSignature(refundSig);
+ assertEquals(PaymentChannelClientState.State.PROVIDE_MULTISIG_CONTRACT_TO_SERVER, clientState.getState());
+
+ // Validate the multisig contract looks right.
+ Transaction multisigContract = new Transaction(params, clientState.getMultisigContract().bitcoinSerialize());
+ assertEquals(PaymentChannelClientState.State.READY, clientState.getState());
+ assertEquals(2, multisigContract.getOutputs().size()); // One multi-sig, one change.
+ Script script = multisigContract.getOutput(0).getScriptPubKey();
+ assertTrue(script.isSentToMultiSig());
+ script = multisigContract.getOutput(1).getScriptPubKey();
+ assertTrue(script.isSentToAddress());
+ assertTrue(wallet.getPendingTransactions().contains(multisigContract));
+
+ // Provide the server with the multisig contract and simulate successful propagation/acceptance.
+ serverState.provideMultiSigContract(multisigContract);
+ assertEquals(PaymentChannelServerState.State.WAITING_FOR_MULTISIG_ACCEPTANCE, serverState.getState());
+ multiSigFuture.set(broadcastMultiSig.getValue());
+ assertEquals(PaymentChannelServerState.State.READY, serverState.getState());
+
+ // Make sure the refund transaction is not in the wallet and multisig contract's output is not connected to it
+ assertEquals(2, wallet.getTransactions(false).size());
+ Iterator walletTransactionIterator = wallet.getTransactions(false).iterator();
+ Transaction clientWalletMultisigContract = walletTransactionIterator.next();
+ assertFalse(clientWalletMultisigContract.getHash().equals(clientState.getCompletedRefundTransaction().getHash()));
+ if (!clientWalletMultisigContract.getHash().equals(multisigContract.getHash())) {
+ clientWalletMultisigContract = walletTransactionIterator.next();
+ assertFalse(clientWalletMultisigContract.getHash().equals(clientState.getCompletedRefundTransaction().getHash()));
+ } else
+ assertFalse(walletTransactionIterator.next().getHash().equals(clientState.getCompletedRefundTransaction().getHash()));
+ assertEquals(multisigContract.getHash(), clientWalletMultisigContract.getHash());
+ assertFalse(clientWalletMultisigContract.getInput(0).getConnectedOutput().getSpentBy().getParentTransaction().getHash().equals(refund.getHash()));
+
+ // Both client and server are now in the ready state. Simulate a few micropayments of 0.005 bitcoins.
+ BigInteger size = halfCoin.divide(BigInteger.TEN).divide(BigInteger.TEN);
+ BigInteger totalPayment = BigInteger.ZERO;
+ for (int i = 0; i < 5; i++) {
+ byte[] signature = clientState.incrementPaymentBy(size);
+ totalPayment = totalPayment.add(size);
+ serverState.incrementPayment(halfCoin.subtract(totalPayment), signature);
+ }
+
+ // And close the channel.
+ serverState.close();
+ assertEquals(PaymentChannelServerState.State.CLOSING, serverState.getState());
+ Transaction closeTx = broadcastClose.getValue();
+ closeFuture.set(closeTx);
+ assertEquals(PaymentChannelServerState.State.CLOSED, serverState.getState());
+ control.verify();
+
+ // Create a block with multisig contract and payment transaction in it and give it to both wallets
+ chain.add(makeSolvedTestBlock(blockStore.getChainHead().getHeader(), multisigContract,
+ new Transaction(params, closeTx.bitcoinSerialize())));
+
+ assertEquals(size.multiply(BigInteger.valueOf(5)), serverWallet.getBalance(new Wallet.DefaultCoinSelector() {
+ @Override
+ protected boolean shouldSelect(Transaction tx) {
+ if (tx.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING)
+ return true;
+ return false;
+ }
+ }));
+ assertEquals(0, serverWallet.getPendingTransactions().size());
+
+ assertEquals(Utils.COIN.subtract(size.multiply(BigInteger.valueOf(5))), wallet.getBalance(new Wallet.DefaultCoinSelector() {
+ @Override
+ protected boolean shouldSelect(Transaction tx) {
+ if (tx.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING)
+ return true;
+ return false;
+ }
+ }));
+ assertEquals(0, wallet.getPendingTransactions().size());
+ assertEquals(3, wallet.getTransactions(false).size());
+
+ walletTransactionIterator = wallet.getTransactions(false).iterator();
+ Transaction clientWalletCloseTransaction = walletTransactionIterator.next();
+ if (!clientWalletCloseTransaction.getHash().equals(closeTx.getHash()))
+ clientWalletCloseTransaction = walletTransactionIterator.next();
+ if (!clientWalletCloseTransaction.getHash().equals(closeTx.getHash()))
+ clientWalletCloseTransaction = walletTransactionIterator.next();
+ assertEquals(closeTx.getHash(), clientWalletCloseTransaction.getHash());
+ assertTrue(clientWalletCloseTransaction.getInput(0).getConnectedOutput() != null);
+
+ control.verify();
+ }
+
+ @Test
+ public void setupDoS() throws Exception {
+ // Check that if the other side stops after we have provided a signed multisig contract, that after a timeout
+ // we can broadcast the refund and get our balance back.
+
+ // Spend the client wallet's one coin
+ Transaction spendCoinTx = wallet.sendCoinsOffline(Wallet.SendRequest.to(new ECKey().toAddress(params), Utils.COIN));
+ assertEquals(wallet.getBalance(), BigInteger.ZERO);
+ chain.add(makeSolvedTestBlock(blockStore.getChainHead().getHeader(), spendCoinTx, createFakeTx(params, Utils.CENT, myAddress)));
+ assertEquals(wallet.getBalance(), Utils.CENT);
+
+ // Set up a mock peergroup.
+ IMocksControl control = createStrictControl();
+ final PeerGroup mockPeerGroup = control.createMock(PeerGroup.class);
+ // We'll broadcast three txns: multisig contract twice (both server and client) and refund transaction.
+ SettableFuture serverMultiSigFuture = SettableFuture.create();
+ SettableFuture paymentFuture = SettableFuture.create();
+ SettableFuture clientMultiSigFuture = SettableFuture.create();
+ SettableFuture refundFuture = SettableFuture.create();
+ Capture serverBroadcastMultiSig = new Capture();
+ Capture broadcastPayment = new Capture();
+ Capture clientBroadcastMultiSig = new Capture();
+ Capture broadcastRefund = new Capture();
+ expect(mockPeerGroup.broadcastTransaction(capture(serverBroadcastMultiSig))).andReturn(serverMultiSigFuture);
+ expect(mockPeerGroup.broadcastTransaction(capture(broadcastPayment))).andReturn(paymentFuture);
+ expect(mockPeerGroup.broadcastTransaction(capture(clientBroadcastMultiSig))).andReturn(clientMultiSigFuture);
+ expect(mockPeerGroup.broadcastTransaction(capture(broadcastRefund))).andReturn(refundFuture);
+ control.replay();
+
+ // Set the wallet's stored states to use our real test PeerGroup
+ StoredPaymentChannelClientStates stateStorage = new StoredPaymentChannelClientStates(new TransactionBroadcaster() {
+ @Override
+ public ListenableFuture broadcastTransaction(Transaction tx) {
+ return mockPeerGroup.broadcastTransaction(tx);
+ }
+ }, wallet);
+ wallet.addOrUpdateExtension(stateStorage);
+
+ Utils.rollMockClock(0); // Use mock clock
+ final long EXPIRE_TIME = Utils.now().getTime()/1000 + 60*60*24;
+
+ serverState = new PaymentChannelServerState(mockPeerGroup, serverWallet, serverKey, EXPIRE_TIME);
+ assertEquals(PaymentChannelServerState.State.WAITING_FOR_REFUND_TRANSACTION, serverState.getState());
+
+ clientState = new PaymentChannelClientState(wallet, myKey, new ECKey(null, serverKey.getPubKey()),
+ Utils.CENT.divide(BigInteger.valueOf(2)), EXPIRE_TIME);
+ assertEquals(PaymentChannelClientState.State.NEW, clientState.getState());
+ assertEquals(Utils.CENT.divide(BigInteger.valueOf(2)), clientState.getTotalValue());
+ clientState.initiate();
+ // We will have to pay min_tx_fee twice - both the multisig contract and the refund tx
+ assertEquals(clientState.getRefundTxFees(), Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.multiply(BigInteger.valueOf(2)));
+ assertEquals(PaymentChannelClientState.State.INITIATED, clientState.getState());
+
+ // Send the refund tx from client to server and get back the signature.
+ Transaction refund = new Transaction(params, clientState.getIncompleteRefundTransaction().bitcoinSerialize());
+ byte[] refundSig = serverState.provideRefundTransaction(refund, myKey.getPubKey());
+ assertEquals(PaymentChannelServerState.State.WAITING_FOR_MULTISIG_CONTRACT, serverState.getState());
+ // This verifies that the refund can spend the multi-sig output when run.
+ clientState.provideRefundSignature(refundSig);
+ assertEquals(PaymentChannelClientState.State.PROVIDE_MULTISIG_CONTRACT_TO_SERVER, clientState.getState());
+
+ // Validate the multisig contract looks right.
+ Transaction multisigContract = new Transaction(params, clientState.getMultisigContract().bitcoinSerialize());
+ assertEquals(PaymentChannelClientState.State.READY, clientState.getState());
+ assertEquals(2, multisigContract.getOutputs().size()); // One multi-sig, one change.
+ Script script = multisigContract.getOutput(0).getScriptPubKey();
+ assertTrue(script.isSentToMultiSig());
+ script = multisigContract.getOutput(1).getScriptPubKey();
+ assertTrue(script.isSentToAddress());
+ assertTrue(wallet.getPendingTransactions().contains(multisigContract));
+
+ // Provide the server with the multisig contract and simulate successful propagation/acceptance.
+ serverState.provideMultiSigContract(multisigContract);
+ assertEquals(PaymentChannelServerState.State.WAITING_FOR_MULTISIG_ACCEPTANCE, serverState.getState());
+ serverMultiSigFuture.set(serverBroadcastMultiSig.getValue());
+ assertEquals(PaymentChannelServerState.State.READY, serverState.getState());
+
+ // Pay a tiny bit
+ serverState.incrementPayment(Utils.CENT.divide(BigInteger.valueOf(2)).subtract(Utils.CENT.divide(BigInteger.TEN)),
+ clientState.incrementPaymentBy(Utils.CENT.divide(BigInteger.TEN)));
+
+ // Advance time until our we get close enough to lock time that server should rebroadcast
+ Utils.rollMockClock(60*60*22);
+ // ... and store server to get it to broadcast payment transaction
+ serverState.storeChannelInWallet(null);
+ while (!broadcastPayment.hasCaptured())
+ Thread.sleep(100);
+ Exception paymentException = new RuntimeException("I'm sorry, but the network really just doesn't like you");
+ paymentFuture.setException(paymentException);
+ try {
+ serverState.close().get();
+ } catch (ExecutionException e) {
+ assertTrue(e.getCause() == paymentException);
+ }
+ assertEquals(PaymentChannelServerState.State.ERROR, serverState.getState());
+
+ // Now advance until client should rebroadcast
+ Utils.rollMockClock(60*60*2 + 60*5);
+
+ // Now store the client state in a stored state object which handles the rebroadcasting
+ clientState.storeChannelInWallet(Sha256Hash.create(new byte[] {}));
+ while (!broadcastRefund.hasCaptured())
+ Thread.sleep(100);
+
+ Transaction clientBroadcastedMultiSig = clientBroadcastMultiSig.getValue();
+ assertTrue(clientBroadcastedMultiSig.getHash().equals(multisigContract.getHash()));
+ for (TransactionInput input : clientBroadcastedMultiSig.getInputs())
+ input.verify();
+ clientMultiSigFuture.set(clientBroadcastedMultiSig);
+
+ Transaction clientBroadcastedRefund = broadcastRefund.getValue();
+ assertTrue(clientBroadcastedRefund.getHash().equals(clientState.getCompletedRefundTransaction().getHash()));
+ for (TransactionInput input : clientBroadcastedRefund.getInputs()) {
+ // If the multisig output is connected, the wallet will fail to deserialize
+ if (input.getOutpoint().getHash().equals(clientBroadcastedMultiSig.getHash()))
+ assertNull(input.getConnectedOutput().getSpentBy());
+ input.verify(clientBroadcastedMultiSig.getOutput(0));
+ }
+ refundFuture.set(clientBroadcastedRefund);
+
+ // Create a block with multisig contract and refund transaction in it and give it to both wallets,
+ // making getBalance() include the transactions
+ chain.add(makeSolvedTestBlock(blockStore.getChainHead().getHeader(), multisigContract,clientBroadcastedRefund));
+
+ // Make sure we actually had to pay what initialize() told us we would
+ assertEquals(wallet.getBalance(), Utils.CENT.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.multiply(BigInteger.valueOf(2))));
+
+ try {
+ // After its expired, we cant still increment payment
+ clientState.incrementPaymentBy(Utils.CENT);
+ fail();
+ } catch (IllegalStateException e) { }
+
+ control.verify();
+ }
+
+ @Test
+ public void checkBadData() throws Exception {
+ // Check that if signatures/transactions/etc are corrupted, the protocol rejects them correctly.
+
+ // Set up a mock peergroup.
+ IMocksControl control = createStrictControl();
+ PeerGroup mockPeerGroup = control.createMock(PeerGroup.class);
+
+ // We'll broadcast only one tx: multisig contract
+ SettableFuture multiSigFuture = SettableFuture.create();
+ Capture broadcastMultiSig = new Capture();
+ expect(mockPeerGroup.broadcastTransaction(capture(broadcastMultiSig))).andReturn(multiSigFuture);
+ control.replay();
+
+ Utils.rollMockClock(0); // Use mock clock
+ final long EXPIRE_TIME = Utils.now().getTime()/1000 + 60*60*24;
+
+ serverState = new PaymentChannelServerState(mockPeerGroup, serverWallet, serverKey, EXPIRE_TIME);
+ assertEquals(PaymentChannelServerState.State.WAITING_FOR_REFUND_TRANSACTION, serverState.getState());
+
+ try {
+ clientState = new PaymentChannelClientState(wallet, myKey, new ECKey(null,
+ Arrays.copyOf(serverKey.getPubKey(), serverKey.getPubKey().length + 1)), halfCoin, EXPIRE_TIME);
+ } catch (VerificationException e) {
+ assertTrue(e.getMessage().contains("not canonical"));
+ }
+
+ clientState = new PaymentChannelClientState(wallet, myKey, new ECKey(null, serverKey.getPubKey()), halfCoin, EXPIRE_TIME);
+ assertEquals(PaymentChannelClientState.State.NEW, clientState.getState());
+ clientState.initiate();
+ assertEquals(PaymentChannelClientState.State.INITIATED, clientState.getState());
+
+ // Test refund transaction with any number of issues
+ byte[] refundTxBytes = clientState.getIncompleteRefundTransaction().bitcoinSerialize();
+ Transaction refund = new Transaction(params, refundTxBytes);
+ refund.addOutput(BigInteger.ZERO, new ECKey().toAddress(params));
+ try {
+ serverState.provideRefundTransaction(refund, myKey.getPubKey());
+ fail();
+ } catch (VerificationException e) {}
+
+ refund = new Transaction(params, refundTxBytes);
+ refund.addInput(new TransactionInput(params, refund, new byte[] {}, new TransactionOutPoint(params, 42, refund.getHash())));
+ try {
+ serverState.provideRefundTransaction(refund, myKey.getPubKey());
+ fail();
+ } catch (VerificationException e) {}
+
+ refund = new Transaction(params, refundTxBytes);
+ refund.setLockTime(0);
+ try {
+ serverState.provideRefundTransaction(refund, myKey.getPubKey());
+ fail();
+ } catch (VerificationException e) {}
+
+ refund = new Transaction(params, refundTxBytes);
+ refund.getInput(0).setSequenceNumber(TransactionInput.NO_SEQUENCE);
+ try {
+ serverState.provideRefundTransaction(refund, myKey.getPubKey());
+ fail();
+ } catch (VerificationException e) {}
+
+ refund = new Transaction(params, refundTxBytes);
+ byte[] refundSig = serverState.provideRefundTransaction(refund, myKey.getPubKey());
+ try { serverState.provideRefundTransaction(refund, myKey.getPubKey()); fail(); } catch (IllegalStateException e) {}
+ assertEquals(PaymentChannelServerState.State.WAITING_FOR_MULTISIG_CONTRACT, serverState.getState());
+
+ byte[] refundSigCopy = Arrays.copyOf(refundSig, refundSig.length);
+ refundSigCopy[refundSigCopy.length-1] = (byte) (Transaction.SigHash.NONE.ordinal() + 1);
+ try {
+ clientState.provideRefundSignature(refundSigCopy);
+ fail();
+ } catch (VerificationException e) {
+ assertTrue(e.getMessage().contains("SIGHASH_NONE"));
+ }
+
+ refundSigCopy = Arrays.copyOf(refundSig, refundSig.length);
+ refundSigCopy[3] ^= 0x42; // Make the signature fail standard checks
+ try {
+ clientState.provideRefundSignature(refundSigCopy);
+ fail();
+ } catch (VerificationException e) {
+ assertTrue(e.getMessage().contains("not canonical"));
+ }
+
+ refundSigCopy = Arrays.copyOf(refundSig, refundSig.length);
+ refundSigCopy[10] ^= 0x42; // Flip some random bits in the signature (to make it invalid, not just nonstandard)
+ try {
+ clientState.provideRefundSignature(refundSigCopy);
+ fail();
+ } catch (VerificationException e) {
+ assertFalse(e.getMessage().contains("not canonical"));
+ }
+
+ refundSigCopy = Arrays.copyOf(refundSig, refundSig.length);
+ try { clientState.getCompletedRefundTransaction(); fail(); } catch (IllegalStateException e) {}
+ clientState.provideRefundSignature(refundSigCopy);
+ try { clientState.provideRefundSignature(refundSigCopy); fail(); } catch (IllegalStateException e) {}
+ assertEquals(PaymentChannelClientState.State.PROVIDE_MULTISIG_CONTRACT_TO_SERVER, clientState.getState());
+
+ try { clientState.incrementPaymentBy(BigInteger.ONE); fail(); } catch (IllegalStateException e) {}
+
+ byte[] multisigContractSerialized = clientState.getMultisigContract().bitcoinSerialize();
+
+ Transaction multisigContract = new Transaction(params, multisigContractSerialized);
+ multisigContract.clearOutputs();
+ multisigContract.addOutput(halfCoin, ScriptBuilder.createMultiSigOutputScript(2, Lists.newArrayList(serverKey, myKey)));
+ try {
+ serverState.provideMultiSigContract(multisigContract);
+ fail();
+ } catch (VerificationException e) {
+ assertTrue(e.getMessage().contains("client and server in that order"));
+ }
+
+ multisigContract = new Transaction(params, multisigContractSerialized);
+ multisigContract.clearOutputs();
+ multisigContract.addOutput(BigInteger.ZERO, ScriptBuilder.createMultiSigOutputScript(2, Lists.newArrayList(myKey, serverKey)));
+ try {
+ serverState.provideMultiSigContract(multisigContract);
+ fail();
+ } catch (VerificationException e) {
+ assertTrue(e.getMessage().contains("zero value"));
+ }
+
+ multisigContract = new Transaction(params, multisigContractSerialized);
+ multisigContract.clearOutputs();
+ multisigContract.addOutput(new TransactionOutput(params, multisigContract, halfCoin, new byte[] {0x01}));
+ try {
+ serverState.provideMultiSigContract(multisigContract);
+ fail();
+ } catch (VerificationException e) {}
+
+ multisigContract = new Transaction(params, multisigContractSerialized);
+ ListenableFuture multisigStateFuture = serverState.provideMultiSigContract(multisigContract);
+ try { serverState.provideMultiSigContract(multisigContract); fail(); } catch (IllegalStateException e) {}
+ assertEquals(PaymentChannelServerState.State.WAITING_FOR_MULTISIG_ACCEPTANCE, serverState.getState());
+ assertFalse(multisigStateFuture.isDone());
+ multiSigFuture.set(broadcastMultiSig.getValue());
+ assertEquals(multisigStateFuture.get(), serverState);
+ assertEquals(PaymentChannelServerState.State.READY, serverState.getState());
+
+ // Both client and server are now in the ready state. Simulate a few micropayments of 0.005 bitcoins.
+ BigInteger size = halfCoin.divide(BigInteger.TEN).divide(BigInteger.TEN);
+ BigInteger totalPayment = BigInteger.ZERO;
+ try {
+ clientState.incrementPaymentBy(Utils.COIN);
+ fail();
+ } catch (ValueOutOfRangeException e) {}
+
+ byte[] signature = clientState.incrementPaymentBy(size);
+ totalPayment = totalPayment.add(size);
+
+ byte[] signatureCopy = Arrays.copyOf(signature, signature.length);
+ signatureCopy[signatureCopy.length - 1] = (byte) ((Transaction.SigHash.NONE.ordinal() + 1) | 0x80);
+ try {
+ serverState.incrementPayment(halfCoin.subtract(totalPayment), signatureCopy);
+ fail();
+ } catch (VerificationException e) {}
+
+ signatureCopy = Arrays.copyOf(signature, signature.length);
+ signatureCopy[2] ^= 0x42; // Make the signature fail standard checks
+ try {
+ serverState.incrementPayment(halfCoin.subtract(totalPayment), signatureCopy);
+ fail();
+ } catch (VerificationException e) {
+ assertTrue(e.getMessage().contains("not canonical"));
+ }
+
+ signatureCopy = Arrays.copyOf(signature, signature.length);
+ signatureCopy[10] ^= 0x42; // Flip some random bits in the signature (to make it invalid, not just nonstandard)
+ try {
+ serverState.incrementPayment(halfCoin.subtract(totalPayment), signatureCopy);
+ fail();
+ } catch (VerificationException e) {
+ assertFalse(e.getMessage().contains("not canonical"));
+ }
+
+ serverState.incrementPayment(halfCoin.subtract(totalPayment), signature);
+
+ // Pay the rest (signed with SIGHASH_NONE|SIGHASH_ANYONECANPAY)
+ byte[] signature2 = clientState.incrementPaymentBy(halfCoin.subtract(totalPayment));
+ totalPayment = totalPayment.add(halfCoin.subtract(totalPayment));
+ assertEquals(totalPayment, halfCoin);
+
+ signatureCopy = Arrays.copyOf(signature, signature.length);
+ signatureCopy[signatureCopy.length - 1] = (byte) ((Transaction.SigHash.SINGLE.ordinal() + 1) | 0x80);
+ try {
+ serverState.incrementPayment(halfCoin.subtract(totalPayment), signatureCopy);
+ fail();
+ } catch (VerificationException e) {}
+
+ serverState.incrementPayment(halfCoin.subtract(totalPayment), signature2);
+
+ serverState.incrementPayment(halfCoin.subtract(totalPayment.subtract(size)), signature);
+ assertEquals(serverState.getBestValueToMe(), totalPayment);
+
+ try {
+ clientState.incrementPaymentBy(BigInteger.ONE.negate());
+ fail();
+ } catch (ValueOutOfRangeException e) {}
+
+ try {
+ clientState.incrementPaymentBy(halfCoin.subtract(size).add(BigInteger.ONE));
+ fail();
+ } catch (ValueOutOfRangeException e) {}
+
+ control.verify();
+ }
+
+ @Test
+ public void feesTest() throws Exception {
+ // Test that transactions are getting the necessary fees
+
+ // Spend the client wallet's one coin
+ wallet.sendCoinsOffline(Wallet.SendRequest.to(new ECKey().toAddress(params), Utils.COIN));
+ assertEquals(wallet.getBalance(), BigInteger.ZERO);
+
+ chain.add(makeSolvedTestBlock(blockStore.getChainHead().getHeader(), createFakeTx(params, Utils.CENT, myAddress)));
+ assertEquals(wallet.getBalance(), Utils.CENT);
+
+ // Set up a mock peergroup.
+ IMocksControl control = createStrictControl();
+ PeerGroup mockPeerGroup = control.createMock(PeerGroup.class);
+ // We'll broadcast two txns: multisig contract and close transaction.
+ SettableFuture multiSigFuture = SettableFuture.create();
+ SettableFuture closeFuture = SettableFuture.create();
+ Capture broadcastMultiSig = new Capture();
+ Capture broadcastClose = new Capture();
+ expect(mockPeerGroup.broadcastTransaction(capture(broadcastMultiSig))).andReturn(multiSigFuture);
+ expect(mockPeerGroup.broadcastTransaction(capture(broadcastClose))).andReturn(closeFuture);
+ control.replay();
+
+ Utils.rollMockClock(0); // Use mock clock
+ final long EXPIRE_TIME = Utils.now().getTime()/1000 + 60*60*24;
+
+ serverState = new PaymentChannelServerState(mockPeerGroup, serverWallet, serverKey, EXPIRE_TIME);
+ assertEquals(PaymentChannelServerState.State.WAITING_FOR_REFUND_TRANSACTION, serverState.getState());
+
+ // Clearly ONE is far too small to be useful
+ clientState = new PaymentChannelClientState(wallet, myKey, new ECKey(null, serverKey.getPubKey()), BigInteger.ONE, EXPIRE_TIME);
+ assertEquals(PaymentChannelClientState.State.NEW, clientState.getState());
+ try {
+ clientState.initiate();
+ fail();
+ } catch (ValueOutOfRangeException e) {}
+
+ clientState = new PaymentChannelClientState(wallet, myKey, new ECKey(null, serverKey.getPubKey()),
+ Transaction.MIN_NONDUST_OUTPUT.subtract(BigInteger.ONE).add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE),
+ EXPIRE_TIME);
+ assertEquals(PaymentChannelClientState.State.NEW, clientState.getState());
+ try {
+ clientState.initiate();
+ fail();
+ } catch (ValueOutOfRangeException e) {}
+
+ // Verify that MIN_NONDUST_OUTPUT + MIN_TX_FEE is accepted
+ clientState = new PaymentChannelClientState(wallet, myKey, new ECKey(null, serverKey.getPubKey()),
+ Transaction.MIN_NONDUST_OUTPUT.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE), EXPIRE_TIME);
+ assertEquals(PaymentChannelClientState.State.NEW, clientState.getState());
+ // We'll have to pay REFERENCE_DEFAULT_MIN_TX_FEE twice (multisig+refund), and we'll end up getting back nearly nothing...
+ clientState.initiate();
+ assertEquals(clientState.getRefundTxFees(), Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.multiply(BigInteger.valueOf(2)));
+ assertEquals(PaymentChannelClientState.State.INITIATED, clientState.getState());
+
+ // Now actually use a more useful CENT
+ clientState = new PaymentChannelClientState(wallet, myKey, new ECKey(null, serverKey.getPubKey()), Utils.CENT, EXPIRE_TIME);
+ assertEquals(PaymentChannelClientState.State.NEW, clientState.getState());
+ clientState.initiate();
+ assertEquals(clientState.getRefundTxFees(), BigInteger.ZERO);
+ assertEquals(PaymentChannelClientState.State.INITIATED, clientState.getState());
+
+ // Send the refund tx from client to server and get back the signature.
+ Transaction refund = new Transaction(params, clientState.getIncompleteRefundTransaction().bitcoinSerialize());
+ byte[] refundSig = serverState.provideRefundTransaction(refund, myKey.getPubKey());
+ assertEquals(PaymentChannelServerState.State.WAITING_FOR_MULTISIG_CONTRACT, serverState.getState());
+ // This verifies that the refund can spend the multi-sig output when run.
+ clientState.provideRefundSignature(refundSig);
+ assertEquals(PaymentChannelClientState.State.PROVIDE_MULTISIG_CONTRACT_TO_SERVER, clientState.getState());
+
+ // Get the multisig contract
+ Transaction multisigContract = new Transaction(params, clientState.getMultisigContract().bitcoinSerialize());
+ assertEquals(PaymentChannelClientState.State.READY, clientState.getState());
+
+ // Provide the server with the multisig contract and simulate successful propagation/acceptance.
+ serverState.provideMultiSigContract(multisigContract);
+ assertEquals(PaymentChannelServerState.State.WAITING_FOR_MULTISIG_ACCEPTANCE, serverState.getState());
+ multiSigFuture.set(broadcastMultiSig.getValue());
+ assertEquals(PaymentChannelServerState.State.READY, serverState.getState());
+
+ // Both client and server are now in the ready state. Simulate a few micropayments
+ BigInteger totalPayment = BigInteger.ZERO;
+
+ // We can send as little as we want - its up to the server to get the fees right
+ byte[] signature = clientState.incrementPaymentBy(BigInteger.ONE);
+ totalPayment = totalPayment.add(BigInteger.ONE);
+ serverState.incrementPayment(Utils.CENT.subtract(totalPayment), signature);
+
+ // We can't refund more than the contract is worth...
+ try {
+ serverState.incrementPayment(Utils.CENT.add(BigInteger.ONE), signature);
+ fail();
+ } catch (ValueOutOfRangeException e) {}
+
+ // We cannot, however, send just under the total value - our refund would make it unspendable
+ try {
+ clientState.incrementPaymentBy(Utils.CENT.subtract(Transaction.MIN_NONDUST_OUTPUT));
+ fail();
+ } catch (ValueOutOfRangeException e) {}
+ // The server also won't accept it if we do that
+ try {
+ serverState.incrementPayment(Transaction.MIN_NONDUST_OUTPUT.subtract(BigInteger.ONE), signature);
+ fail();
+ } catch (ValueOutOfRangeException e) {}
+
+ signature = clientState.incrementPaymentBy(Utils.CENT.subtract(BigInteger.ONE));
+ totalPayment = totalPayment.add(Utils.CENT.subtract(BigInteger.ONE));
+ assertEquals(totalPayment, Utils.CENT);
+ serverState.incrementPayment(Utils.CENT.subtract(totalPayment), signature);
+
+ // And close the channel.
+ serverState.close();
+ assertEquals(PaymentChannelServerState.State.CLOSING, serverState.getState());
+ Transaction closeTx = broadcastClose.getValue();
+ closeFuture.set(closeTx);
+ assertEquals(PaymentChannelServerState.State.CLOSED, serverState.getState());
+ serverState.close();
+ assertEquals(PaymentChannelServerState.State.CLOSED, serverState.getState());
+ control.verify();
+ }
+
+ @Test
+ public void serverAddsFeeTest() throws Exception {
+ // Test that the server properly adds the necessary fee at the end (or just drops the payment if its not worth it)
+
+ // Set up a mock peergroup.
+ IMocksControl control = createStrictControl();
+ PeerGroup mockPeerGroup = control.createMock(PeerGroup.class);
+ // We'll broadcast two txns: multisig contract and close transaction.
+ SettableFuture multiSigFuture = SettableFuture.create();
+ SettableFuture closeFuture = SettableFuture.create();
+ Capture broadcastMultiSig = new Capture();
+ Capture broadcastClose = new Capture();
+ expect(mockPeerGroup.broadcastTransaction(capture(broadcastMultiSig))).andReturn(multiSigFuture);
+ expect(mockPeerGroup.broadcastTransaction(capture(broadcastClose))).andReturn(closeFuture);
+ control.replay();
+
+ Utils.rollMockClock(0); // Use mock clock
+ final long EXPIRE_TIME = Utils.now().getTime()/1000 + 60*60*24;
+
+ serverState = new PaymentChannelServerState(mockPeerGroup, serverWallet, serverKey, EXPIRE_TIME);
+ assertEquals(PaymentChannelServerState.State.WAITING_FOR_REFUND_TRANSACTION, serverState.getState());
+
+ clientState = new PaymentChannelClientState(wallet, myKey, new ECKey(null, serverKey.getPubKey()), Utils.CENT, EXPIRE_TIME);
+ assertEquals(PaymentChannelClientState.State.NEW, clientState.getState());
+ clientState.initiate();
+ assertEquals(PaymentChannelClientState.State.INITIATED, clientState.getState());
+
+ // Send the refund tx from client to server and get back the signature.
+ Transaction refund = new Transaction(params, clientState.getIncompleteRefundTransaction().bitcoinSerialize());
+ byte[] refundSig = serverState.provideRefundTransaction(refund, myKey.getPubKey());
+ assertEquals(PaymentChannelServerState.State.WAITING_FOR_MULTISIG_CONTRACT, serverState.getState());
+ // This verifies that the refund can spend the multi-sig output when run.
+ clientState.provideRefundSignature(refundSig);
+ assertEquals(PaymentChannelClientState.State.PROVIDE_MULTISIG_CONTRACT_TO_SERVER, clientState.getState());
+
+ // Validate the multisig contract looks right.
+ Transaction multisigContract = new Transaction(params, clientState.getMultisigContract().bitcoinSerialize());
+ assertEquals(PaymentChannelClientState.State.READY, clientState.getState());
+ assertEquals(2, multisigContract.getOutputs().size()); // One multi-sig, one change.
+ Script script = multisigContract.getOutput(0).getScriptPubKey();
+ assertTrue(script.isSentToMultiSig());
+ script = multisigContract.getOutput(1).getScriptPubKey();
+ assertTrue(script.isSentToAddress());
+ assertTrue(wallet.getPendingTransactions().contains(multisigContract));
+
+ // Provide the server with the multisig contract and simulate successful propagation/acceptance.
+ serverState.provideMultiSigContract(multisigContract);
+ assertEquals(PaymentChannelServerState.State.WAITING_FOR_MULTISIG_ACCEPTANCE, serverState.getState());
+ multiSigFuture.set(broadcastMultiSig.getValue());
+ assertEquals(PaymentChannelServerState.State.READY, serverState.getState());
+
+ // Both client and server are now in the ready state, split the channel in half
+ byte[] signature = clientState.incrementPaymentBy(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.subtract(BigInteger.ONE));
+ BigInteger totalRefund = Utils.CENT.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.subtract(BigInteger.ONE));
+ serverState.incrementPayment(totalRefund, signature);
+
+ // We need to pay MIN_TX_FEE, but we only have MIN_NONDUST_OUTPUT
+ try {
+ serverState.close();
+ fail();
+ } catch (ValueOutOfRangeException e) {
+ assertTrue(e.getMessage().contains("unable to pay required fee"));
+ }
+
+ // Now give the server enough coins to pay the fee
+ StoredBlock block = new StoredBlock(makeSolvedTestBlock(blockStore, new ECKey().toAddress(params)), BigInteger.ONE, 1);
+ Transaction tx1 = createFakeTx(params, Utils.COIN, serverKey.toAddress(params));
+ serverWallet.receiveFromBlock(tx1, block, AbstractBlockChain.NewBlockType.BEST_CHAIN);
+
+ // The contract is still not worth redeeming - its worth less than we pay in fee
+ try {
+ serverState.close();
+ fail();
+ } catch (ValueOutOfRangeException e) {
+ assertTrue(e.getMessage().contains("more in fees than the channel was worth"));
+ }
+
+ signature = clientState.incrementPaymentBy(BigInteger.ONE.shiftLeft(1));
+ totalRefund = totalRefund.subtract(BigInteger.ONE.shiftLeft(1));
+ serverState.incrementPayment(totalRefund, signature);
+
+ // And close the channel.
+ serverState.close();
+ assertEquals(PaymentChannelServerState.State.CLOSING, serverState.getState());
+ Transaction closeTx = broadcastClose.getValue();
+ closeFuture.set(closeTx);
+ assertEquals(PaymentChannelServerState.State.CLOSED, serverState.getState());
+ control.verify();
+ }
+}
diff --git a/examples/src/main/java/com/google/bitcoin/examples/ExamplePaymentChannelClient.java b/examples/src/main/java/com/google/bitcoin/examples/ExamplePaymentChannelClient.java
new file mode 100644
index 00000000..940e4835
--- /dev/null
+++ b/examples/src/main/java/com/google/bitcoin/examples/ExamplePaymentChannelClient.java
@@ -0,0 +1,137 @@
+/*
+ * 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 com.google.bitcoin.examples;
+
+import java.io.File;
+import java.math.BigInteger;
+import java.net.InetSocketAddress;
+
+
+import com.google.bitcoin.core.*;
+import com.google.bitcoin.kits.WalletAppKit;
+import com.google.bitcoin.params.TestNet3Params;
+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.common.util.concurrent.*;
+import org.slf4j.LoggerFactory;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+/**
+ * Simple client that connects to the given host, opens a channel, and pays one cent.
+ */
+public class ExamplePaymentChannelClient {
+ private static final org.slf4j.Logger log = LoggerFactory.getLogger(ExamplePaymentChannelClient.class);
+ private WalletAppKit appKit;
+ private final BigInteger maxAcceptableRequestedAmount;
+ private final ECKey myKey;
+ private final NetworkParameters params;
+
+ public static void main(String[] args) throws Exception {
+ BriefLogFormatter.init();
+ System.out.println("USAGE: host");
+ new ExamplePaymentChannelClient().run(args[0]);
+ }
+
+ public ExamplePaymentChannelClient() {
+ maxAcceptableRequestedAmount = Utils.COIN;
+ myKey = new ECKey();
+ params = TestNet3Params.get();
+ }
+
+ public void run(final String host) throws Exception {
+ // 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
+ // the plugin that knows how to parse all the additional data is present during the load.
+ appKit = new WalletAppKit(params, new File("."), "payment_channel_example_client") {
+ @Override
+ protected void addWalletExtensions() {
+ // The StoredPaymentChannelClientStates object is responsible for, amongst other things, broadcasting
+ // the refund transaction if its lock time has expired. It also persists channels so we can resume them
+ // after a restart.
+ wallet().addExtension(new StoredPaymentChannelClientStates(peerGroup(), wallet()));
+ }
+ };
+ appKit.startAndWait();
+ // We now have active network connections and a fully synced wallet.
+ // Add a new key which will be used for the multisig contract.
+ appKit.wallet().addKey(myKey);
+
+ // Create the object which manages the payment channels protocol, client side. Tell it where the server to
+ // connect to is, along with some reasonable network timeouts, the wallet and our temporary key. We also have
+ // to pick an amount of value to lock up for the duration of the channel.
+ //
+ // Note that this may or may not actually construct a new channel. If an existing unclosed channel is found in
+ // the wallet, then it'll re-use that one instead.
+ final int timeoutSecs = 15;
+ final InetSocketAddress server = new InetSocketAddress(host, 4242);
+ PaymentChannelClientConnection client = null;
+
+ while (client == null) {
+ try {
+ final String channelID = host;
+ client = new PaymentChannelClientConnection(
+ server, timeoutSecs, appKit.wallet(), myKey, maxAcceptableRequestedAmount, channelID);
+ } catch (ValueOutOfRangeException e) {
+ // We don't have enough money in our wallet yet. Wait and try again.
+ waitForSufficientBalance(maxAcceptableRequestedAmount);
+ }
+ }
+
+ // Opening the channel requires talking to the server, so it's asynchronous.
+ 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 10 times.
+ for (int i = 0; i < 10; i++) {
+ try {
+ client.incrementPayment(Utils.CENT);
+ } catch (ValueOutOfRangeException e) {
+ log.error("Failed to increment payment by a CENT, remaining value is {}", client.state().getValueRefunded());
+ System.exit(-3);
+ }
+ log.info("Successfully sent payment of one CENT, total remaining on channel is now {}", client.state().getValueRefunded());
+ Uninterruptibles.sleepUninterruptibly(500, MILLISECONDS);
+ }
+ // 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
+ // by ourselves.
+ log.info("Closing channel!");
+ client.close();
+ }
+
+ @Override
+ public void onFailure(Throwable throwable) {
+ log.error("Failed to open connection", throwable);
+ }
+ });
+ }
+
+ private void waitForSufficientBalance(BigInteger amount) {
+ // Not enough money in the wallet.
+ BigInteger amountPlusFee = amount.add(Wallet.SendRequest.DEFAULT_FEE_PER_KB);
+ ListenableFuture balanceFuture = appKit.wallet().getBalanceFuture(amountPlusFee, Wallet.BalanceType.AVAILABLE);
+ if (!balanceFuture.isDone()) {
+ System.out.println("Please send " + Utils.bitcoinValueToFriendlyString(amountPlusFee) +
+ " BTC to " + myKey.toAddress(params));
+ Futures.getUnchecked(balanceFuture);
+ }
+ }
+}
diff --git a/examples/src/main/java/com/google/bitcoin/examples/ExamplePaymentChannelServer.java b/examples/src/main/java/com/google/bitcoin/examples/ExamplePaymentChannelServer.java
new file mode 100644
index 00000000..317ae86a
--- /dev/null
+++ b/examples/src/main/java/com/google/bitcoin/examples/ExamplePaymentChannelServer.java
@@ -0,0 +1,107 @@
+/*
+ * 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 com.google.bitcoin.examples;
+
+import java.io.File;
+import java.math.BigInteger;
+import java.net.SocketAddress;
+
+import com.google.bitcoin.core.NetworkParameters;
+import com.google.bitcoin.core.Sha256Hash;
+import com.google.bitcoin.core.Utils;
+import com.google.bitcoin.core.VerificationException;
+import com.google.bitcoin.kits.WalletAppKit;
+import com.google.bitcoin.params.TestNet3Params;
+import com.google.bitcoin.protocols.channels.*;
+import com.google.bitcoin.utils.BriefLogFormatter;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Simple server that listens on port 4242 for incoming payment channels.
+ */
+public class ExamplePaymentChannelServer implements PaymentChannelServerListener.HandlerFactory {
+ private static final org.slf4j.Logger log = LoggerFactory.getLogger(ExamplePaymentChannelServer.class);
+
+ private StoredPaymentChannelServerStates storedStates;
+ private WalletAppKit appKit;
+
+ public static void main(String[] args) throws Exception {
+ BriefLogFormatter.init();
+ new ExamplePaymentChannelServer().run();
+ }
+
+ public void run() throws Exception {
+ NetworkParameters params = TestNet3Params.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
+ // the plugin that knows how to parse all the additional data is present during the load.
+ appKit = new WalletAppKit(params, new File("."), "payment_channel_example_server") {
+ @Override
+ protected void addWalletExtensions() {
+ // The StoredPaymentChannelClientStates object is responsible for, amongst other things, broadcasting
+ // the refund transaction if its lock time has expired. It also persists channels so we can resume them
+ // after a restart.
+ storedStates = new StoredPaymentChannelServerStates(wallet(), peerGroup());
+ wallet().addExtension(storedStates);
+ }
+ };
+ appKit.startAndWait();
+
+ // We provide a peer group, a wallet, a timeout in seconds, the amount we require to start a channel and
+ // an implementation of HandlerFactory, which we just implement ourselves.
+ new PaymentChannelServerListener(appKit.peerGroup(), appKit.wallet(), 15, Utils.COIN, this).bindAndStart(4242);
+ }
+
+ @Override
+ public ServerConnectionEventHandler onNewConnection(final SocketAddress clientAddress) {
+ // Each connection needs a handler which is informed when that payment channel gets adjusted. Here we just log
+ // things. In a real app this object would be connected to some business logic.
+ return new ServerConnectionEventHandler() {
+ @Override
+ public void channelOpen(Sha256Hash channelId) {
+ log.info("Channel open for {}: {}.", clientAddress, channelId);
+
+ // Try to get the state object from the stored state set in our wallet
+ PaymentChannelServerState state = null;
+ try {
+ state = storedStates.getChannel(channelId).getState(appKit.wallet(), appKit.peerGroup());
+ } catch (VerificationException e) {
+ // This indicates corrupted data, and since the channel was just opened, cannot happen
+ throw new RuntimeException(e);
+ }
+ log.info(" with a maximum value of {}, expiring at UNIX timestamp {}.",
+ // The channel's maximum value is the value of the multisig contract which locks in some
+ // amount of money to the channel
+ state.getMultisigContract().getOutput(0).getValue(),
+ // The channel expires at some offset from when the client's refund transaction becomes
+ // spendable.
+ state.getRefundTransactionUnlockTime() + StoredPaymentChannelServerStates.CHANNEL_EXPIRE_OFFSET);
+ }
+
+ @Override
+ public void paymentIncrease(BigInteger by, BigInteger to) {
+ log.info("Client {} paid increased payment by {} for a total of " + to.toString(), clientAddress, by);
+ }
+
+ @Override
+ public void channelClosed(PaymentChannelCloseException.CloseReason reason) {
+ log.info("Client {} closed channel for reason {}", clientAddress, reason);
+ }
+ };
+ }
+}