Support for bundling an optional info Protobuf ByteString with a PaymentAck message

This commit is contained in:
cyberzac
2014-07-31 10:24:38 +02:00
committed by Mike Hearn
parent f1dd47443f
commit eff9ac2ecc
13 changed files with 793 additions and 105 deletions

View File

@@ -17,7 +17,7 @@ public class NativePaymentChannelServerConnectionEventHandler extends ServerConn
public native void channelOpen(Sha256Hash channelId); public native void channelOpen(Sha256Hash channelId);
@Override @Override
public native void paymentIncrease(Coin by, Coin to, ByteString info); public native ByteString paymentIncrease(Coin by, Coin to, ByteString info);
@Override @Override
public native void channelClosed(PaymentChannelCloseException.CloseReason reason); public native void channelClosed(PaymentChannelCloseException.CloseReason reason);

View File

@@ -84,7 +84,7 @@ public interface IPaymentChannelClient {
* (see {@link PaymentChannelClientConnection#getChannelOpenFuture()} for the second) * (see {@link PaymentChannelClientConnection#getChannelOpenFuture()} for the second)
* @return a future that completes when the server acknowledges receipt and acceptance of the payment. * @return a future that completes when the server acknowledges receipt and acceptance of the payment.
*/ */
ListenableFuture<Coin> incrementPayment(Coin size, @Nullable ByteString info) throws ValueOutOfRangeException, IllegalStateException; ListenableFuture<PaymentIncrementAck> incrementPayment(Coin size, @Nullable ByteString info) throws ValueOutOfRangeException, IllegalStateException;
/** /**
* Implements the connection between this client and the server, providing an interface which allows messages to be * Implements the connection between this client and the server, providing an interface which allows messages to be

View File

@@ -90,7 +90,7 @@ public class PaymentChannelClient implements IPaymentChannelClient {
@GuardedBy("lock") private long minPayment; @GuardedBy("lock") private long minPayment;
@GuardedBy("lock") SettableFuture<Coin> increasePaymentFuture; @GuardedBy("lock") SettableFuture<PaymentIncrementAck> increasePaymentFuture;
@GuardedBy("lock") Coin lastPaymentActualAmount; @GuardedBy("lock") Coin lastPaymentActualAmount;
/** /**
@@ -290,7 +290,7 @@ public class PaymentChannelClient implements IPaymentChannelClient {
receiveChannelOpen(); receiveChannelOpen();
return; return;
case PAYMENT_ACK: case PAYMENT_ACK:
receivePaymentAck(); receivePaymentAck(msg.getPaymentAck());
return; return;
case CLOSE: case CLOSE:
receiveClose(msg); receiveClose(msg);
@@ -462,13 +462,13 @@ public class PaymentChannelClient implements IPaymentChannelClient {
* you wait for the previous increase payment future to complete before incrementing the payment again. * you wait for the previous increase payment future to complete before incrementing the payment again.
* *
* @param size How many satoshis to increment the payment by (note: not the new total). * @param size How many satoshis to increment the payment by (note: not the new total).
* @return a future that completes when the server acknowledges receipt and acceptance of the payment.
* @throws ValueOutOfRangeException If the size is negative or would pay more than this channel's total value * @throws ValueOutOfRangeException If the size is negative or would pay more than this channel's total value
* ({@link PaymentChannelClientConnection#state()}.getTotalValue()) * ({@link PaymentChannelClientConnection#state()}.getTotalValue())
* @throws IllegalStateException If the channel has been closed or is not yet open * @throws IllegalStateException If the channel has been closed or is not yet open
* (see {@link PaymentChannelClientConnection#getChannelOpenFuture()} for the second) * (see {@link PaymentChannelClientConnection#getChannelOpenFuture()} for the second)
* @return a future that completes when the server acknowledges receipt and acceptance of the payment.
*/ */
public ListenableFuture<Coin> incrementPayment(Coin size) throws ValueOutOfRangeException, IllegalStateException { public ListenableFuture<PaymentIncrementAck> incrementPayment(Coin size) throws ValueOutOfRangeException, IllegalStateException {
return incrementPayment(size, null); return incrementPayment(size, null);
} }
@@ -481,14 +481,14 @@ public class PaymentChannelClient implements IPaymentChannelClient {
* *
* @param size How many satoshis to increment the payment by (note: not the new total). * @param size How many satoshis to increment the payment by (note: not the new total).
* @param info Information about this update, used to extend this protocol. * @param info Information about this update, used to extend this protocol.
* @return a future that completes when the server acknowledges receipt and acceptance of the payment.
* @throws ValueOutOfRangeException If the size is negative or would pay more than this channel's total value * @throws ValueOutOfRangeException If the size is negative or would pay more than this channel's total value
* ({@link PaymentChannelClientConnection#state()}.getTotalValue()) * ({@link PaymentChannelClientConnection#state()}.getTotalValue())
* @throws IllegalStateException If the channel has been closed or is not yet open * @throws IllegalStateException If the channel has been closed or is not yet open
* (see {@link PaymentChannelClientConnection#getChannelOpenFuture()} for the second) * (see {@link PaymentChannelClientConnection#getChannelOpenFuture()} for the second)
* @return a future that completes when the server acknowledges receipt and acceptance of the payment.
*/ */
@Override @Override
public ListenableFuture<Coin> incrementPayment(Coin size, @Nullable ByteString info) throws ValueOutOfRangeException, IllegalStateException { public ListenableFuture<PaymentIncrementAck> incrementPayment(Coin size, @Nullable ByteString info) throws ValueOutOfRangeException, IllegalStateException {
lock.lock(); lock.lock();
try { try {
if (state() == null || !connectionOpen || step != InitStep.CHANNEL_OPEN) if (state() == null || !connectionOpen || step != InitStep.CHANNEL_OPEN)
@@ -523,8 +523,8 @@ public class PaymentChannelClient implements IPaymentChannelClient {
} }
} }
private void receivePaymentAck() { private void receivePaymentAck(Protos.PaymentAck paymentAck) {
SettableFuture<Coin> future; SettableFuture<PaymentIncrementAck> future;
Coin value; Coin value;
lock.lock(); lock.lock();
@@ -539,6 +539,6 @@ public class PaymentChannelClient implements IPaymentChannelClient {
} }
// Ensure the future runs without the client lock held. // Ensure the future runs without the client lock held.
future.set(value); future.set(new PaymentIncrementAck(value, paymentAck.getInfo()));
} }
} }

View File

@@ -136,7 +136,7 @@ public class PaymentChannelClientConnection {
* @throws IllegalStateException If the channel has been closed or is not yet open * @throws IllegalStateException If the channel has been closed or is not yet open
* (see {@link PaymentChannelClientConnection#getChannelOpenFuture()} for the second) * (see {@link PaymentChannelClientConnection#getChannelOpenFuture()} for the second)
*/ */
public ListenableFuture<Coin> incrementPayment(Coin size) throws ValueOutOfRangeException, IllegalStateException { public ListenableFuture<PaymentIncrementAck> incrementPayment(Coin size) throws ValueOutOfRangeException, IllegalStateException {
return channelClient.incrementPayment(size, null); return channelClient.incrementPayment(size, null);
} }
/** /**
@@ -149,7 +149,7 @@ public class PaymentChannelClientConnection {
* @throws IllegalStateException If the channel has been closed or is not yet open * @throws IllegalStateException If the channel has been closed or is not yet open
* (see {@link PaymentChannelClientConnection#getChannelOpenFuture()} for the second) * (see {@link PaymentChannelClientConnection#getChannelOpenFuture()} for the second)
*/ */
public ListenableFuture<Coin> incrementPayment(Coin size, ByteString info) throws ValueOutOfRangeException, IllegalStateException { public ListenableFuture<PaymentIncrementAck> incrementPayment(Coin size, ByteString info) throws ValueOutOfRangeException, IllegalStateException {
return channelClient.incrementPayment(size, info); return channelClient.incrementPayment(size, info);
} }

View File

@@ -101,8 +101,10 @@ public class PaymentChannelServer {
* @param by The increase in total payment * @param by The increase in total payment
* @param to The new total payment to us (not including fees which may be required to claim the payment) * @param to The new total payment to us (not including fees which may be required to claim the payment)
* @param info Information about this payment increase, used to extend this protocol. * @param info Information about this payment increase, used to extend this protocol.
* @return An ack message that will be included in the PaymentAck message to the client. Use null for no ack message.
*/ */
public void paymentIncrease(Coin by, Coin to, @Nullable ByteString info); @Nullable
public ByteString paymentIncrease(Coin by, Coin to, @Nullable ByteString info);
} }
private final ServerConnection conn; private final ServerConnection conn;
@@ -315,14 +317,16 @@ public class PaymentChannelServer {
boolean stillUsable = state.incrementPayment(refundSize, msg.getSignature().toByteArray()); boolean stillUsable = state.incrementPayment(refundSize, msg.getSignature().toByteArray());
Coin bestPaymentChange = state.getBestValueToMe().subtract(lastBestPayment); Coin bestPaymentChange = state.getBestValueToMe().subtract(lastBestPayment);
ByteString ackInfo = null;
if (bestPaymentChange.signum() > 0) { if (bestPaymentChange.signum() > 0) {
ByteString info = (msg.hasInfo()) ? msg.getInfo() : null; ByteString info = (msg.hasInfo()) ? msg.getInfo() : null;
conn.paymentIncrease(bestPaymentChange, state.getBestValueToMe(), info); ackInfo = conn.paymentIncrease(bestPaymentChange, state.getBestValueToMe(), info);
} }
if (sendAck) { if (sendAck) {
Protos.TwoWayChannelMessage.Builder ack = Protos.TwoWayChannelMessage.newBuilder(); Protos.TwoWayChannelMessage.Builder ack = Protos.TwoWayChannelMessage.newBuilder();
ack.setType(Protos.TwoWayChannelMessage.MessageType.PAYMENT_ACK); ack.setType(Protos.TwoWayChannelMessage.MessageType.PAYMENT_ACK);
if (ackInfo != null) ack.setPaymentAck(ack.getPaymentAckBuilder().setInfo(ackInfo));
conn.sendToClient(ack.build()); conn.sendToClient(ack.build());
} }

View File

@@ -83,8 +83,8 @@ public class PaymentChannelServerListener {
eventHandler.channelOpen(contractHash); eventHandler.channelOpen(contractHash);
} }
@Override public void paymentIncrease(Coin by, Coin to, @Nullable ByteString info) { @Override public ByteString paymentIncrease(Coin by, Coin to, @Nullable ByteString info) {
eventHandler.paymentIncrease(by, to, info); return eventHandler.paymentIncrease(by, to, info);
} }
}); });

View File

@@ -0,0 +1,28 @@
package com.google.bitcoin.protocols.channels;
import com.google.bitcoin.core.Coin;
import com.google.protobuf.ByteString;
import javax.annotation.Nullable;
/**
* An acknowledgement of a payment increase
*/
public class PaymentIncrementAck {
private final Coin value;
@Nullable private final ByteString info;
public PaymentIncrementAck(Coin value, @Nullable ByteString info) {
this.value = value;
this.info = info;
}
public Coin getValue() {
return value;
}
@Nullable
public ByteString getInfo() {
return info;
}
}

View File

@@ -71,8 +71,10 @@ public abstract class ServerConnectionEventHandler {
* @param by The increase in total payment * @param by The increase in total payment
* @param to The new total payment to us (not including fees which may be required to claim the payment) * @param to The new total payment to us (not including fees which may be required to claim the payment)
* @param info Information about this payment increase, used to extend this protocol. * @param info Information about this payment increase, used to extend this protocol.
* @return acknowledgment information to be sent to the client.
*/ */
public abstract void paymentIncrease(Coin by, Coin to, ByteString info); @Nullable
public abstract ByteString paymentIncrease(Coin by, Coin to, ByteString info);
/** /**
* <p>Called when the channel was closed for some reason. May be called without a call to * <p>Called when the channel was closed for some reason. May be called without a call to

File diff suppressed because it is too large Load Diff

View File

@@ -83,6 +83,7 @@ message TwoWayChannelMessage {
optional ReturnRefund return_refund = 6; optional ReturnRefund return_refund = 6;
optional ProvideContract provide_contract = 7; optional ProvideContract provide_contract = 7;
optional UpdatePayment update_payment = 8; optional UpdatePayment update_payment = 8;
optional PaymentAck payment_ack = 11;
optional Settlement settlement = 9; optional Settlement settlement = 9;
optional Error error = 10; optional Error error = 10;
@@ -214,6 +215,12 @@ message UpdatePayment {
} }
// This message is sent as an acknowledgement of an UpdatePayment message
message PaymentAck {
// Information about this update. Used to extend this protocol
optional bytes info = 1;
}
message Settlement { message Settlement {
// A copy of the fully signed final contract that settles the channel. The client can verify // A copy of the fully signed final contract that settles the channel. The client can verify
// the transaction is correct and then commit it to their wallet. // the transaction is correct and then commit it to their wallet.

View File

@@ -130,8 +130,9 @@ public class ChannelConnectionTest extends TestWithWallet {
} }
@Override @Override
public void paymentIncrease(Coin by, Coin to, ByteString info) { public ByteString paymentIncrease(Coin by, Coin to, ByteString info) {
q.add(new ChannelTestUtils.UpdatePair(to, info)); q.add(new ChannelTestUtils.UpdatePair(to, info));
return null;
} }
@Override @Override
@@ -682,20 +683,20 @@ public class ChannelConnectionTest extends TestWithWallet {
pair.clientRecorder.checkInitiated(); pair.clientRecorder.checkInitiated();
assertNull(pair.serverRecorder.q.poll()); assertNull(pair.serverRecorder.q.poll());
assertNull(pair.clientRecorder.q.poll()); assertNull(pair.clientRecorder.q.poll());
ListenableFuture<Coin> future = client.incrementPayment(CENT); for (int i = 0; i < 3; i++) {
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.UPDATE_PAYMENT)); ListenableFuture<PaymentIncrementAck> future = client.incrementPayment(CENT);
pair.serverRecorder.q.take(); server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.UPDATE_PAYMENT));
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.PAYMENT_ACK)); pair.serverRecorder.q.take();
assertTrue(future.isDone()); final Protos.TwoWayChannelMessage msg = pair.serverRecorder.checkNextMsg(MessageType.PAYMENT_ACK);
client.incrementPayment(CENT); final Protos.PaymentAck paymentAck = msg.getPaymentAck();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.UPDATE_PAYMENT)); assertTrue("No PaymentAck.Info", paymentAck.hasInfo());
pair.serverRecorder.q.take(); assertEquals("Wrong PaymentAck info", ByteString.copyFromUtf8(CENT.toPlainString()), paymentAck.getInfo());
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.PAYMENT_ACK)); client.receiveMessage(msg);
assertTrue(future.isDone());
client.incrementPayment(CENT); final PaymentIncrementAck paymentIncrementAck = future.get();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.UPDATE_PAYMENT)); assertEquals("Wrong value returned from increasePayment", CENT, paymentIncrementAck.getValue());
pair.serverRecorder.q.take(); assertEquals("Wrong info returned from increasePayment", ByteString.copyFromUtf8(CENT.toPlainString()), paymentIncrementAck.getInfo());
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.PAYMENT_ACK)); }
// Settle it and verify it's considered to be settled. // Settle it and verify it's considered to be settled.
broadcastTxPause.release(); broadcastTxPause.release();

View File

@@ -37,8 +37,9 @@ public class ChannelTestUtils {
} }
@Override @Override
public void paymentIncrease(Coin by, Coin to, @Nullable ByteString info) { public ByteString paymentIncrease(Coin by, Coin to, @Nullable ByteString info) {
q.add(new UpdatePair(to, info)); q.add(new UpdatePair(to, info));
return ByteString.copyFromUtf8(by.toPlainString());
} }
public Protos.TwoWayChannelMessage getNextMsg() throws InterruptedException { public Protos.TwoWayChannelMessage getNextMsg() throws InterruptedException {

View File

@@ -102,8 +102,9 @@ public class ExamplePaymentChannelServer implements PaymentChannelServerListener
} }
@Override @Override
public void paymentIncrease(Coin by, Coin to, ByteString info) { public ByteString paymentIncrease(Coin by, Coin to, ByteString info) {
log.info("Client {} paid increased payment by {} for a total of " + to.toString(), clientAddress, by); log.info("Client {} paid increased payment by {} for a total of " + to.toString(), clientAddress, by);
return null;
} }
@Override @Override