/*
* Copyright by the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.bitcoinj.protocols.channels;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.TransactionBroadcaster;
import org.bitcoinj.core.Utils;
import org.bitcoinj.wallet.Wallet;
import org.bitcoin.paymentchannel.Protos;
import org.easymock.Capture;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.util.Arrays;
import java.util.Collection;
import static junit.framework.TestCase.assertTrue;
import static org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage;
import static org.bitcoin.paymentchannel.Protos.TwoWayChannelMessage.MessageType;
import static org.easymock.EasyMock.*;
import static org.junit.Assert.assertEquals;
@RunWith(Parameterized.class)
public class PaymentChannelServerTest {
public Wallet wallet;
public PaymentChannelServer.ServerConnection connection;
public PaymentChannelServer dut;
public Capture<? extends TwoWayChannelMessage> serverVersionCapture;
private TransactionBroadcaster broadcaster;
@Before
public void setUp() {
broadcaster = createMock(TransactionBroadcaster.class);
wallet = createMock(Wallet.class);
connection = createMock(PaymentChannelServer.ServerConnection.class);
serverVersionCapture = new Capture<TwoWayChannelMessage>();
connection.sendToClient(capture(serverVersionCapture));
Utils.setMockClock();
}
/**
* We use parameterized tests to run the client channel tests with each
* version of the channel.
*/
@Parameterized.Parameters(name = "{index}: PaymentChannelServerTest(version {0})")
public static Collection<Integer> data() {
return Arrays.asList(1, 2);
}
@Parameterized.Parameter
public int protocolVersion;
@Test
public void shouldAcceptDefaultTimeWindow() {
final TwoWayChannelMessage message = createClientVersionMessage();
final Capture<TwoWayChannelMessage> initiateCapture = new Capture<TwoWayChannelMessage>();
connection.sendToClient(capture(initiateCapture));
replay(connection);
dut = new PaymentChannelServer(broadcaster, wallet, Coin.CENT, connection);
dut.connectionOpen();
dut.receiveMessage(message);
long expectedExpire = Utils.currentTimeSeconds() + 24 * 60 * 60 - 60; // This the default defined in paymentchannel.proto
assertServerVersion();
assertExpireTime(expectedExpire, initiateCapture);
}
@Test
public void shouldTruncateTooSmallTimeWindow() {
final int minTimeWindow = 20000;
final int timeWindow = minTimeWindow - 1;
final TwoWayChannelMessage message = createClientVersionMessage(timeWindow);
final Capture<TwoWayChannelMessage> initiateCapture = new Capture<TwoWayChannelMessage>();
connection.sendToClient(capture(initiateCapture));
replay(connection);
dut = new PaymentChannelServer(broadcaster, wallet, Coin.CENT, new PaymentChannelServer.DefaultServerChannelProperties() {
@Override
public long getMinTimeWindow() {
return minTimeWindow;
}
@Override
public long getMaxTimeWindow() {
return 40000;
}
}, connection);
dut.connectionOpen();
dut.receiveMessage(message);
long expectedExpire = Utils.currentTimeSeconds() + minTimeWindow;
assertServerVersion();
assertExpireTime(expectedExpire, initiateCapture);
}
@Test
public void shouldTruncateTooLargeTimeWindow() {
final int maxTimeWindow = 40000;
final int timeWindow = maxTimeWindow + 1;
final TwoWayChannelMessage message = createClientVersionMessage(timeWindow);
final Capture<TwoWayChannelMessage> initiateCapture = new Capture<TwoWayChannelMessage>();
connection.sendToClient(capture(initiateCapture));
replay(connection);
dut = new PaymentChannelServer(broadcaster, wallet, Coin.CENT, new PaymentChannelServer.DefaultServerChannelProperties(){
@Override
public long getMaxTimeWindow() {
return maxTimeWindow;
}
@Override
public long getMinTimeWindow() { return 20000; }
}, connection);
dut.connectionOpen();
dut.receiveMessage(message);
long expectedExpire = Utils.currentTimeSeconds() + maxTimeWindow;
assertServerVersion();
assertExpireTime(expectedExpire, initiateCapture);
}
@Test(expected = IllegalArgumentException.class)
public void shouldNotAllowTimeWindowLessThan2h() {
dut = new PaymentChannelServer(broadcaster, wallet, Coin.CENT, new PaymentChannelServer.DefaultServerChannelProperties(){
@Override
public long getMaxTimeWindow() { return 40000; }
@Override
public long getMinTimeWindow() {
return 7199;
}
}, connection);
}
@Test(expected = IllegalArgumentException.class)
public void shouldNotAllowNegativeTimeWindow() {
dut = new PaymentChannelServer(broadcaster, wallet, Coin.CENT, new PaymentChannelServer.DefaultServerChannelProperties(){
@Override
public long getMaxTimeWindow() { return 40000; }
@Override
public long getMinTimeWindow() { return 40001; }
}, connection);
}
@Test
public void shouldAllowExactTimeWindow() {
final TwoWayChannelMessage message = createClientVersionMessage();
final Capture<TwoWayChannelMessage> initiateCapture = new Capture<TwoWayChannelMessage>();
connection.sendToClient(capture(initiateCapture));
replay(connection);
final int expire = 24 * 60 * 60 - 60; // This the default defined in paymentchannel.proto
dut = new PaymentChannelServer(broadcaster, wallet, Coin.CENT, new PaymentChannelServer.DefaultServerChannelProperties(){
@Override
public long getMaxTimeWindow() { return expire; }
@Override
public long getMinTimeWindow() { return expire; }
}, connection);
dut.connectionOpen();
long expectedExpire = Utils.currentTimeSeconds() + expire;
dut.receiveMessage(message);
assertServerVersion();
assertExpireTime(expectedExpire, initiateCapture);
}
private void assertServerVersion() {
final TwoWayChannelMessage response = serverVersionCapture.getValue();
final MessageType type = response.getType();
assertEquals("Wrong type " + type, MessageType.SERVER_VERSION, type);
final long major = response.getServerVersion().getMajor();
assertEquals("Wrong major version", protocolVersion, major);
}
private void assertExpireTime(long expectedExpire, Capture<TwoWayChannelMessage> initiateCapture) {
final TwoWayChannelMessage response = initiateCapture.getValue();
final MessageType type = response.getType();
assertEquals("Wrong type " + type, MessageType.INITIATE, type);
final long actualExpire = response.getInitiate().getExpireTimeSecs();
assertTrue("Expire time too small " + expectedExpire + " > " + actualExpire, expectedExpire <= actualExpire);
assertTrue("Expire time too large " + expectedExpire + "<" + actualExpire, expectedExpire >= actualExpire);
}
private TwoWayChannelMessage createClientVersionMessage() {
final Protos.ClientVersion.Builder clientVersion = Protos.ClientVersion.newBuilder().setMajor(protocolVersion);
return TwoWayChannelMessage.newBuilder().setType(MessageType.CLIENT_VERSION).setClientVersion(clientVersion).build();
}
private TwoWayChannelMessage createClientVersionMessage(long timeWindow) {
final Protos.ClientVersion.Builder clientVersion = Protos.ClientVersion.newBuilder().setMajor(protocolVersion);
if (timeWindow > 0) clientVersion.setTimeWindowSecs(timeWindow);
return TwoWayChannelMessage.newBuilder().setType(MessageType.CLIENT_VERSION).setClientVersion(clientVersion).build();
}
}