diff --git a/core/src/main/java/org/bitcoinj/protocols/channels/IPaymentChannelClient.java b/core/src/main/java/org/bitcoinj/protocols/channels/IPaymentChannelClient.java
index 398cf2ce..7e788c11 100644
--- a/core/src/main/java/org/bitcoinj/protocols/channels/IPaymentChannelClient.java
+++ b/core/src/main/java/org/bitcoinj/protocols/channels/IPaymentChannelClient.java
@@ -23,6 +23,7 @@ import com.google.common.util.concurrent.ListenableFuture;
import com.google.protobuf.ByteString;
import org.bitcoin.paymentchannel.Protos;
+import org.bitcoinj.wallet.SendRequest;
import org.spongycastle.crypto.params.KeyParameter;
import javax.annotation.Nullable;
@@ -147,6 +148,41 @@ public interface IPaymentChannelClient {
void channelOpen(boolean wasInitiated);
}
+ /**
+ * Set Client payment channel properties.
+ */
+ interface ClientChannelProperties {
+ /**
+ * Modify the sendRequest used for the contract.
+ * @param sendRequest the current sendRequest.
+ * @return the modified sendRequest.
+ */
+ SendRequest modifyContractSendRequest(SendRequest sendRequest);
+
+ /**
+ * The maximum acceptable min payment. If the server suggests a higher amount
+ * the channel creation will be aborted.
+ */
+ Coin acceptableMinPayment();
+
+ /**
+ * The time in seconds, relative to now, on how long this channel should be kept open. Note that is is
+ * a proposal to the server. The server may in turn propose something different.
+ * See {@link org.bitcoinj.protocols.channels.IPaymentChannelClient.ClientConnection#acceptExpireTime(long)}
+ *
+ */
+ long timeWindow();
+
+ /**
+ * An enum indicating which versions to support:
+ * VERSION_1: use only version 1 of the protocol
+ * VERSION_2_ALLOW_1: suggest version 2 but allow downgrade to version 1
+ * VERSION_2: suggest version 2 and enforce use of version 2
+ *
+ */
+ PaymentChannelClient.VersionSelector versionSelector();
+ }
+
/**
* An implementor of this interface creates payment channel clients that "talk back" with the given connection.
* The client might be a PaymentChannelClient, or an RPC interface, or something else entirely.
diff --git a/core/src/main/java/org/bitcoinj/protocols/channels/PaymentChannelClient.java b/core/src/main/java/org/bitcoinj/protocols/channels/PaymentChannelClient.java
index 3bb0ed1b..cefd6d41 100644
--- a/core/src/main/java/org/bitcoinj/protocols/channels/PaymentChannelClient.java
+++ b/core/src/main/java/org/bitcoinj/protocols/channels/PaymentChannelClient.java
@@ -20,6 +20,7 @@ package org.bitcoinj.protocols.channels;
import org.bitcoinj.core.*;
import org.bitcoinj.protocols.channels.PaymentChannelCloseException.CloseReason;
import org.bitcoinj.utils.Threading;
+import org.bitcoinj.wallet.SendRequest;
import org.bitcoinj.wallet.Wallet;
import com.google.common.annotations.VisibleForTesting;
@@ -56,6 +57,7 @@ public class PaymentChannelClient implements IPaymentChannelClient {
private static final org.slf4j.Logger log = LoggerFactory.getLogger(PaymentChannelClient.class);
protected final ReentrantLock lock = Threading.lock("channelclient");
+ protected final ClientChannelProperties clientChannelProperties;
// Used to track the negotiated version number
@GuardedBy("lock") private int majorVersion;
@@ -167,36 +169,8 @@ public class PaymentChannelClient implements IPaymentChannelClient {
* @param conn A callback listener which represents the connection to the server (forwards messages we generate to
* the server)
*/
- public PaymentChannelClient(Wallet wallet, ECKey myKey, Coin maxValue, Sha256Hash serverId,
- ClientConnection conn) {
- this(wallet,myKey,maxValue,serverId, conn, VersionSelector.VERSION_2_ALLOW_1);
- }
-
- /**
- * Constructs a new channel manager which waits for {@link PaymentChannelClient#connectionOpen()} before acting.
- * A default time window of {@link #DEFAULT_TIME_WINDOW} will be used.
- *
- * @param wallet The wallet which will be paid from, and where completed transactions will be committed.
- * Must already have a {@link StoredPaymentChannelClientStates} object in its extensions set.
- * @param myKey A freshly generated keypair used for the multisig contract and refund output.
- * @param maxValue The maximum value the server is allowed to request that we lock into this channel until the
- * refund transaction unlocks. Note that if there is a previously open channel, the refund
- * transaction used in this channel may be larger than maxValue. Thus, maxValue is not a method for
- * limiting the amount payable through this channel.
- * @param serverId An arbitrary hash representing this channel. This must uniquely identify the server. If an
- * existing stored channel exists in the wallet's {@link StoredPaymentChannelClientStates}, then an
- * attempt will be made to resume that channel.
- * @param conn A callback listener which represents the connection to the server (forwards messages we generate to
- * the server)
- * @param versionSelector An enum indicating which versions to support:
- * VERSION_1: use only version 1 of the protocol
- * VERSION_2_ALLOW_1: suggest version 2 but allow downgrade to version 1
- * VERSION_2: suggest version 2 and enforce use of version 2
- *
- */
- public PaymentChannelClient(Wallet wallet, ECKey myKey, Coin maxValue, Sha256Hash serverId,
- ClientConnection conn, VersionSelector versionSelector) {
- this(wallet,myKey,maxValue,serverId, DEFAULT_TIME_WINDOW, null, conn, versionSelector);
+ public PaymentChannelClient(Wallet wallet, ECKey myKey, Coin maxValue, Sha256Hash serverId, ClientConnection conn) {
+ this(wallet,myKey,maxValue,serverId, null, conn);
}
/**
@@ -212,16 +186,13 @@ public class PaymentChannelClient implements IPaymentChannelClient {
* @param serverId An arbitrary hash representing this channel. This must uniquely identify the server. If an
* existing stored channel exists in the wallet's {@link StoredPaymentChannelClientStates}, then an
* attempt will be made to resume that channel.
- * @param timeWindow The time in seconds, relative to now, on how long this channel should be kept open. Note that is is
- * a proposal to the server. The server may in turn propose something different.
- * See {@link org.bitcoinj.protocols.channels.IPaymentChannelClient.ClientConnection#acceptExpireTime(long)}
* @param userKeySetup Key derived from a user password, used to decrypt myKey, if it is encrypted, during setup.
* @param conn A callback listener which represents the connection to the server (forwards messages we generate to
* the server)
*/
- public PaymentChannelClient(Wallet wallet, ECKey myKey, Coin maxValue, Sha256Hash serverId, long timeWindow,
+ public PaymentChannelClient(Wallet wallet, ECKey myKey, Coin maxValue, Sha256Hash serverId,
@Nullable KeyParameter userKeySetup, ClientConnection conn) {
- this(wallet, myKey, maxValue, serverId, timeWindow, userKeySetup, conn, VersionSelector.VERSION_2_ALLOW_1);
+ this(wallet, myKey, maxValue, serverId, userKeySetup, defaultChannelProperties, conn);
}
/**
@@ -237,28 +208,28 @@ public class PaymentChannelClient implements IPaymentChannelClient {
* @param serverId An arbitrary hash representing this channel. This must uniquely identify the server. If an
* existing stored channel exists in the wallet's {@link StoredPaymentChannelClientStates}, then an
* attempt will be made to resume that channel.
- * @param timeWindow The time in seconds, relative to now, on how long this channel should be kept open. Note that is is
- * a proposal to the server. The server may in turn propose something different.
- * See {@link org.bitcoinj.protocols.channels.IPaymentChannelClient.ClientConnection#acceptExpireTime(long)}
* @param userKeySetup Key derived from a user password, used to decrypt myKey, if it is encrypted, during setup.
+ * @param clientChannelProperties Modify the channel's properties. You may extend {@link DefaultClientChannelProperties}
* @param conn A callback listener which represents the connection to the server (forwards messages we generate to
* the server)
- * @param versionSelector An enum indicating which versions to support:
- * VERSION_1: use only version 1 of the protocol
- * VERSION_2_ALLOW_1: suggest version 2 but allow downgrade to version 1
- * VERSION_2: suggest version 2 and enforce use of version 2
*/
- public PaymentChannelClient(Wallet wallet, ECKey myKey, Coin maxValue, Sha256Hash serverId, long timeWindow,
- @Nullable KeyParameter userKeySetup, ClientConnection conn, VersionSelector versionSelector) {
+ public PaymentChannelClient(Wallet wallet, ECKey myKey, Coin maxValue, Sha256Hash serverId,
+ @Nullable KeyParameter userKeySetup, @Nullable ClientChannelProperties clientChannelProperties,
+ ClientConnection conn) {
this.wallet = checkNotNull(wallet);
this.myKey = checkNotNull(myKey);
this.maxValue = checkNotNull(maxValue);
this.serverId = checkNotNull(serverId);
- checkState(timeWindow >= 0);
- this.timeWindow = timeWindow;
this.conn = checkNotNull(conn);
this.userKeySetup = userKeySetup;
- this.versionSelector = versionSelector;
+ if (clientChannelProperties == null) {
+ this.clientChannelProperties = defaultChannelProperties;
+ } else {
+ this.clientChannelProperties = clientChannelProperties;
+ }
+ this.timeWindow = clientChannelProperties.timeWindow();
+ checkState(timeWindow >= 0);
+ this.versionSelector = clientChannelProperties.versionSelector();
}
/**
@@ -299,12 +270,12 @@ public class PaymentChannelClient implements IPaymentChannelClient {
// For now we require a hard-coded value. In future this will have to get more complex and dynamic as the fees
// start to float.
- final long MIN_PAYMENT = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.value;
- if (initiate.getMinPayment() != MIN_PAYMENT) {
- log.error("Server requested a min payment of {} but we expected {}", initiate.getMinPayment(), MIN_PAYMENT);
+ final long maxMin = clientChannelProperties.acceptableMinPayment().value;
+ if (initiate.getMinPayment() > maxMin) {
+ log.error("Server requested a min payment of {} but we only accept up to {}", initiate.getMinPayment(), maxMin);
errorBuilder.setCode(Protos.Error.ErrorCode.MIN_PAYMENT_TOO_LARGE);
- errorBuilder.setExpectedValue(MIN_PAYMENT);
- missing = Coin.valueOf(initiate.getMinPayment() - MIN_PAYMENT);
+ errorBuilder.setExpectedValue(maxMin);
+ missing = Coin.valueOf(initiate.getMinPayment() - maxMin);
return CloseReason.SERVER_REQUESTED_TOO_MUCH_VALUE;
}
@@ -322,7 +293,7 @@ public class PaymentChannelClient implements IPaymentChannelClient {
return CloseReason.NO_ACCEPTABLE_VERSION;
}
try {
- state.initiate(userKeySetup);
+ state.initiate(userKeySetup, clientChannelProperties);
} catch (ValueOutOfRangeException e) {
log.error("Value out of range when trying to initiate", e);
errorBuilder.setCode(Protos.Error.ErrorCode.CHANNEL_VALUE_TOO_LARGE);
@@ -754,4 +725,28 @@ public class PaymentChannelClient implements IPaymentChannelClient {
// Ensure the future runs without the client lock held.
future.set(new PaymentIncrementAck(value, paymentAck.getInfo()));
}
+
+ public static class DefaultClientChannelProperties implements ClientChannelProperties {
+
+ @Override
+ public SendRequest modifyContractSendRequest(SendRequest sendRequest) {
+ return sendRequest;
+ }
+
+ @Override
+ public Coin acceptableMinPayment() { return Transaction.REFERENCE_DEFAULT_MIN_TX_FEE; }
+
+ @Override
+ public long timeWindow() {
+ return DEFAULT_TIME_WINDOW;
+ }
+
+ @Override
+ public VersionSelector versionSelector() {
+ return VersionSelector.VERSION_2_ALLOW_1;
+ }
+
+ }
+
+ public static DefaultClientChannelProperties defaultChannelProperties = new DefaultClientChannelProperties();
}
diff --git a/core/src/main/java/org/bitcoinj/protocols/channels/PaymentChannelClientConnection.java b/core/src/main/java/org/bitcoinj/protocols/channels/PaymentChannelClientConnection.java
index 43491ed8..25e92a34 100644
--- a/core/src/main/java/org/bitcoinj/protocols/channels/PaymentChannelClientConnection.java
+++ b/core/src/main/java/org/bitcoinj/protocols/channels/PaymentChannelClientConnection.java
@@ -23,6 +23,7 @@ import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Utils;
import org.bitcoinj.net.NioClient;
import org.bitcoinj.net.ProtobufConnection;
+import org.bitcoinj.protocols.channels.IPaymentChannelClient.ClientChannelProperties;
import org.bitcoinj.wallet.Wallet;
import com.google.common.util.concurrent.ListenableFuture;
@@ -68,44 +69,14 @@ public class PaymentChannelClientConnection {
*/
public PaymentChannelClientConnection(InetSocketAddress server, int timeoutSeconds, Wallet wallet, ECKey myKey,
Coin maxValue, String serverId) throws IOException, ValueOutOfRangeException {
- this(server, timeoutSeconds, wallet, myKey, maxValue, serverId,
- PaymentChannelClient.VersionSelector.VERSION_2_ALLOW_1);
+ this(server, timeoutSeconds, wallet, myKey, maxValue, serverId, null);
}
/**
* Attempts to open a new connection to and open a payment channel with the given host and port, blocking until the
- * connection is open. The server is requested to keep the channel open for
- * {@link org.bitcoinj.protocols.channels.PaymentChannelClient#DEFAULT_TIME_WINDOW}
- * seconds. If the server proposes a longer time the channel will be closed.
- *
- * @param server The host/port pair where the server is listening.
- * @param timeoutSeconds The connection timeout and read timeout during initialization. This should be large enough
- * to accommodate ECDSA signature operations and network latency.
- * @param wallet The wallet which will be paid from, and where completed transactions will be committed.
- * Must be unencrypted. Must already have a {@link StoredPaymentChannelClientStates} object in its extensions set.
- * @param myKey A freshly generated keypair used for the multisig contract and refund output.
- * @param maxValue The maximum value this channel is allowed to request
- * @param serverId A unique ID which is used to attempt reopening of an existing channel.
- * This must be unique to the server, and, if your application is exposing payment channels to some
- * API, this should also probably encompass some caller UID to avoid applications opening channels
- * which were created by others.
- * @param versionSelector An enum indicating which versions to support:
- * VERSION_1: use only version 1 of the protocol
- * VERSION_2_ALLOW_1: suggest version 2 but allow downgrade to version 1
- * VERSION_2: suggest version 2 and enforce use of version 2
- * @throws IOException if there's an issue using the network.
- * @throws ValueOutOfRangeException if the balance of wallet is lower than maxValue.
- */
- public PaymentChannelClientConnection(InetSocketAddress server, int timeoutSeconds, Wallet wallet, ECKey myKey,
- Coin maxValue, String serverId, PaymentChannelClient.VersionSelector versionSelector) throws IOException, ValueOutOfRangeException {
- this(server, timeoutSeconds, wallet, myKey, maxValue, serverId,
- PaymentChannelClient.DEFAULT_TIME_WINDOW, null, versionSelector);
- }
-
- /**
- * Attempts to open a new connection to and open a payment channel with the given host and port, blocking until the
- * connection is open. The server is requested to keep the channel open for {@param timeWindow}
- * seconds. If the server proposes a longer time the channel will be closed.
+ * connection is open. The server is requested to keep the channel open for
+ * {@link org.bitcoinj.protocols.channels.PaymentChannelClient#DEFAULT_TIME_WINDOW} seconds.
+ * If the server proposes a longer time the channel will be closed.
*
* @param server The host/port pair where the server is listening.
* @param timeoutSeconds The connection timeout and read timeout during initialization. This should be large enough
@@ -119,16 +90,13 @@ public class PaymentChannelClientConnection {
* This must be unique to the server, and, if your application is exposing payment channels to some
* API, this should also probably encompass some caller UID to avoid applications opening channels
* which were created by others.
- * @param timeWindow The time in seconds, relative to now, on how long this channel should be kept open.
* @param userKeySetup Key derived from a user password, used to decrypt myKey, if it is encrypted, during setup.
* @throws IOException if there's an issue using the network.
* @throws ValueOutOfRangeException if the balance of wallet is lower than maxValue.
*/
public PaymentChannelClientConnection(InetSocketAddress server, int timeoutSeconds, Wallet wallet, ECKey myKey,
- Coin maxValue, String serverId, final long timeWindow,
- @Nullable KeyParameter userKeySetup) throws IOException, ValueOutOfRangeException {
- this(server, timeoutSeconds, wallet, myKey, maxValue, serverId,
- timeWindow, userKeySetup, PaymentChannelClient.VersionSelector.VERSION_2_ALLOW_1);
+ Coin maxValue, String serverId, @Nullable KeyParameter userKeySetup) throws IOException, ValueOutOfRangeException {
+ this(server, timeoutSeconds, wallet, myKey, maxValue, serverId, userKeySetup, PaymentChannelClient.defaultChannelProperties);
}
@@ -149,24 +117,20 @@ public class PaymentChannelClientConnection {
* This must be unique to the server, and, if your application is exposing payment channels to some
* API, this should also probably encompass some caller UID to avoid applications opening channels
* which were created by others.
- * @param timeWindow The time in seconds, relative to now, on how long this channel should be kept open.
* @param userKeySetup Key derived from a user password, used to decrypt myKey, if it is encrypted, during setup.
- * @param versionSelector An enum indicating which versions to support:
- * VERSION_1: use only version 1 of the protocol
- * VERSION_2_ALLOW_1: suggest version 2 but allow downgrade to version 1
- * VERSION_2: suggest version 2 and enforce use of version 2
+ * @param clientChannelProperties Modifier to change the channel's configuration.
*
* @throws IOException if there's an issue using the network.
* @throws ValueOutOfRangeException if the balance of wallet is lower than maxValue.
*/
public PaymentChannelClientConnection(InetSocketAddress server, int timeoutSeconds, Wallet wallet, ECKey myKey,
- Coin maxValue, String serverId, final long timeWindow,
- @Nullable KeyParameter userKeySetup, PaymentChannelClient.VersionSelector versionSelector)
+ Coin maxValue, String serverId,
+ @Nullable KeyParameter userKeySetup, final ClientChannelProperties clientChannelProperties)
throws IOException, ValueOutOfRangeException {
// Glue the object which vends/ingests protobuf messages in order to manage state to the network object which
// reads/writes them to the wire in length prefixed form.
- channelClient = new PaymentChannelClient(wallet, myKey, maxValue, Sha256Hash.of(serverId.getBytes()), timeWindow,
- userKeySetup, new PaymentChannelClient.ClientConnection() {
+ channelClient = new PaymentChannelClient(wallet, myKey, maxValue, Sha256Hash.of(serverId.getBytes()),
+ userKeySetup, clientChannelProperties, new PaymentChannelClient.ClientConnection() {
@Override
public void sendToServer(Protos.TwoWayChannelMessage msg) {
wireParser.write(msg);
@@ -180,7 +144,7 @@ public class PaymentChannelClientConnection {
@Override
public boolean acceptExpireTime(long expireTime) {
- return expireTime <= (timeWindow + Utils.currentTimeSeconds() + 60); // One extra minute to compensate for time skew and latency
+ return expireTime <= (clientChannelProperties.timeWindow() + Utils.currentTimeSeconds() + 60); // One extra minute to compensate for time skew and latency
}
@Override
@@ -189,7 +153,7 @@ public class PaymentChannelClientConnection {
// Inform the API user that we're done and ready to roll.
channelOpenFuture.set(PaymentChannelClientConnection.this);
}
- }, versionSelector);
+ });
// And glue back in the opposite direction - network to the channelClient.
wireParser = new ProtobufConnection
To begin, the client calls {@link PaymentChannelV1ClientState#initiate()}, which moves the channel into state + *
To begin, the client calls {@link PaymentChannelClientState#initiate(KeyParameter, ClientChannelProperties)}, 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 PaymentChannelV1ClientState#getIncompleteRefundTransaction()} and pass the resultant transaction through to the * server. Once you have retrieved the signature, use {@link PaymentChannelV1ClientState#provideRefundSignature(byte[], KeyParameter)}. - * You must then call {@link PaymentChannelV1ClientState#storeChannelInWallet(Sha256Hash)} to store the refund transaction + * You must then call {@link PaymentChannelClientState#storeChannelInWallet(Sha256Hash)} to store the refund transaction * in the wallet, protecting you against a malicious server attempting to destroy all your coins. At this point, you can - * provide the server with the multi-sig contract (via {@link PaymentChannelV1ClientState#getContract()}) safely. + * provide the server with the multi-sig contract (via {@link PaymentChannelClientState#getContract()}) safely. *
*/ public abstract class PaymentChannelClientState { @@ -126,9 +126,9 @@ public abstract class PaymentChannelClientState { /** * Creates a state object for a payment channel client. It is expected that you be ready to - * {@link PaymentChannelV1ClientState#initiate()} after construction (to avoid creating objects for channels which are + * {@link PaymentChannelClientState#initiate(KeyParameter, ClientChannelProperties)} 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 PaymentChannelV1ClientState#initiate()} to create the Multisig contract and refund transaction. + * {@link PaymentChannelClientState#initiate(KeyParameter, ClientChannelProperties)} 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. @@ -211,37 +211,28 @@ public abstract class PaymentChannelClientState { * Creates the initial multisig contract and incomplete refund transaction which can be requested at the appropriate * time using {@link PaymentChannelV1ClientState#getIncompleteRefundTransaction} and * {@link PaymentChannelV1ClientState#getContract()}. The way the contract is crafted can be adjusted by - * overriding {@link PaymentChannelV1ClientState#editContractSendRequest(org.bitcoinj.wallet.Wallet.SendRequest)}. * By default unconfirmed coins are allowed to be used, as for micropayments the risk should be relatively low. * * @throws ValueOutOfRangeException if the value being used is too small to be accepted by the network * @throws InsufficientMoneyException if the wallet doesn't contain enough balance to initiate */ public void initiate() throws ValueOutOfRangeException, InsufficientMoneyException { - initiate(null); + initiate(null, PaymentChannelClient.defaultChannelProperties); } /** * Creates the initial multisig contract and incomplete refund transaction which can be requested at the appropriate * time using {@link PaymentChannelV1ClientState#getIncompleteRefundTransaction} and - * {@link PaymentChannelV1ClientState#getContract()}. The way the contract is crafted can be adjusted by - * overriding {@link PaymentChannelV1ClientState#editContractSendRequest(org.bitcoinj.wallet.Wallet.SendRequest)}. + * {@link PaymentChannelClientState#getContract()}. * By default unconfirmed coins are allowed to be used, as for micropayments the risk should be relatively low. * @param userKey Key derived from a user password, needed for any signing when the wallet is encrypted. - * The wallet KeyCrypter is assumed. + * The wallet KeyCrypter is assumed. + * @param clientChannelProperties Modify the channel's configuration. * * @throws ValueOutOfRangeException if the value being used is too small to be accepted by the network * @throws InsufficientMoneyException if the wallet doesn't contain enough balance to initiate */ - public abstract void initiate(@Nullable KeyParameter userKey) throws ValueOutOfRangeException, InsufficientMoneyException; - - /** - * You can override this method in order to control the construction of the initial contract that creates the - * channel. For example if you want it to only use specific coins, you can adjust the coin selector here. - * The default implementation does nothing. - */ - protected void editContractSendRequest(SendRequest req) { - } + public abstract void initiate(@Nullable KeyParameter userKey, ClientChannelProperties clientChannelProperties) throws ValueOutOfRangeException, InsufficientMoneyException; /** * Gets the contract which was used to initialize this channel @@ -392,7 +383,7 @@ public abstract class PaymentChannelClientState { /** * Returns the fees that will be paid if the refund transaction has to be claimed because the server failed to settle - * the channel properly. May only be called after {@link PaymentChannelV1ClientState#initiate()} + * the channel properly. May only be called after {@link PaymentChannelClientState#initiate(KeyParameter, ClientChannelProperties)} */ public abstract Coin getRefundTxFees(); diff --git a/core/src/main/java/org/bitcoinj/protocols/channels/PaymentChannelServer.java b/core/src/main/java/org/bitcoinj/protocols/channels/PaymentChannelServer.java index 8a4c6e22..a733f4c0 100644 --- a/core/src/main/java/org/bitcoinj/protocols/channels/PaymentChannelServer.java +++ b/core/src/main/java/org/bitcoinj/protocols/channels/PaymentChannelServer.java @@ -129,6 +129,25 @@ public class PaymentChannelServer { } private final ServerConnection conn; + public interface ServerChannelProperties { + /** + * The size of the payment that the client is requested to pay in the initiate phase. + */ + Coin getMinPayment(); + + /** + * The maximum allowed channel time window in seconds. + * Note that the server need to be online for the whole time the channel is open. + * Failure to do this could cause loss of all payments received on the channel. + */ + long getMaxTimeWindow(); + + /** + * The minimum allowed channel time window in seconds, must be larger than 7200. + */ + long getMinTimeWindow(); + } + // Used to track the negotiated version number @GuardedBy("lock") private int majorVersion; @@ -144,6 +163,10 @@ public class PaymentChannelServer { // The key used for multisig in this channel @GuardedBy("lock") private ECKey myKey; + // The fee server charges for managing (and settling the channel). + // This is will be requested in the setup via the min_payment field in the initiate message. + private final Coin minPayment; + // The minimum accepted channel value private final Coin minAcceptedChannelSize; @@ -187,7 +210,7 @@ public class PaymentChannelServer { */ public PaymentChannelServer(TransactionBroadcaster broadcaster, Wallet wallet, Coin minAcceptedChannelSize, ServerConnection conn) { - this(broadcaster, wallet, minAcceptedChannelSize, DEFAULT_MIN_TIME_WINDOW, DEFAULT_MAX_TIME_WINDOW, conn); + this(broadcaster, wallet, minAcceptedChannelSize, new DefaultServerChannelProperties(), conn); } /** @@ -201,22 +224,21 @@ public class PaymentChannelServer { * and may cause fees to be require to settle 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 minTimeWindow The minimum allowed channel time window in seconds, must be larger than 7200. - * @param maxTimeWindow The maximum allowed channel time window in seconds. Note that the server need to be online for the whole time the channel is open. - * Failure to do this could cause loss of all payments received on the channel. + * @param serverChannelProperties Modify the channel's properties. You may extend {@link DefaultServerChannelProperties} * @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(TransactionBroadcaster broadcaster, Wallet wallet, - Coin minAcceptedChannelSize, long minTimeWindow, long maxTimeWindow, ServerConnection conn) { + Coin minAcceptedChannelSize, ServerChannelProperties serverChannelProperties, ServerConnection conn) { + minTimeWindow = serverChannelProperties.getMinTimeWindow(); + maxTimeWindow = serverChannelProperties.getMaxTimeWindow(); if (minTimeWindow > maxTimeWindow) throw new IllegalArgumentException("minTimeWindow must be less or equal to maxTimeWindow"); if (minTimeWindow < HARD_MIN_TIME_WINDOW) throw new IllegalArgumentException("minTimeWindow must be larger than" + HARD_MIN_TIME_WINDOW + " seconds"); this.broadcaster = checkNotNull(broadcaster); this.wallet = checkNotNull(wallet); + this.minPayment = checkNotNull(serverChannelProperties.getMinPayment()); this.minAcceptedChannelSize = checkNotNull(minAcceptedChannelSize); this.conn = checkNotNull(conn); - this.minTimeWindow = minTimeWindow; - this.maxTimeWindow = maxTimeWindow; } /** @@ -299,7 +321,7 @@ public class PaymentChannelServer { .setMultisigKey(ByteString.copyFrom(myKey.getPubKey())) .setExpireTimeSecs(expireTime) .setMinAcceptedChannelSize(minAcceptedChannelSize.value) - .setMinPayment(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.value); + .setMinPayment(minPayment.value); conn.sendToClient(Protos.TwoWayChannelMessage.newBuilder() .setInitiate(initiateBuilder) @@ -634,4 +656,27 @@ public class PaymentChannelServer { lock.unlock(); } } + + /** + * Extend this class and override the values you want to change. + */ + public static class DefaultServerChannelProperties implements ServerChannelProperties { + + @Override + public Coin getMinPayment() { + return Transaction.REFERENCE_DEFAULT_MIN_TX_FEE; + } + + @Override + public long getMaxTimeWindow() { + return DEFAULT_MAX_TIME_WINDOW; + } + + @Override + public long getMinTimeWindow() { + return DEFAULT_MIN_TIME_WINDOW; + } + + } + } diff --git a/core/src/main/java/org/bitcoinj/protocols/channels/PaymentChannelV1ClientState.java b/core/src/main/java/org/bitcoinj/protocols/channels/PaymentChannelV1ClientState.java index 57d89887..f1d741a4 100644 --- a/core/src/main/java/org/bitcoinj/protocols/channels/PaymentChannelV1ClientState.java +++ b/core/src/main/java/org/bitcoinj/protocols/channels/PaymentChannelV1ClientState.java @@ -20,6 +20,7 @@ import com.google.common.collect.Multimap; import com.google.common.collect.MultimapBuilder; import org.bitcoinj.core.*; import org.bitcoinj.crypto.TransactionSignature; +import org.bitcoinj.protocols.channels.IPaymentChannelClient.ClientChannelProperties; import org.bitcoinj.script.Script; import org.bitcoinj.script.ScriptBuilder; import org.bitcoinj.wallet.AllowUnconfirmedCoinSelector; @@ -71,9 +72,9 @@ public class PaymentChannelV1ClientState extends PaymentChannelClientState { /** * Creates a state object for a payment channel client. It is expected that you be ready to - * {@link PaymentChannelV1ClientState#initiate()} after construction (to avoid creating objects for channels which are + * {@link PaymentChannelClientState#initiate(KeyParameter, ClientChannelProperties)} 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 PaymentChannelV1ClientState#initiate()} to create the Multisig contract and refund transaction. + * {@link PaymentChannelClientState#initiate(KeyParameter, ClientChannelProperties)} 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. @@ -115,17 +116,17 @@ public class PaymentChannelV1ClientState extends PaymentChannelClientState { /** * Creates the initial multisig contract and incomplete refund transaction which can be requested at the appropriate * time using {@link PaymentChannelV1ClientState#getIncompleteRefundTransaction} and - * {@link PaymentChannelV1ClientState#getContract()}. The way the contract is crafted can be adjusted by - * overriding {@link PaymentChannelV1ClientState#editContractSendRequest(org.bitcoinj.core.Wallet.SendRequest)}. + * {@link PaymentChannelV1ClientState#getContract()}. * By default unconfirmed coins are allowed to be used, as for micropayments the risk should be relatively low. * @param userKey Key derived from a user password, needed for any signing when the wallet is encrypted. - * The wallet KeyCrypter is assumed. + * The wallet KeyCrypter is assumed. + * @param clientChannelProperties Modify the channel's configuration. * * @throws ValueOutOfRangeException if the value being used is too small to be accepted by the network * @throws InsufficientMoneyException if the wallet doesn't contain enough balance to initiate */ @Override - public synchronized void initiate(@Nullable KeyParameter userKey) throws ValueOutOfRangeException, InsufficientMoneyException { + public synchronized void initiate(@Nullable KeyParameter userKey, ClientChannelProperties clientChannelProperties) throws ValueOutOfRangeException, InsufficientMoneyException { 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 @@ -139,9 +140,9 @@ public class PaymentChannelV1ClientState extends PaymentChannelClientState { throw new ValueOutOfRangeException("totalValue too small to use"); SendRequest req = SendRequest.forTx(template); req.coinSelector = AllowUnconfirmedCoinSelector.get(); - editContractSendRequest(req); req.shuffleOutputs = false; // TODO: Fix things so shuffling is usable. - req.aesKey = userKey; + req = clientChannelProperties.modifyContractSendRequest(req); + if (userKey != null) req.aesKey = userKey; wallet.completeTx(req); Coin multisigFee = req.tx.getFee(); multisigContract = req.tx; diff --git a/core/src/main/java/org/bitcoinj/protocols/channels/PaymentChannelV2ClientState.java b/core/src/main/java/org/bitcoinj/protocols/channels/PaymentChannelV2ClientState.java index c35470dd..193f19be 100644 --- a/core/src/main/java/org/bitcoinj/protocols/channels/PaymentChannelV2ClientState.java +++ b/core/src/main/java/org/bitcoinj/protocols/channels/PaymentChannelV2ClientState.java @@ -21,6 +21,7 @@ import com.google.common.collect.Multimap; import com.google.common.collect.MultimapBuilder; import org.bitcoinj.core.*; import org.bitcoinj.crypto.TransactionSignature; +import org.bitcoinj.protocols.channels.IPaymentChannelClient.ClientChannelProperties; import org.bitcoinj.script.Script; import org.bitcoinj.script.ScriptBuilder; import org.bitcoinj.wallet.AllowUnconfirmedCoinSelector; @@ -99,7 +100,7 @@ public class PaymentChannelV2ClientState extends PaymentChannelClientState { } @Override - public synchronized void initiate(@Nullable KeyParameter userKey) throws ValueOutOfRangeException, InsufficientMoneyException { + public synchronized void initiate(@Nullable KeyParameter userKey, ClientChannelProperties clientChannelProperties) throws ValueOutOfRangeException, InsufficientMoneyException { final NetworkParameters params = wallet.getParams(); Transaction template = new Transaction(params); // There is also probably a change output, but we don't bother shuffling them as it's obvious from the @@ -113,9 +114,9 @@ public class PaymentChannelV2ClientState extends PaymentChannelClientState { throw new ValueOutOfRangeException("totalValue too small to use"); SendRequest req = SendRequest.forTx(template); req.coinSelector = AllowUnconfirmedCoinSelector.get(); - editContractSendRequest(req); req.shuffleOutputs = false; // TODO: Fix things so shuffling is usable. - req.aesKey = userKey; + req = clientChannelProperties.modifyContractSendRequest(req); + if (userKey != null) req.aesKey = userKey; wallet.completeTx(req); Coin multisigFee = req.tx.getFee(); contract = req.tx; diff --git a/core/src/test/java/org/bitcoinj/protocols/channels/ChannelConnectionTest.java b/core/src/test/java/org/bitcoinj/protocols/channels/ChannelConnectionTest.java index ad909503..eb2d7f25 100644 --- a/core/src/test/java/org/bitcoinj/protocols/channels/ChannelConnectionTest.java +++ b/core/src/test/java/org/bitcoinj/protocols/channels/ChannelConnectionTest.java @@ -17,6 +17,7 @@ package org.bitcoinj.protocols.channels; import org.bitcoinj.core.*; +import org.bitcoinj.protocols.channels.PaymentChannelClient.VersionSelector; import org.bitcoinj.testing.TestWithWallet; import org.bitcoinj.utils.Threading; import org.bitcoinj.wallet.Wallet; @@ -48,6 +49,7 @@ import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import static org.bitcoinj.core.Coin.*; +import static org.bitcoinj.protocols.channels.PaymentChannelClient.VersionSelector.*; import static org.bitcoinj.protocols.channels.PaymentChannelCloseException.CloseReason; import static org.bitcoinj.testing.FakeTxBuilder.createFakeBlock; import static org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage.MessageType; @@ -75,20 +77,26 @@ public class ChannelConnectionTest extends TestWithWallet { * version of the channel. */ @Parameterized.Parameters(name = "{index}: ChannelConnectionTest({0})") - public static Collectiontrue
if we are using a protocol version that requires the exchange of refunds.
*/
private boolean useRefunds() {
- return versionSelector == PaymentChannelClient.VersionSelector.VERSION_1;
+ return clientChannelProperties.versionSelector() == VERSION_1;
}
/**
@@ -96,7 +104,7 @@ public class ChannelConnectionTest extends TestWithWallet {
* @return
*/
private boolean isMultiSigContract() {
- return versionSelector == PaymentChannelClient.VersionSelector.VERSION_1;
+ return clientChannelProperties.versionSelector() == VERSION_1;
}
@Override
@@ -197,7 +205,7 @@ public class ChannelConnectionTest extends TestWithWallet {
server.bindAndStart(4243);
PaymentChannelClientConnection client = new PaymentChannelClientConnection(
- new InetSocketAddress("localhost", 4243), 30, wallet, myKey, COIN, "", PaymentChannelClient.DEFAULT_TIME_WINDOW, userKeySetup, versionSelector);
+ new InetSocketAddress("localhost", 4243), 30, wallet, myKey, COIN, "", userKeySetup, clientChannelProperties);
// Wait for the multi-sig tx to be transmitted.
broadcastTxPause.release();
@@ -280,7 +288,7 @@ public class ChannelConnectionTest extends TestWithWallet {
}
// Gives the server crap and checks proper error responses are sent.
ChannelTestUtils.RecordingPair pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
- PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, Sha256Hash.ZERO_HASH, pair.clientRecorder, versionSelector);
+ PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, Sha256Hash.ZERO_HASH, null, clientChannelProperties, pair.clientRecorder);
PaymentChannelServer server = pair.server;
server.connectionOpen();
client.connectionOpen();
@@ -305,7 +313,7 @@ public class ChannelConnectionTest extends TestWithWallet {
public void testServerErrorHandling_killSocketOnClose() throws Exception {
// Make sure the server closes the socket on CLOSE
ChannelTestUtils.RecordingPair pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
- PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, Sha256Hash.ZERO_HASH, pair.clientRecorder, versionSelector);
+ PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, Sha256Hash.ZERO_HASH, null, clientChannelProperties, pair.clientRecorder);
PaymentChannelServer server = pair.server;
server.connectionOpen();
client.connectionOpen();
@@ -323,7 +331,7 @@ public class ChannelConnectionTest extends TestWithWallet {
public void testServerErrorHandling_killSocketOnError() throws Exception {
// Make sure the server closes the socket on ERROR
ChannelTestUtils.RecordingPair pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
- PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, Sha256Hash.ZERO_HASH, pair.clientRecorder, versionSelector);
+ PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, Sha256Hash.ZERO_HASH, null, clientChannelProperties, pair.clientRecorder);
PaymentChannelServer server = pair.server;
server.connectionOpen();
client.connectionOpen();
@@ -347,7 +355,7 @@ public class ChannelConnectionTest extends TestWithWallet {
// Open up a normal channel.
ChannelTestUtils.RecordingPair pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
pair.server.connectionOpen();
- PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, someServerId, pair.clientRecorder, versionSelector);
+ PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, someServerId, null, clientChannelProperties, pair.clientRecorder);
PaymentChannelServer server = pair.server;
client.connectionOpen();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION));
@@ -402,7 +410,7 @@ public class ChannelConnectionTest extends TestWithWallet {
// Open up a normal channel.
ChannelTestUtils.RecordingPair pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
pair.server.connectionOpen();
- PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, someServerId, pair.clientRecorder, versionSelector);
+ PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, someServerId, null, clientChannelProperties, pair.clientRecorder);
PaymentChannelServer server = pair.server;
client.connectionOpen();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION));
@@ -462,7 +470,7 @@ public class ChannelConnectionTest extends TestWithWallet {
(StoredPaymentChannelClientStates) wallet.getExtensions().get(StoredPaymentChannelClientStates.EXTENSION_ID);
pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
- client = new PaymentChannelClient(wallet, myKey, COIN, someServerId, pair.clientRecorder, versionSelector);
+ client = new PaymentChannelClient(wallet, myKey, COIN, someServerId, null, clientChannelProperties, pair.clientRecorder);
server = pair.server;
client.connectionOpen();
server.connectionOpen();
@@ -489,7 +497,7 @@ public class ChannelConnectionTest extends TestWithWallet {
// Now open up a new client with the same id and make sure the server disconnects the previous client.
pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
- client = new PaymentChannelClient(wallet, myKey, COIN, someServerId, pair.clientRecorder, versionSelector);
+ client = new PaymentChannelClient(wallet, myKey, COIN, someServerId, null, clientChannelProperties, pair.clientRecorder);
server = pair.server;
client.connectionOpen();
server.connectionOpen();
@@ -501,7 +509,7 @@ public class ChannelConnectionTest extends TestWithWallet {
}
// Make sure the server allows two simultaneous opens. It will close the first and allow resumption of the second.
pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
- client = new PaymentChannelClient(wallet, myKey, COIN, someServerId, pair.clientRecorder, versionSelector);
+ client = new PaymentChannelClient(wallet, myKey, COIN, someServerId, null, clientChannelProperties, pair.clientRecorder);
server = pair.server;
client.connectionOpen();
server.connectionOpen();
@@ -585,7 +593,7 @@ public class ChannelConnectionTest extends TestWithWallet {
public void testClientUnknownVersion() throws Exception {
// Tests client rejects unknown version
ChannelTestUtils.RecordingPair pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
- PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, Sha256Hash.ZERO_HASH, pair.clientRecorder, versionSelector);
+ PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, Sha256Hash.ZERO_HASH, null, clientChannelProperties, pair.clientRecorder);
client.connectionOpen();
pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION);
client.receiveMessage(Protos.TwoWayChannelMessage.newBuilder()
@@ -605,7 +613,7 @@ public class ChannelConnectionTest extends TestWithWallet {
// Tests that clients reject too large time windows
ChannelTestUtils.RecordingPair pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster, 100);
PaymentChannelServer server = pair.server;
- PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, Sha256Hash.ZERO_HASH, pair.clientRecorder, versionSelector);
+ PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, Sha256Hash.ZERO_HASH, null, clientChannelProperties, pair.clientRecorder);
client.connectionOpen();
server.connectionOpen();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION));
@@ -630,7 +638,7 @@ public class ChannelConnectionTest extends TestWithWallet {
public void testValuesAreRespected() throws Exception {
ChannelTestUtils.RecordingPair pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
PaymentChannelServer server = pair.server;
- PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, Sha256Hash.ZERO_HASH, pair.clientRecorder, versionSelector);
+ PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, Sha256Hash.ZERO_HASH, null, clientChannelProperties, pair.clientRecorder);
client.connectionOpen();
server.connectionOpen();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION));
@@ -656,7 +664,7 @@ public class ChannelConnectionTest extends TestWithWallet {
pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
server = pair.server;
final Coin myValue = COIN.multiply(10);
- client = new PaymentChannelClient(wallet, myKey, myValue, Sha256Hash.ZERO_HASH, pair.clientRecorder, versionSelector);
+ client = new PaymentChannelClient(wallet, myKey, myValue, Sha256Hash.ZERO_HASH, null, clientChannelProperties, pair.clientRecorder);
client.connectionOpen();
server.connectionOpen();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION));
@@ -684,7 +692,7 @@ public class ChannelConnectionTest extends TestWithWallet {
emptyWallet.freshReceiveKey();
ChannelTestUtils.RecordingPair pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
PaymentChannelServer server = pair.server;
- PaymentChannelClient client = new PaymentChannelClient(emptyWallet, myKey, COIN, Sha256Hash.ZERO_HASH, pair.clientRecorder, versionSelector);
+ PaymentChannelClient client = new PaymentChannelClient(emptyWallet, myKey, COIN, Sha256Hash.ZERO_HASH, null, clientChannelProperties, pair.clientRecorder);
client.connectionOpen();
server.connectionOpen();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION));
@@ -706,7 +714,7 @@ public class ChannelConnectionTest extends TestWithWallet {
public void testClientRefusesNonCanonicalKey() throws Exception {
ChannelTestUtils.RecordingPair pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
PaymentChannelServer server = pair.server;
- PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, Sha256Hash.ZERO_HASH, pair.clientRecorder, versionSelector);
+ PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, Sha256Hash.ZERO_HASH, null, clientChannelProperties, pair.clientRecorder);
client.connectionOpen();
server.connectionOpen();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION));
@@ -724,7 +732,7 @@ public class ChannelConnectionTest extends TestWithWallet {
public void testClientResumeNothing() throws Exception {
ChannelTestUtils.RecordingPair pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
PaymentChannelServer server = pair.server;
- PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, Sha256Hash.ZERO_HASH, pair.clientRecorder, versionSelector);
+ PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, Sha256Hash.ZERO_HASH, null, clientChannelProperties, pair.clientRecorder);
client.connectionOpen();
server.connectionOpen();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION));
@@ -738,7 +746,7 @@ public class ChannelConnectionTest extends TestWithWallet {
@Test
public void testClientRandomMessage() throws Exception {
ChannelTestUtils.RecordingPair pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
- PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, Sha256Hash.ZERO_HASH, pair.clientRecorder, versionSelector);
+ PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, Sha256Hash.ZERO_HASH, null, clientChannelProperties, pair.clientRecorder);
client.connectionOpen();
pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION);
@@ -759,7 +767,7 @@ public class ChannelConnectionTest extends TestWithWallet {
Sha256Hash someServerId = Sha256Hash.ZERO_HASH;
ChannelTestUtils.RecordingPair pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
pair.server.connectionOpen();
- PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, someServerId, pair.clientRecorder, versionSelector);
+ PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, someServerId, null, clientChannelProperties, pair.clientRecorder);
PaymentChannelServer server = pair.server;
client.connectionOpen();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION));
@@ -792,7 +800,7 @@ public class ChannelConnectionTest extends TestWithWallet {
client.connectionClosed();
// Now try opening a new channel with the same server ID and verify the client asks for a new channel.
- client = new PaymentChannelClient(wallet, myKey, COIN, someServerId, pair.clientRecorder, versionSelector);
+ client = new PaymentChannelClient(wallet, myKey, COIN, someServerId, null, clientChannelProperties, pair.clientRecorder);
client.connectionOpen();
Protos.TwoWayChannelMessage msg = pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION);
assertFalse(msg.getClientVersion().hasPreviousChannelContractHash());
@@ -807,7 +815,7 @@ public class ChannelConnectionTest extends TestWithWallet {
Sha256Hash someServerId = Sha256Hash.ZERO_HASH;
ChannelTestUtils.RecordingPair pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
pair.server.connectionOpen();
- PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, someServerId, pair.clientRecorder, versionSelector);
+ PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, someServerId, null, clientChannelProperties, pair.clientRecorder);
PaymentChannelServer server = pair.server;
client.connectionOpen();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION));
@@ -861,7 +869,7 @@ public class ChannelConnectionTest extends TestWithWallet {
Sha256Hash someServerId = Sha256Hash.ZERO_HASH;
ChannelTestUtils.RecordingPair pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
pair.server.connectionOpen();
- PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, someServerId, pair.clientRecorder, versionSelector);
+ PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, someServerId, null, clientChannelProperties, pair.clientRecorder);
PaymentChannelServer server = pair.server;
client.connectionOpen();
final Protos.TwoWayChannelMessage msg = pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION);
@@ -892,7 +900,7 @@ public class ChannelConnectionTest extends TestWithWallet {
Sha256Hash someServerId = Sha256Hash.ZERO_HASH;
ChannelTestUtils.RecordingPair pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
pair.server.connectionOpen();
- PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, someServerId, pair.clientRecorder, versionSelector);
+ PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, someServerId, null, clientChannelProperties, pair.clientRecorder);
PaymentChannelServer server = pair.server;
client.connectionOpen();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION));
diff --git a/core/src/test/java/org/bitcoinj/protocols/channels/PaymentChannelClientTest.java b/core/src/test/java/org/bitcoinj/protocols/channels/PaymentChannelClientTest.java
index 999782cc..25d04acd 100644
--- a/core/src/test/java/org/bitcoinj/protocols/channels/PaymentChannelClientTest.java
+++ b/core/src/test/java/org/bitcoinj/protocols/channels/PaymentChannelClientTest.java
@@ -34,6 +34,9 @@ import java.util.HashMap;
import static org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage;
import static org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage.MessageType.*;
+import static org.bitcoinj.protocols.channels.PaymentChannelClient.VersionSelector.VERSION_1;
+import static org.bitcoinj.protocols.channels.PaymentChannelClient.VersionSelector.VERSION_2;
+import static org.bitcoinj.protocols.channels.PaymentChannelClient.VersionSelector.VERSION_2_ALLOW_1;
import static org.easymock.EasyMock.capture;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.replay;
@@ -55,16 +58,25 @@ public class PaymentChannelClientTest {
* version of the channel.
*/
@Parameterized.Parameters(name = "{index}: PaymentChannelClientTest({0})")
- public static Collection