/* * 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(); } }