@Test 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, null, clientChannelProperties, pair.clientRecorder); PaymentChannelServer server = pair.server; server.connectionOpen(); client.connectionOpen(); server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION)); client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.SERVER_VERSION)); client.settle(); client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.INITIATE)); server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLOSE)); assertEquals(CloseReason.CLIENT_REQUESTED_CLOSE, pair.serverRecorder.q.take()); }
@Test public void shouldAcceptDefaultTimeWindow() { final TwoWayChannelMessage message = createClientVersionMessage(); final Capture<TwoWayChannelMessage> initiateCapture = new Capture<>(); 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 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, null, clientChannelProperties, pair.clientRecorder); PaymentChannelServer server = pair.server; server.connectionOpen(); client.connectionOpen(); server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION)); client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.SERVER_VERSION)); client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.INITIATE)); server.receiveMessage(Protos.TwoWayChannelMessage.newBuilder() .setType(MessageType.ERROR) .setError(Protos.Error.newBuilder().setCode(Protos.Error.ErrorCode.TIMEOUT)) .build()); assertEquals(CloseReason.REMOTE_SENT_ERROR, pair.serverRecorder.q.take()); }
@Test 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, null, clientChannelProperties, pair.clientRecorder); client.connectionOpen(); server.connectionOpen(); server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION)); client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.SERVER_VERSION)); Protos.TwoWayChannelMessage.Builder initiateMsg = Protos.TwoWayChannelMessage.newBuilder(pair.serverRecorder.checkNextMsg(MessageType.INITIATE)); ByteString brokenKey = initiateMsg.getInitiate().getMultisigKey(); brokenKey = ByteString.copyFrom(Arrays.copyOf(brokenKey.toByteArray(), brokenKey.size() + 1)); initiateMsg.getInitiateBuilder().setMultisigKey(brokenKey); client.receiveMessage(initiateMsg.build()); pair.clientRecorder.checkNextMsg(MessageType.ERROR); assertEquals(CloseReason.REMOTE_SENT_INVALID_MESSAGE, pair.clientRecorder.q.take()); }
@Test public void shouldAllowExactTimeWindow() { final TwoWayChannelMessage message = createClientVersionMessage(); final Capture<TwoWayChannelMessage> initiateCapture = new Capture<>(); 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); }
@Test public void testBadResumeHash() throws InterruptedException { // Check that server-side will reject incorrectly formatted hashes. If anything goes wrong with session resume, // then the server will start the opening of a new channel automatically, so we expect to see INITIATE here. ChannelTestUtils.RecordingPair srv = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster); srv.server.connectionOpen(); srv.server.receiveMessage(Protos.TwoWayChannelMessage.newBuilder() .setType(MessageType.CLIENT_VERSION) .setClientVersion(Protos.ClientVersion.newBuilder() .setPreviousChannelContractHash(ByteString.copyFrom(new byte[]{0x00, 0x01})) .setMajor(CLIENT_MAJOR_VERSION).setMinor(42)) .build()); srv.serverRecorder.checkNextMsg(MessageType.SERVER_VERSION); srv.serverRecorder.checkNextMsg(MessageType.INITIATE); assertTrue(srv.serverRecorder.q.isEmpty()); }
PaymentChannelServer server = pair.server; client.connectionOpen(); server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION)); client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.SERVER_VERSION)); client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.INITIATE)); if (useRefunds()) { server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.PROVIDE_REFUND)); client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.RETURN_REFUND)); server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.PROVIDE_CONTRACT)); broadcasts.take(); pair.serverRecorder.checkTotalPayment(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE); server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.UPDATE_PAYMENT)); broadcasts.take();
@Test public void shouldTruncateTooLargeTimeWindow() { final int maxTimeWindow = 40000; final int timeWindow = maxTimeWindow + 1; final TwoWayChannelMessage message = createClientVersionMessage(timeWindow); final Capture<TwoWayChannelMessage> initiateCapture = new Capture<>(); 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 public void shouldTruncateTooSmallTimeWindow() { final int minTimeWindow = 20000; final int timeWindow = minTimeWindow - 1; final TwoWayChannelMessage message = createClientVersionMessage(timeWindow); final Capture<TwoWayChannelMessage> initiateCapture = new Capture<>(); 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 testClientResumeNothing() throws Exception { ChannelTestUtils.RecordingPair pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster); PaymentChannelServer server = pair.server; 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)); client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.SERVER_VERSION)); client.receiveMessage(Protos.TwoWayChannelMessage.newBuilder() .setType(MessageType.CHANNEL_OPEN).build()); pair.clientRecorder.checkNextMsg(MessageType.ERROR); assertEquals(CloseReason.REMOTE_SENT_INVALID_MESSAGE, pair.clientRecorder.q.take()); }
PaymentChannelServer server = pair.server; client.connectionOpen(); server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION)); client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.SERVER_VERSION)); client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.INITIATE)); if (useRefunds()) { server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.PROVIDE_REFUND)); client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.RETURN_REFUND)); server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.PROVIDE_CONTRACT)); broadcasts.take(); pair.serverRecorder.checkTotalPayment(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE); for (int i = 0; i < 3; i++) { ListenableFuture<PaymentIncrementAck> future = client.incrementPayment(CENT); server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.UPDATE_PAYMENT)); pair.serverRecorder.q.take(); final Protos.TwoWayChannelMessage msg = pair.serverRecorder.checkNextMsg(MessageType.PAYMENT_ACK); server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLOSE)); Transaction settlement1 = broadcasts.take(); final Protos.TwoWayChannelMessage msg = pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION); assertFalse(msg.getClientVersion().hasPreviousChannelContractHash()); server.receiveMessage(msg); client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.SERVER_VERSION)); client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.INITIATE)); if (useRefunds()) { server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.PROVIDE_REFUND));
PaymentChannelServer server = pair.server; client.connectionOpen(); server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION)); client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.SERVER_VERSION)); final Protos.TwoWayChannelMessage initiateMsg = pair.serverRecorder.checkNextMsg(MessageType.INITIATE); client.receiveMessage(initiateMsg); if (useRefunds()) { server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.PROVIDE_REFUND)); client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.RETURN_REFUND)); server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.PROVIDE_CONTRACT)); broadcasts.take(); pair.serverRecorder.checkTotalPayment(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE);
@Test public void testServerErrorHandling_badTransaction() throws Exception { if (!useRefunds()) { // This test only applies to versions with refunds return; } // 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, null, clientChannelProperties, pair.clientRecorder); PaymentChannelServer server = pair.server; server.connectionOpen(); client.connectionOpen(); // Make sure we get back a BAD_TRANSACTION if we send a bogus refund transaction. server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION)); client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.SERVER_VERSION)); client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.INITIATE)); Protos.TwoWayChannelMessage msg = pair.clientRecorder.checkNextMsg(MessageType.PROVIDE_REFUND); server.receiveMessage(Protos.TwoWayChannelMessage.newBuilder() .setType(MessageType.PROVIDE_REFUND) .setProvideRefund( Protos.ProvideRefund.newBuilder(msg.getProvideRefund()) .setMultisigKey(ByteString.EMPTY) .setTx(ByteString.EMPTY) ).build()); final Protos.TwoWayChannelMessage errorMsg = pair.serverRecorder.checkNextMsg(MessageType.ERROR); assertEquals(Protos.Error.ErrorCode.BAD_TRANSACTION, errorMsg.getError().getCode()); }
PaymentChannelServer server = pair.server; client.connectionOpen(); server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION)); client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.SERVER_VERSION)); final Protos.TwoWayChannelMessage initiateMsg = pair.serverRecorder.checkNextMsg(MessageType.INITIATE); client.receiveMessage(initiateMsg); if (useRefunds()) { server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.PROVIDE_REFUND)); client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.RETURN_REFUND)); server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.PROVIDE_CONTRACT)); broadcasts.take(); pair.serverRecorder.checkTotalPayment(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE); server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.UPDATE_PAYMENT)); assertEquals(amount, ((ChannelTestUtils.UpdatePair)pair.serverRecorder.q.take()).amount); server.close(); pair.server.receiveMessage(Protos.TwoWayChannelMessage.newBuilder() .setType(MessageType.CLIENT_VERSION) .setClientVersion(Protos.ClientVersion.newBuilder() assertTrue(clientVersionMsg.getClientVersion().hasPreviousChannelContractHash()); assertEquals(contractHash, Sha256Hash.wrap(clientVersionMsg.getClientVersion().getPreviousChannelContractHash().toByteArray())); server.receiveMessage(clientVersionMsg); client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.SERVER_VERSION)); client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.CHANNEL_OPEN));
@Test public void testClientTimeWindowUnacceptable() throws Exception { // 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, null, clientChannelProperties, pair.clientRecorder); client.connectionOpen(); server.connectionOpen(); server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION)); client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.SERVER_VERSION)); client.receiveMessage(Protos.TwoWayChannelMessage.newBuilder() .setInitiate(Protos.Initiate.newBuilder().setExpireTimeSecs(Utils.currentTimeSeconds() + 60 * 60 * 48) .setMinAcceptedChannelSize(100) .setMultisigKey(ByteString.copyFrom(new ECKey().getPubKey())) .setMinPayment(Transaction.MIN_NONDUST_OUTPUT.value)) .setType(MessageType.INITIATE).build()); pair.clientRecorder.checkNextMsg(MessageType.ERROR); assertEquals(CloseReason.TIME_WINDOW_UNACCEPTABLE, pair.clientRecorder.q.take()); // Double-check that we cant do anything that requires an open channel try { client.incrementPayment(Coin.SATOSHI); fail(); } catch (IllegalStateException e) { } }
@Test public void testEmptyWallet() throws Exception { Wallet emptyWallet = new Wallet(PARAMS); emptyWallet.freshReceiveKey(); ChannelTestUtils.RecordingPair pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster); PaymentChannelServer server = pair.server; 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)); client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.SERVER_VERSION)); try { client.receiveMessage(Protos.TwoWayChannelMessage.newBuilder() .setInitiate(Protos.Initiate.newBuilder().setExpireTimeSecs(Utils.currentTimeSeconds()) .setMinAcceptedChannelSize(CENT.value) .setMultisigKey(ByteString.copyFrom(new ECKey().getPubKey())) .setMinPayment(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.value)) .setType(MessageType.INITIATE).build()); fail(); } catch (InsufficientMoneyException expected) { // This should be thrown. } }
client.connectionOpen(); server.connectionOpen(); server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION)); client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.SERVER_VERSION)); client.receiveMessage(Protos.TwoWayChannelMessage.newBuilder() client.connectionOpen(); server.connectionOpen(); server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION)); client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.SERVER_VERSION)); client.receiveMessage(Protos.TwoWayChannelMessage.newBuilder()