3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-12 02:05:53 +00:00

Support for bundling an optional info Protobuf ByteString with an UpdatePayment message.

This commit is contained in:
cyberzac 2014-07-29 15:24:19 +02:00 committed by Mike Hearn
parent 9e56093353
commit 1153192be8
12 changed files with 241 additions and 41 deletions

View File

@ -3,6 +3,7 @@ package com.google.bitcoin.jni;
import com.google.bitcoin.core.*;
import com.google.bitcoin.protocols.channels.PaymentChannelCloseException;
import com.google.bitcoin.protocols.channels.ServerConnectionEventHandler;
import com.google.protobuf.ByteString;
/**
* An event listener that relays events to a native C++ object. A pointer to that object is stored in
@ -16,7 +17,7 @@ public class NativePaymentChannelServerConnectionEventHandler extends ServerConn
public native void channelOpen(Sha256Hash channelId);
@Override
public native void paymentIncrease(Coin by, Coin to);
public native void paymentIncrease(Coin by, Coin to, ByteString info);
@Override
public native void channelClosed(PaymentChannelCloseException.CloseReason reason);

View File

@ -20,8 +20,11 @@ import com.google.bitcoin.core.Coin;
import com.google.bitcoin.core.InsufficientMoneyException;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.protobuf.ByteString;
import org.bitcoin.paymentchannel.Protos;
import javax.annotation.Nullable;
/**
* A class implementing this interface supports the basic operations of a payment channel. An implementation is provided
* in {@link PaymentChannelClient}, but alternative implementations are possible. For example, an implementor might
@ -74,13 +77,14 @@ public interface IPaymentChannelClient {
* 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 info Information about this update, used to extend this protocol.
* @throws ValueOutOfRangeException If the size is negative or would pay more than this channel's total value
* ({@link PaymentChannelClientConnection#state()}.getTotalValue())
* @throws IllegalStateException If the channel has been closed or is not yet open
* (see {@link PaymentChannelClientConnection#getChannelOpenFuture()} for the second)
* @return a future that completes when the server acknowledges receipt and acceptance of the payment.
*/
ListenableFuture<Coin> incrementPayment(Coin size) throws ValueOutOfRangeException, IllegalStateException;
ListenableFuture<Coin> 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

View File

@ -468,8 +468,27 @@ public class PaymentChannelClient implements IPaymentChannelClient {
* (see {@link PaymentChannelClientConnection#getChannelOpenFuture()} for the second)
* @return a future that completes when the server acknowledges receipt and acceptance of the payment.
*/
@Override
public ListenableFuture<Coin> incrementPayment(Coin size) throws ValueOutOfRangeException, IllegalStateException {
return incrementPayment(size, ByteString.EMPTY);
}
/**
* Increments the total value which we pay the server. Note that the amount of money sent may not be the same as the
* amount of money actually requested. It can be larger if the amount left over in the channel would be too small to
* be accepted by the Bitcoin network. ValueOutOfRangeException will be thrown, however, if there's not enough money
* left in the channel to make the payment at all. Only one payment can be in-flight at once. You have to ensure
* 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 info Information about this update, used to extend this protocol.
* @throws ValueOutOfRangeException If the size is negative or would pay more than this channel's total value
* ({@link PaymentChannelClientConnection#state()}.getTotalValue())
* @throws IllegalStateException If the channel has been closed or is not yet open
* (see {@link PaymentChannelClientConnection#getChannelOpenFuture()} for the second)
* @return a future that completes when the server acknowledges receipt and acceptance of the payment.
*/
@Override
public ListenableFuture<Coin> incrementPayment(Coin size, @Nullable ByteString info) throws ValueOutOfRangeException, IllegalStateException {
lock.lock();
try {
if (state() == null || !connectionOpen || step != InitStep.CHANNEL_OPEN)
@ -481,6 +500,7 @@ public class PaymentChannelClient implements IPaymentChannelClient {
Protos.UpdatePayment.Builder updatePaymentBuilder = Protos.UpdatePayment.newBuilder()
.setSignature(ByteString.copyFrom(payment.signature.encodeToBitcoin()))
.setClientChangeValue(state.getValueRefunded().value);
if (info != null) updatePaymentBuilder.setInfo(info);
increasePaymentFuture = SettableFuture.create();
increasePaymentFuture.addListener(new Runnable() {

View File

@ -26,6 +26,7 @@ import com.google.bitcoin.net.ProtobufParser;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import com.google.protobuf.ByteString;
import org.bitcoin.paymentchannel.Protos;
import java.io.IOException;
@ -120,7 +121,7 @@ public class PaymentChannelClientConnection {
* an error before the channel has reached the open state.</p>
*
* <p>After this future completes successfully, you may call
* {@link PaymentChannelClientConnection#incrementPayment(Coin)} to begin paying the server.</p>
* {@link PaymentChannelClientConnection#incrementPayment(Coin)} or {@link PaymentChannelClientConnection#incrementPayment(Coin, com.google.protobuf.ByteString)} to begin paying the server.</p>
*/
public ListenableFuture<PaymentChannelClientConnection> getChannelOpenFuture() {
return channelOpenFuture;
@ -136,7 +137,20 @@ public class PaymentChannelClientConnection {
* (see {@link PaymentChannelClientConnection#getChannelOpenFuture()} for the second)
*/
public ListenableFuture<Coin> incrementPayment(Coin size) throws ValueOutOfRangeException, IllegalStateException {
return channelClient.incrementPayment(size);
return channelClient.incrementPayment(size, ByteString.EMPTY);
}
/**
* Increments the total value which we pay the server.
*
* @param size How many satoshis to increment the payment by (note: not the new total).
* @param info Information about this payment increment, used to extend this protocol.
* @throws ValueOutOfRangeException If the size is negative or would pay more than this channel's total value
* ({@link PaymentChannelClientConnection#state()}.getTotalValue())
* @throws IllegalStateException If the channel has been closed or is not yet open
* (see {@link PaymentChannelClientConnection#getChannelOpenFuture()} for the second)
*/
public ListenableFuture<Coin> incrementPayment(Coin size, ByteString info) throws ValueOutOfRangeException, IllegalStateException {
return channelClient.incrementPayment(size, info);
}
/**

View File

@ -100,8 +100,9 @@ public class PaymentChannelServer {
*
* @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 info Information about this payment increase, used to extend this protocol.
*/
public void paymentIncrease(Coin by, Coin to);
public void paymentIncrease(Coin by, Coin to, @Nullable ByteString info);
}
private final ServerConnection conn;
@ -314,8 +315,10 @@ public class PaymentChannelServer {
boolean stillUsable = state.incrementPayment(refundSize, msg.getSignature().toByteArray());
Coin bestPaymentChange = state.getBestValueToMe().subtract(lastBestPayment);
if (bestPaymentChange.signum() > 0)
conn.paymentIncrease(bestPaymentChange, state.getBestValueToMe());
if (bestPaymentChange.signum() > 0) {
ByteString info = (msg.hasInfo()) ? msg.getInfo() : null;
conn.paymentIncrease(bestPaymentChange, state.getBestValueToMe(), info);
}
if (sendAck) {
Protos.TwoWayChannelMessage.Builder ack = Protos.TwoWayChannelMessage.newBuilder();

View File

@ -25,6 +25,7 @@ import com.google.bitcoin.net.NioServer;
import com.google.bitcoin.net.ProtobufParser;
import com.google.bitcoin.net.StreamParserFactory;
import com.google.protobuf.ByteString;
import org.bitcoin.paymentchannel.Protos;
import javax.annotation.Nullable;
@ -82,8 +83,8 @@ public class PaymentChannelServerListener {
eventHandler.channelOpen(contractHash);
}
@Override public void paymentIncrease(Coin by, Coin to) {
eventHandler.paymentIncrease(by, to);
@Override public void paymentIncrease(Coin by, Coin to, @Nullable ByteString info) {
eventHandler.paymentIncrease(by, to, info);
}
});

View File

@ -20,6 +20,7 @@ import com.google.bitcoin.core.Coin;
import com.google.bitcoin.core.Sha256Hash;
import com.google.bitcoin.net.ProtobufParser;
import com.google.protobuf.ByteString;
import org.bitcoin.paymentchannel.Protos;
import javax.annotation.Nullable;
@ -69,8 +70,9 @@ public abstract class ServerConnectionEventHandler {
*
* @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 info Information about this payment increase, used to extend this protocol.
*/
public abstract void paymentIncrease(Coin by, Coin to);
public abstract void paymentIncrease(Coin by, Coin to, ByteString info);
/**
* <p>Called when the channel was closed for some reason. May be called without a call to

View File

@ -6582,6 +6582,24 @@ public final class Protos {
* </pre>
*/
com.google.protobuf.ByteString getSignature();
// optional bytes info = 3;
/**
* <code>optional bytes info = 3;</code>
*
* <pre>
* Information about the this update. Used to extend this protocol.
* </pre>
*/
boolean hasInfo();
/**
* <code>optional bytes info = 3;</code>
*
* <pre>
* Information about the this update. Used to extend this protocol.
* </pre>
*/
com.google.protobuf.ByteString getInfo();
}
/**
* Protobuf type {@code paymentchannels.UpdatePayment}
@ -6660,6 +6678,11 @@ public final class Protos {
signature_ = input.readBytes();
break;
}
case 26: {
bitField0_ |= 0x00000004;
info_ = input.readBytes();
break;
}
}
}
} catch (com.google.protobuf.InvalidProtocolBufferException e) {
@ -6756,9 +6779,34 @@ public final class Protos {
return signature_;
}
// optional bytes info = 3;
public static final int INFO_FIELD_NUMBER = 3;
private com.google.protobuf.ByteString info_;
/**
* <code>optional bytes info = 3;</code>
*
* <pre>
* Information about the this update. Used to extend this protocol.
* </pre>
*/
public boolean hasInfo() {
return ((bitField0_ & 0x00000004) == 0x00000004);
}
/**
* <code>optional bytes info = 3;</code>
*
* <pre>
* Information about the this update. Used to extend this protocol.
* </pre>
*/
public com.google.protobuf.ByteString getInfo() {
return info_;
}
private void initFields() {
clientChangeValue_ = 0L;
signature_ = com.google.protobuf.ByteString.EMPTY;
info_ = com.google.protobuf.ByteString.EMPTY;
}
private byte memoizedIsInitialized = -1;
public final boolean isInitialized() {
@ -6786,6 +6834,9 @@ public final class Protos {
if (((bitField0_ & 0x00000002) == 0x00000002)) {
output.writeBytes(2, signature_);
}
if (((bitField0_ & 0x00000004) == 0x00000004)) {
output.writeBytes(3, info_);
}
getUnknownFields().writeTo(output);
}
@ -6803,6 +6854,10 @@ public final class Protos {
size += com.google.protobuf.CodedOutputStream
.computeBytesSize(2, signature_);
}
if (((bitField0_ & 0x00000004) == 0x00000004)) {
size += com.google.protobuf.CodedOutputStream
.computeBytesSize(3, info_);
}
size += getUnknownFields().getSerializedSize();
memoizedSerializedSize = size;
return size;
@ -6939,6 +6994,8 @@ public final class Protos {
bitField0_ = (bitField0_ & ~0x00000001);
signature_ = com.google.protobuf.ByteString.EMPTY;
bitField0_ = (bitField0_ & ~0x00000002);
info_ = com.google.protobuf.ByteString.EMPTY;
bitField0_ = (bitField0_ & ~0x00000004);
return this;
}
@ -6975,6 +7032,10 @@ public final class Protos {
to_bitField0_ |= 0x00000002;
}
result.signature_ = signature_;
if (((from_bitField0_ & 0x00000004) == 0x00000004)) {
to_bitField0_ |= 0x00000004;
}
result.info_ = info_;
result.bitField0_ = to_bitField0_;
onBuilt();
return result;
@ -6997,6 +7058,9 @@ public final class Protos {
if (other.hasSignature()) {
setSignature(other.getSignature());
}
if (other.hasInfo()) {
setInfo(other.getInfo());
}
this.mergeUnknownFields(other.getUnknownFields());
return this;
}
@ -7149,6 +7213,58 @@ public final class Protos {
return this;
}
// optional bytes info = 3;
private com.google.protobuf.ByteString info_ = com.google.protobuf.ByteString.EMPTY;
/**
* <code>optional bytes info = 3;</code>
*
* <pre>
* Information about the this update. Used to extend this protocol.
* </pre>
*/
public boolean hasInfo() {
return ((bitField0_ & 0x00000004) == 0x00000004);
}
/**
* <code>optional bytes info = 3;</code>
*
* <pre>
* Information about the this update. Used to extend this protocol.
* </pre>
*/
public com.google.protobuf.ByteString getInfo() {
return info_;
}
/**
* <code>optional bytes info = 3;</code>
*
* <pre>
* Information about the this update. Used to extend this protocol.
* </pre>
*/
public Builder setInfo(com.google.protobuf.ByteString value) {
if (value == null) {
throw new NullPointerException();
}
bitField0_ |= 0x00000004;
info_ = value;
onChanged();
return this;
}
/**
* <code>optional bytes info = 3;</code>
*
* <pre>
* Information about the this update. Used to extend this protocol.
* </pre>
*/
public Builder clearInfo() {
bitField0_ = (bitField0_ & ~0x00000004);
info_ = getDefaultInstance().getInfo();
onChanged();
return this;
}
// @@protoc_insertion_point(builder_scope:paymentchannels.UpdatePayment)
}
@ -8624,18 +8740,18 @@ public final class Protos {
"d\022\024\n\014multisig_key\030\001 \002(\014\022\n\n\002tx\030\002 \002(\014\"!\n\014R" +
"eturnRefund\022\021\n\tsignature\030\001 \002(\014\"V\n\017Provid" +
"eContract\022\n\n\002tx\030\001 \002(\014\0227\n\017initial_payment",
"\030\002 \002(\0132\036.paymentchannels.UpdatePayment\"?" +
"\030\002 \002(\0132\036.paymentchannels.UpdatePayment\"M" +
"\n\rUpdatePayment\022\033\n\023client_change_value\030\001" +
" \002(\004\022\021\n\tsignature\030\002 \002(\014\"\030\n\nSettlement\022\n\n" +
"\002tx\030\003 \002(\014\"\246\002\n\005Error\0225\n\004code\030\001 \001(\0162 .paym" +
"entchannels.Error.ErrorCode:\005OTHER\022\023\n\013ex" +
"planation\030\002 \001(\t\022\026\n\016expected_value\030\003 \001(\004\"" +
"\270\001\n\tErrorCode\022\013\n\007TIMEOUT\020\001\022\020\n\014SYNTAX_ERR" +
"OR\020\002\022\031\n\025NO_ACCEPTABLE_VERSION\020\003\022\023\n\017BAD_T" +
"RANSACTION\020\004\022\031\n\025TIME_WINDOW_TOO_LARGE\020\005\022" +
"\033\n\027CHANNEL_VALUE_TOO_LARGE\020\006\022\031\n\025MIN_PAYM",
"ENT_TOO_LARGE\020\007\022\t\n\005OTHER\020\010B$\n\032org.bitcoi" +
"n.paymentchannelB\006Protos"
" \002(\004\022\021\n\tsignature\030\002 \002(\014\022\014\n\004info\030\003 \001(\014\"\030\n" +
"\nSettlement\022\n\n\002tx\030\003 \002(\014\"\246\002\n\005Error\0225\n\004cod" +
"e\030\001 \001(\0162 .paymentchannels.Error.ErrorCod" +
"e:\005OTHER\022\023\n\013explanation\030\002 \001(\t\022\026\n\016expecte" +
"d_value\030\003 \001(\004\"\270\001\n\tErrorCode\022\013\n\007TIMEOUT\020\001" +
"\022\020\n\014SYNTAX_ERROR\020\002\022\031\n\025NO_ACCEPTABLE_VERS" +
"ION\020\003\022\023\n\017BAD_TRANSACTION\020\004\022\031\n\025TIME_WINDO" +
"W_TOO_LARGE\020\005\022\033\n\027CHANNEL_VALUE_TOO_LARGE",
"\020\006\022\031\n\025MIN_PAYMENT_TOO_LARGE\020\007\022\t\n\005OTHER\020\010" +
"B$\n\032org.bitcoin.paymentchannelB\006Protos"
};
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
@ -8689,7 +8805,7 @@ public final class Protos {
internal_static_paymentchannels_UpdatePayment_fieldAccessorTable = new
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
internal_static_paymentchannels_UpdatePayment_descriptor,
new java.lang.String[] { "ClientChangeValue", "Signature", });
new java.lang.String[] { "ClientChangeValue", "Signature", "Info", });
internal_static_paymentchannels_Settlement_descriptor =
getDescriptor().getMessageTypes().get(8);
internal_static_paymentchannels_Settlement_fieldAccessorTable = new

View File

@ -209,6 +209,9 @@ message UpdatePayment {
// the primary's refund output and thus the secondary is free to do what they wish with their
// part of the multisig output.
required bytes signature = 2;
// Information about this update. Used to extend this protocol.
optional bytes info = 3;
}
message Settlement {

View File

@ -117,7 +117,7 @@ public class ChannelConnectionTest extends TestWithWallet {
// Test with network code and without any issues. We'll broadcast two txns: multisig contract and settle transaction.
final SettableFuture<ListenableFuture<PaymentChannelServerState>> serverCloseFuture = SettableFuture.create();
final SettableFuture<Sha256Hash> channelOpenFuture = SettableFuture.create();
final BlockingQueue<Coin> q = new LinkedBlockingQueue<Coin>();
final BlockingQueue<ChannelTestUtils.UpdatePair> q = new LinkedBlockingQueue<ChannelTestUtils.UpdatePair>();
final PaymentChannelServerListener server = new PaymentChannelServerListener(mockBroadcaster, serverWallet, 30, COIN,
new PaymentChannelServerListener.HandlerFactory() {
@Nullable
@ -130,8 +130,8 @@ public class ChannelConnectionTest extends TestWithWallet {
}
@Override
public void paymentIncrease(Coin by, Coin to) {
q.add(to);
public void paymentIncrease(Coin by, Coin to, ByteString info) {
q.add(new ChannelTestUtils.UpdatePair(to, info));
}
@Override
@ -171,16 +171,13 @@ public class ChannelConnectionTest extends TestWithWallet {
Thread.sleep(1250); // No timeouts once the channel is open
Coin amount = client.state().getValueSpent();
assertEquals(amount, q.take());
client.incrementPayment(CENT).get();
amount = amount.add(CENT);
assertEquals(amount, q.take());
client.incrementPayment(CENT).get();
amount = amount.add(CENT);
assertEquals(amount, q.take());
client.incrementPayment(CENT).get();
amount = amount.add(CENT);
assertEquals(amount, q.take());
q.take().assertPair(amount, ByteString.EMPTY);
ByteString[] infos = new ByteString[]{ByteString.EMPTY, ByteString.copyFromUtf8("one"),ByteString.copyFromUtf8("two")};
for (ByteString info : infos) {
client.incrementPayment(CENT, info).get();
amount = amount.add(CENT);
q.take().assertPair(amount, info);
}
latch.await();
StoredPaymentChannelServerStates channels = (StoredPaymentChannelServerStates)serverWallet.getExtensions().get(StoredPaymentChannelServerStates.EXTENSION_ID);
@ -301,7 +298,7 @@ public class ChannelConnectionTest extends TestWithWallet {
Coin amount = minPayment.add(CENT);
client.incrementPayment(CENT);
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.UPDATE_PAYMENT));
assertEquals(amount, pair.serverRecorder.q.take());
assertEquals(amount, ((ChannelTestUtils.UpdatePair)pair.serverRecorder.q.take()).amount);
server.close();
server.connectionClosed();
client.receiveMessage(pair.serverRecorder.checkNextMsg(MessageType.PAYMENT_ACK));

View File

@ -5,8 +5,10 @@ import com.google.bitcoin.core.Sha256Hash;
import com.google.bitcoin.core.TransactionBroadcaster;
import com.google.bitcoin.core.Wallet;
import com.google.protobuf.ByteString;
import org.bitcoin.paymentchannel.Protos;
import javax.annotation.Nullable;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
@ -35,8 +37,8 @@ public class ChannelTestUtils {
}
@Override
public void paymentIncrease(Coin by, Coin to) {
q.add(to);
public void paymentIncrease(Coin by, Coin to, @Nullable ByteString info) {
q.add(new UpdatePair(to, info));
}
public Protos.TwoWayChannelMessage getNextMsg() throws InterruptedException {
@ -50,7 +52,7 @@ public class ChannelTestUtils {
}
public void checkTotalPayment(Coin valueSoFar) throws InterruptedException {
Coin lastSeen = (Coin) q.take();
Coin lastSeen = ((UpdatePair) q.take()).amount;
assertEquals(lastSeen, valueSoFar);
}
}
@ -112,4 +114,40 @@ public class ChannelTestUtils {
pair.clientRecorder = new RecordingClientConnection();
return pair;
}
public static class UpdatePair {
public Coin amount;
public ByteString info;
public UpdatePair(Coin amount, ByteString info) {
this.amount = amount;
this.info = info;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UpdatePair that = (UpdatePair) o;
if (amount != null ? !amount.equals(that.amount) : that.amount != null) return false;
if (info != null ? !info.equals(that.info) : that.info != null) return false;
return true;
}
@Override
public int hashCode() {
int result = amount != null ? amount.hashCode() : 0;
result = 31 * result + (info != null ? info.hashCode() : 0);
return result;
}
public void assertPair(Coin amount, ByteString info) {
assertEquals(amount, this.amount);
assertEquals(info, this.info);
}
}
}

View File

@ -28,6 +28,7 @@ import com.google.bitcoin.protocols.channels.*;
import com.google.bitcoin.utils.BriefLogFormatter;
import com.google.common.collect.ImmutableList;
import com.google.protobuf.ByteString;
import org.slf4j.LoggerFactory;
import java.io.File;
@ -101,7 +102,7 @@ public class ExamplePaymentChannelServer implements PaymentChannelServerListener
}
@Override
public void paymentIncrease(Coin by, Coin to) {
public void paymentIncrease(Coin by, Coin to, ByteString info) {
log.info("Client {} paid increased payment by {} for a total of " + to.toString(), clientAddress, by);
}