Close increasePaymentFuture when receiving an ERROR from the server.

This commit is contained in:
ollekullberg 2015-08-29 12:09:04 +02:00 committed by Mike Hearn
parent 829e147ec7
commit 724cf7de36
2 changed files with 82 additions and 7 deletions

View File

@ -338,12 +338,14 @@ public class PaymentChannelClient implements IPaymentChannelClient {
checkState(msg.hasError());
log.error("Server sent ERROR {} with explanation {}", msg.getError().getCode().name(),
msg.getError().hasExplanation() ? msg.getError().getExplanation() : "");
setIncreasePaymentFutureIfNeeded(CloseReason.REMOTE_SENT_ERROR, msg.getError().getCode().name());
conn.destroyConnection(CloseReason.REMOTE_SENT_ERROR);
return;
default:
log.error("Got unknown message type or type that doesn't apply to clients.");
errorBuilder = Protos.Error.newBuilder()
.setCode(Protos.Error.ErrorCode.SYNTAX_ERROR);
setIncreasePaymentFutureIfNeeded(CloseReason.REMOTE_SENT_INVALID_MESSAGE, "");
closeReason = CloseReason.REMOTE_SENT_INVALID_MESSAGE;
break;
}
@ -369,6 +371,18 @@ public class PaymentChannelClient implements IPaymentChannelClient {
}
}
/*
* If this is an ongoing payment channel increase we need to call setException() on its future.
*
* @param reason is the reason for aborting
* @param message is the detailed message
*/
private void setIncreasePaymentFutureIfNeeded(PaymentChannelCloseException.CloseReason reason, String message) {
if (increasePaymentFuture != null && !increasePaymentFuture.isDone()) {
increasePaymentFuture.setException(new PaymentChannelCloseException(message, reason));
}
}
@GuardedBy("lock")
private void receiveClose(Protos.TwoWayChannelMessage msg) throws VerificationException {
checkState(lock.isHeldByCurrentThread());

View File

@ -32,6 +32,7 @@ import org.junit.Test;
import org.spongycastle.crypto.params.KeyParameter;
import javax.annotation.Nullable;
import javax.lang.model.type.ExecutableType;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
@ -233,7 +234,7 @@ public class ChannelConnectionTest extends TestWithWallet {
}
@Test
public void testServerErrorHandling() throws Exception {
public void testServerErrorHandling_badTransaction() throws Exception {
// 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);
@ -255,11 +256,14 @@ public class ChannelConnectionTest extends TestWithWallet {
).build());
final Protos.TwoWayChannelMessage errorMsg = pair.serverRecorder.checkNextMsg(MessageType.ERROR);
assertEquals(Protos.Error.ErrorCode.BAD_TRANSACTION, errorMsg.getError().getCode());
}
@Test
public void testServerErrorHandling_killSocketOnClose() throws Exception {
// Make sure the server closes the socket on CLOSE
pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
client = new PaymentChannelClient(wallet, myKey, COIN, Sha256Hash.ZERO_HASH, pair.clientRecorder);
server = pair.server;
ChannelTestUtils.RecordingPair pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, Sha256Hash.ZERO_HASH, pair.clientRecorder);
PaymentChannelServer server = pair.server;
server.connectionOpen();
client.connectionOpen();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION));
@ -270,10 +274,14 @@ public class ChannelConnectionTest extends TestWithWallet {
assertEquals(CloseReason.CLIENT_REQUESTED_CLOSE, pair.serverRecorder.q.take());
}
@Test
public void testServerErrorHandling_killSocketOnError() throws Exception {
// Make sure the server closes the socket on ERROR
pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
client = new PaymentChannelClient(wallet, myKey, COIN, Sha256Hash.ZERO_HASH, pair.clientRecorder);
server = pair.server;
ChannelTestUtils.RecordingPair pair = ChannelTestUtils.makeRecorders(serverWallet, mockBroadcaster);
PaymentChannelClient client = new PaymentChannelClient(wallet, myKey, COIN, Sha256Hash.ZERO_HASH, pair.clientRecorder);
PaymentChannelServer server = pair.server;
server.connectionOpen();
client.connectionOpen();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLIENT_VERSION));
@ -286,6 +294,59 @@ public class ChannelConnectionTest extends TestWithWallet {
assertEquals(CloseReason.REMOTE_SENT_ERROR, pair.serverRecorder.q.take());
}
@Test
public void testClientErrorHandlingIncreasePaymentError() throws Exception {
// Tests various aspects of channel resuming.
Utils.setMockClock();
final Sha256Hash someServerId = Sha256Hash.of(new byte[]{});
// 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);
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);
Coin minPayment = Coin.valueOf(initiateMsg.getInitiate().getMinPayment());
client.receiveMessage(initiateMsg);
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.PROVIDE_REFUND));
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.RETURN_REFUND));
broadcastTxPause.release();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.PROVIDE_CONTRACT));
broadcasts.take();
pair.serverRecorder.checkTotalPayment(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE);
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.CHANNEL_OPEN));
Sha256Hash contractHash = (Sha256Hash) pair.serverRecorder.q.take();
pair.clientRecorder.checkInitiated();
assertNull(pair.serverRecorder.q.poll());
assertNull(pair.clientRecorder.q.poll());
assertEquals(minPayment, client.state().getValueSpent());
// Send a bitcent.
Coin amount = minPayment.add(CENT);
ListenableFuture<PaymentIncrementAck> ackFuture = client.incrementPayment(CENT);
// We never pass this message to the server
// Instead we pretend the server didn't like our increase
client.receiveMessage(Protos.TwoWayChannelMessage.newBuilder()
.setType(MessageType.ERROR)
.setError(Protos.Error.newBuilder().setCode(Protos.Error.ErrorCode.CHANNEL_VALUE_TOO_LARGE)) // some random error
.build());
// Now we need the client to actually close the future and report this error
try {
ackFuture.get(1L, TimeUnit.SECONDS);
fail("This should not work");
} catch (ExecutionException ee) {
PaymentChannelCloseException ce = (PaymentChannelCloseException) ee.getCause();
assertEquals(CloseReason.REMOTE_SENT_ERROR, ce.getCloseReason());
} catch (TimeoutException e) {
fail("Should not time out");
}
}
@Test
public void testChannelResume() throws Exception {
// Tests various aspects of channel resuming.