package org.bitcoinj.protocols.channels; import org.bitcoinj.core.Coin; import org.bitcoinj.core.TransactionBroadcaster; import org.bitcoinj.core.Utils; import org.bitcoinj.core.Wallet; import org.bitcoin.paymentchannel.Protos; import org.easymock.Capture; import org.junit.Before; import org.junit.Test; 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; public class PaymentChannelServerTest { private static final int CLIENT_MAJOR_VERSION = 1; private static final long SERVER_MAJOR_VERSION = 1; 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(); } @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, minTimeWindow, 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, 20000, maxTimeWindow, 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, 7199, 40000, connection); } @Test(expected = IllegalArgumentException.class) public void shouldNotAllowNegativeTimeWindow() { dut = new PaymentChannelServer(broadcaster, wallet, Coin.CENT, 40001, 40000, 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, expire, 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", SERVER_MAJOR_VERSION, 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(CLIENT_MAJOR_VERSION); return TwoWayChannelMessage.newBuilder().setType(MessageType.CLIENT_VERSION).setClientVersion(clientVersion).build(); } private TwoWayChannelMessage createClientVersionMessage(long timeWindow) { final Protos.ClientVersion.Builder clientVersion = Protos.ClientVersion.newBuilder().setMajor(CLIENT_MAJOR_VERSION); if (timeWindow > 0) clientVersion.setTimeWindowSecs(timeWindow); return TwoWayChannelMessage.newBuilder().setType(MessageType.CLIENT_VERSION).setClientVersion(clientVersion).build(); } }