3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-01-31 15:22:16 +00:00

Payment channels: protocol tweak - when the client sends a CLOSE, the server sends a CLOSE back that contains the final negotiated contract, so it can be inserted into the wallet without needing to wait for a network broadcast (this is useful if the client does not have internet connectivity at that point).

This commit is contained in:
Mike Hearn 2013-10-04 17:10:42 +02:00
parent b9aa1e4d1e
commit 6342af0913
9 changed files with 830 additions and 91 deletions

View File

@ -91,7 +91,9 @@ public class PaymentChannelClient {
WAITING_FOR_INITIATE,
WAITING_FOR_REFUND_RETURN,
WAITING_FOR_CHANNEL_OPEN,
CHANNEL_OPEN
CHANNEL_OPEN,
WAITING_FOR_CHANNEL_CLOSE,
CHANNEL_CLOSED,
}
@GuardedBy("lock") private InitStep step = InitStep.WAITING_FOR_CONNECTION_OPEN;
@ -253,7 +255,7 @@ public class PaymentChannelClient {
receiveChannelOpen();
return;
case CLOSE:
conn.destroyConnection(CloseReason.SERVER_REQUESTED_CLOSE);
receiveClose(msg);
return;
case ERROR:
checkState(msg.hasError());
@ -290,6 +292,26 @@ public class PaymentChannelClient {
}
}
@GuardedBy("lock")
private void receiveClose(Protos.TwoWayChannelMessage msg) throws VerificationException {
checkState(lock.isHeldByCurrentThread());
if (msg.hasClose()) {
Transaction closeTx = new Transaction(wallet.getParams(), msg.getClose().getTx().toByteArray());
// TODO: set source
if (state != null && state().isCloseTransaction(closeTx)) {
// The wallet has a listener on it that the state object will use to do the right thing at this
// point (like watching it for confirmations). The tx has been checked by now for syntactical validity
// and that it correctly spends the multisig contract.
wallet.receivePending(closeTx, null);
}
}
if (step == InitStep.WAITING_FOR_CHANNEL_CLOSE)
conn.destroyConnection(CloseReason.CLIENT_REQUESTED_CLOSE);
else
conn.destroyConnection(CloseReason.SERVER_REQUESTED_CLOSE);
step = InitStep.CHANNEL_CLOSED;
}
/**
* <p>Called when the connection terminates. Notifies the {@link StoredClientChannel} object that we can attempt to
* resume this channel in the future and stops generating messages for the server.</p>
@ -327,10 +349,11 @@ public class PaymentChannelClient {
lock.lock();
try {
checkState(connectionOpen);
step = InitStep.WAITING_FOR_CHANNEL_CLOSE;
log.info("Sending a CLOSE message to the server and waiting for response indicating successful propagation.");
conn.sendToServer(Protos.TwoWayChannelMessage.newBuilder()
.setType(Protos.TwoWayChannelMessage.MessageType.CLOSE)
.build());
conn.destroyConnection(CloseReason.CLIENT_REQUESTED_CLOSE);
} finally {
lock.unlock();
}

View File

@ -123,8 +123,12 @@ public class PaymentChannelClientState {
initWalletListeners();
}
private boolean isCloseTransaction(Transaction tx) {
/**
* Returns true if the tx is a valid close transaction.
*/
public boolean isCloseTransaction(Transaction tx) {
try {
tx.verify();
tx.getInput(0).verify(multisigContract.getOutput(0));
return true;
} catch (VerificationException e) {

View File

@ -3,6 +3,8 @@ package com.google.bitcoin.protocols.channels;
import com.google.bitcoin.core.*;
import com.google.bitcoin.protocols.channels.PaymentChannelCloseException.CloseReason;
import com.google.bitcoin.utils.Threading;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.protobuf.ByteString;
import net.jcip.annotations.GuardedBy;
import org.bitcoin.paymentchannel.Protos;
@ -304,9 +306,31 @@ public class PaymentChannelServer {
case CLOSE:
log.info("Got CLOSE message, closing channel");
connectionClosing = true;
if (state != null)
state.close();
if (state != null) {
Futures.addCallback(state.close(), new FutureCallback<Transaction>() {
@Override
public void onSuccess(Transaction result) {
// Send the successfully accepted transaction back to the client.
final Protos.TwoWayChannelMessage.Builder msg = Protos.TwoWayChannelMessage.newBuilder();
msg.setType(Protos.TwoWayChannelMessage.MessageType.CLOSE);
if (result != null) {
// Result can be null on various error paths, like if we never actually opened
// properly and so on.
msg.getCloseBuilder().setTx(ByteString.copyFrom(result.bitcoinSerialize()));
}
conn.sendToClient(msg.build());
conn.destroyConnection(CloseReason.CLIENT_REQUESTED_CLOSE);
}
@Override
public void onFailure(Throwable t) {
log.error("Failed to broadcast close TX", t);
conn.destroyConnection(CloseReason.CLIENT_REQUESTED_CLOSE);
}
});
} else {
conn.destroyConnection(CloseReason.CLIENT_REQUESTED_CLOSE);
}
return;
case ERROR:
checkState(msg.hasError());
@ -407,9 +431,9 @@ public class PaymentChannelServer {
lock.lock();
try {
if (connectionOpen && !connectionClosing) {
conn.sendToClient(Protos.TwoWayChannelMessage.newBuilder()
.setType(Protos.TwoWayChannelMessage.MessageType.CLOSE)
.build());
final Protos.TwoWayChannelMessage.Builder msg = Protos.TwoWayChannelMessage.newBuilder();
msg.setType(Protos.TwoWayChannelMessage.MessageType.CLOSE);
conn.sendToClient(msg.build());
conn.destroyConnection(CloseReason.SERVER_REQUESTED_CLOSE);
}
} finally {

View File

@ -344,7 +344,7 @@ public class PaymentChannelServerState {
tx.getInput(0).setScriptSig(scriptSig);
}
final SettableFuture<PaymentChannelServerState> closedFuture = SettableFuture.create();
final SettableFuture<Transaction> closedFuture = SettableFuture.create();
/**
* <p>Closes this channel and broadcasts the highest value payment transaction on the network.</p>
*
@ -355,11 +355,12 @@ public class PaymentChannelServerState {
* simply set the state to {@link State#CLOSED} and let the client handle getting its refund transaction confirmed.
* </p>
*
* @return a future which completes when the provided multisig contract successfully broadcasts, or throws if the broadcast fails for some reason
* Note that if the network simply rejects the transaction, this future will never complete, a timeout should be used.
* @throws ValueOutOfRangeException If the payment transaction would have cost more in fees to spend than it was worth
* @return a future which completes when the provided multisig contract successfully broadcasts, or throws if the
* broadcast fails for some reason. Note that if the network simply rejects the transaction, this future
* will never complete, a timeout should be used.
* @throws ValueOutOfRangeException If the payment tx would have cost more in fees to spend than it is worth.
*/
public synchronized ListenableFuture<PaymentChannelServerState> close() throws ValueOutOfRangeException {
public synchronized ListenableFuture<Transaction> close() throws ValueOutOfRangeException {
if (storedServerChannel != null) {
StoredServerChannel temp = storedServerChannel;
storedServerChannel = null;
@ -371,11 +372,13 @@ public class PaymentChannelServerState {
}
if (state.ordinal() < State.READY.ordinal()) {
log.error("Attempt to close channel in state " + state);
state = State.CLOSED;
closedFuture.set(this);
closedFuture.set(null);
return closedFuture;
}
if (state != State.READY) {
// TODO: What is this codepath for?
log.warn("Failed attempt to close a channel in state " + state);
return closedFuture;
}
@ -386,7 +389,7 @@ public class PaymentChannelServerState {
// the submission of an initial zero-valued payment during the open phase.
log.warn("Closing channel that never received any payments.");
state = State.CLOSED;
closedFuture.set(this);
closedFuture.set(null);
return closedFuture;
}
Transaction tx = null;
@ -426,7 +429,7 @@ public class PaymentChannelServerState {
@Override public void onSuccess(Transaction transaction) {
log.info("TX {} propagated, channel successfully closed.", transaction.getHash());
state = State.CLOSED;
closedFuture.set(PaymentChannelServerState.this);
closedFuture.set(transaction);
}
@Override public void onFailure(Throwable throwable) {

View File

@ -83,7 +83,6 @@ public class StoredPaymentChannelServerStates implements WalletExtension {
synchronized (channel) {
channel.closeConnectedHandler();
try {
//TODO add event listener to PaymentChannelServerStateManager
channel.getOrCreateState(wallet, broadcaster).close();
} catch (ValueOutOfRangeException e) {
e.printStackTrace();

View File

@ -143,6 +143,20 @@ public final class Protos {
*/
org.bitcoin.paymentchannel.Protos.UpdatePaymentOrBuilder getUpdatePaymentOrBuilder();
// optional .paymentchannels.Close close = 9;
/**
* <code>optional .paymentchannels.Close close = 9;</code>
*/
boolean hasClose();
/**
* <code>optional .paymentchannels.Close close = 9;</code>
*/
org.bitcoin.paymentchannel.Protos.Close getClose();
/**
* <code>optional .paymentchannels.Close close = 9;</code>
*/
org.bitcoin.paymentchannel.Protos.CloseOrBuilder getCloseOrBuilder();
// optional .paymentchannels.Error error = 10;
/**
* <code>optional .paymentchannels.Error error = 10;</code>
@ -316,9 +330,22 @@ public final class Protos {
bitField0_ |= 0x00000080;
break;
}
case 74: {
org.bitcoin.paymentchannel.Protos.Close.Builder subBuilder = null;
if (((bitField0_ & 0x00000100) == 0x00000100)) {
subBuilder = close_.toBuilder();
}
close_ = input.readMessage(org.bitcoin.paymentchannel.Protos.Close.PARSER, extensionRegistry);
if (subBuilder != null) {
subBuilder.mergeFrom(close_);
close_ = subBuilder.buildPartial();
}
bitField0_ |= 0x00000100;
break;
}
case 82: {
org.bitcoin.paymentchannel.Protos.Error.Builder subBuilder = null;
if (((bitField0_ & 0x00000100) == 0x00000100)) {
if (((bitField0_ & 0x00000200) == 0x00000200)) {
subBuilder = error_.toBuilder();
}
error_ = input.readMessage(org.bitcoin.paymentchannel.Protos.Error.PARSER, extensionRegistry);
@ -326,7 +353,7 @@ public final class Protos {
subBuilder.mergeFrom(error_);
error_ = subBuilder.buildPartial();
}
bitField0_ |= 0x00000100;
bitField0_ |= 0x00000200;
break;
}
}
@ -419,10 +446,15 @@ public final class Protos {
* <code>CLOSE = 9;</code>
*
* <pre>
* Note that there are no optional fields set for CLOSE, it is sent by either party to
* indicate that the channel is now closed and no further updates can happen. After this,
* the secondary takes the most recent signature it received in an UPDATE_PAYMENT and
* uses it to create a valid transaction, which it then broadcasts on the network.
* Either side can send this message. If the client sends it to the server, then the server
* takes the most recent signature it received in an UPDATE_PAYMENT and uses it to create a
* valid transaction, which it then broadcasts on the network.
*
* If the server sends it to the client, then it can either be a simple "good bye" message or
* it can include the fully signed close transaction and give it back so the client can
* eventually broadcast it as well, in case the server fails to do so for some reason (and
* more usefully so the client can build on it). In that case the server replies to a CLOSE
* with another CLOSE and then disconnects.
* </pre>
*/
CLOSE(8, 9),
@ -432,7 +464,8 @@ public final class Protos {
* <pre>
* Used to indicate an error condition.
* Both parties should make an effort to send either an ERROR or a CLOSE immediately
* before closing the socket (unless they just received an ERROR or a CLOSE)
* before closing the socket (unless they just received an ERROR or a CLOSE). This is important
* because the protocol may not run over TCP.
* </pre>
*/
ERROR(9, 10),
@ -484,10 +517,15 @@ public final class Protos {
* <code>CLOSE = 9;</code>
*
* <pre>
* Note that there are no optional fields set for CLOSE, it is sent by either party to
* indicate that the channel is now closed and no further updates can happen. After this,
* the secondary takes the most recent signature it received in an UPDATE_PAYMENT and
* uses it to create a valid transaction, which it then broadcasts on the network.
* Either side can send this message. If the client sends it to the server, then the server
* takes the most recent signature it received in an UPDATE_PAYMENT and uses it to create a
* valid transaction, which it then broadcasts on the network.
*
* If the server sends it to the client, then it can either be a simple "good bye" message or
* it can include the fully signed close transaction and give it back so the client can
* eventually broadcast it as well, in case the server fails to do so for some reason (and
* more usefully so the client can build on it). In that case the server replies to a CLOSE
* with another CLOSE and then disconnects.
* </pre>
*/
public static final int CLOSE_VALUE = 9;
@ -497,7 +535,8 @@ public final class Protos {
* <pre>
* Used to indicate an error condition.
* Both parties should make an effort to send either an ERROR or a CLOSE immediately
* before closing the socket (unless they just received an ERROR or a CLOSE)
* before closing the socket (unless they just received an ERROR or a CLOSE). This is important
* because the protocol may not run over TCP.
* </pre>
*/
public static final int ERROR_VALUE = 10;
@ -763,6 +802,28 @@ public final class Protos {
return updatePayment_;
}
// optional .paymentchannels.Close close = 9;
public static final int CLOSE_FIELD_NUMBER = 9;
private org.bitcoin.paymentchannel.Protos.Close close_;
/**
* <code>optional .paymentchannels.Close close = 9;</code>
*/
public boolean hasClose() {
return ((bitField0_ & 0x00000100) == 0x00000100);
}
/**
* <code>optional .paymentchannels.Close close = 9;</code>
*/
public org.bitcoin.paymentchannel.Protos.Close getClose() {
return close_;
}
/**
* <code>optional .paymentchannels.Close close = 9;</code>
*/
public org.bitcoin.paymentchannel.Protos.CloseOrBuilder getCloseOrBuilder() {
return close_;
}
// optional .paymentchannels.Error error = 10;
public static final int ERROR_FIELD_NUMBER = 10;
private org.bitcoin.paymentchannel.Protos.Error error_;
@ -770,7 +831,7 @@ public final class Protos {
* <code>optional .paymentchannels.Error error = 10;</code>
*/
public boolean hasError() {
return ((bitField0_ & 0x00000100) == 0x00000100);
return ((bitField0_ & 0x00000200) == 0x00000200);
}
/**
* <code>optional .paymentchannels.Error error = 10;</code>
@ -794,6 +855,7 @@ public final class Protos {
returnRefund_ = org.bitcoin.paymentchannel.Protos.ReturnRefund.getDefaultInstance();
provideContract_ = org.bitcoin.paymentchannel.Protos.ProvideContract.getDefaultInstance();
updatePayment_ = org.bitcoin.paymentchannel.Protos.UpdatePayment.getDefaultInstance();
close_ = org.bitcoin.paymentchannel.Protos.Close.getDefaultInstance();
error_ = org.bitcoin.paymentchannel.Protos.Error.getDefaultInstance();
}
private byte memoizedIsInitialized = -1;
@ -847,6 +909,12 @@ public final class Protos {
return false;
}
}
if (hasClose()) {
if (!getClose().isInitialized()) {
memoizedIsInitialized = 0;
return false;
}
}
memoizedIsInitialized = 1;
return true;
}
@ -879,6 +947,9 @@ public final class Protos {
output.writeMessage(8, updatePayment_);
}
if (((bitField0_ & 0x00000100) == 0x00000100)) {
output.writeMessage(9, close_);
}
if (((bitField0_ & 0x00000200) == 0x00000200)) {
output.writeMessage(10, error_);
}
getUnknownFields().writeTo(output);
@ -923,6 +994,10 @@ public final class Protos {
.computeMessageSize(8, updatePayment_);
}
if (((bitField0_ & 0x00000100) == 0x00000100)) {
size += com.google.protobuf.CodedOutputStream
.computeMessageSize(9, close_);
}
if (((bitField0_ & 0x00000200) == 0x00000200)) {
size += com.google.protobuf.CodedOutputStream
.computeMessageSize(10, error_);
}
@ -1047,6 +1122,7 @@ public final class Protos {
getReturnRefundFieldBuilder();
getProvideContractFieldBuilder();
getUpdatePaymentFieldBuilder();
getCloseFieldBuilder();
getErrorFieldBuilder();
}
}
@ -1100,12 +1176,18 @@ public final class Protos {
updatePaymentBuilder_.clear();
}
bitField0_ = (bitField0_ & ~0x00000080);
if (closeBuilder_ == null) {
close_ = org.bitcoin.paymentchannel.Protos.Close.getDefaultInstance();
} else {
closeBuilder_.clear();
}
bitField0_ = (bitField0_ & ~0x00000100);
if (errorBuilder_ == null) {
error_ = org.bitcoin.paymentchannel.Protos.Error.getDefaultInstance();
} else {
errorBuilder_.clear();
}
bitField0_ = (bitField0_ & ~0x00000100);
bitField0_ = (bitField0_ & ~0x00000200);
return this;
}
@ -1197,6 +1279,14 @@ public final class Protos {
if (((from_bitField0_ & 0x00000100) == 0x00000100)) {
to_bitField0_ |= 0x00000100;
}
if (closeBuilder_ == null) {
result.close_ = close_;
} else {
result.close_ = closeBuilder_.build();
}
if (((from_bitField0_ & 0x00000200) == 0x00000200)) {
to_bitField0_ |= 0x00000200;
}
if (errorBuilder_ == null) {
result.error_ = error_;
} else {
@ -1242,6 +1332,9 @@ public final class Protos {
if (other.hasUpdatePayment()) {
mergeUpdatePayment(other.getUpdatePayment());
}
if (other.hasClose()) {
mergeClose(other.getClose());
}
if (other.hasError()) {
mergeError(other.getError());
}
@ -1296,6 +1389,12 @@ public final class Protos {
return false;
}
}
if (hasClose()) {
if (!getClose().isInitialized()) {
return false;
}
}
return true;
}
@ -2233,6 +2332,123 @@ public final class Protos {
return updatePaymentBuilder_;
}
// optional .paymentchannels.Close close = 9;
private org.bitcoin.paymentchannel.Protos.Close close_ = org.bitcoin.paymentchannel.Protos.Close.getDefaultInstance();
private com.google.protobuf.SingleFieldBuilder<
org.bitcoin.paymentchannel.Protos.Close, org.bitcoin.paymentchannel.Protos.Close.Builder, org.bitcoin.paymentchannel.Protos.CloseOrBuilder> closeBuilder_;
/**
* <code>optional .paymentchannels.Close close = 9;</code>
*/
public boolean hasClose() {
return ((bitField0_ & 0x00000100) == 0x00000100);
}
/**
* <code>optional .paymentchannels.Close close = 9;</code>
*/
public org.bitcoin.paymentchannel.Protos.Close getClose() {
if (closeBuilder_ == null) {
return close_;
} else {
return closeBuilder_.getMessage();
}
}
/**
* <code>optional .paymentchannels.Close close = 9;</code>
*/
public Builder setClose(org.bitcoin.paymentchannel.Protos.Close value) {
if (closeBuilder_ == null) {
if (value == null) {
throw new NullPointerException();
}
close_ = value;
onChanged();
} else {
closeBuilder_.setMessage(value);
}
bitField0_ |= 0x00000100;
return this;
}
/**
* <code>optional .paymentchannels.Close close = 9;</code>
*/
public Builder setClose(
org.bitcoin.paymentchannel.Protos.Close.Builder builderForValue) {
if (closeBuilder_ == null) {
close_ = builderForValue.build();
onChanged();
} else {
closeBuilder_.setMessage(builderForValue.build());
}
bitField0_ |= 0x00000100;
return this;
}
/**
* <code>optional .paymentchannels.Close close = 9;</code>
*/
public Builder mergeClose(org.bitcoin.paymentchannel.Protos.Close value) {
if (closeBuilder_ == null) {
if (((bitField0_ & 0x00000100) == 0x00000100) &&
close_ != org.bitcoin.paymentchannel.Protos.Close.getDefaultInstance()) {
close_ =
org.bitcoin.paymentchannel.Protos.Close.newBuilder(close_).mergeFrom(value).buildPartial();
} else {
close_ = value;
}
onChanged();
} else {
closeBuilder_.mergeFrom(value);
}
bitField0_ |= 0x00000100;
return this;
}
/**
* <code>optional .paymentchannels.Close close = 9;</code>
*/
public Builder clearClose() {
if (closeBuilder_ == null) {
close_ = org.bitcoin.paymentchannel.Protos.Close.getDefaultInstance();
onChanged();
} else {
closeBuilder_.clear();
}
bitField0_ = (bitField0_ & ~0x00000100);
return this;
}
/**
* <code>optional .paymentchannels.Close close = 9;</code>
*/
public org.bitcoin.paymentchannel.Protos.Close.Builder getCloseBuilder() {
bitField0_ |= 0x00000100;
onChanged();
return getCloseFieldBuilder().getBuilder();
}
/**
* <code>optional .paymentchannels.Close close = 9;</code>
*/
public org.bitcoin.paymentchannel.Protos.CloseOrBuilder getCloseOrBuilder() {
if (closeBuilder_ != null) {
return closeBuilder_.getMessageOrBuilder();
} else {
return close_;
}
}
/**
* <code>optional .paymentchannels.Close close = 9;</code>
*/
private com.google.protobuf.SingleFieldBuilder<
org.bitcoin.paymentchannel.Protos.Close, org.bitcoin.paymentchannel.Protos.Close.Builder, org.bitcoin.paymentchannel.Protos.CloseOrBuilder>
getCloseFieldBuilder() {
if (closeBuilder_ == null) {
closeBuilder_ = new com.google.protobuf.SingleFieldBuilder<
org.bitcoin.paymentchannel.Protos.Close, org.bitcoin.paymentchannel.Protos.Close.Builder, org.bitcoin.paymentchannel.Protos.CloseOrBuilder>(
close_,
getParentForChildren(),
isClean());
close_ = null;
}
return closeBuilder_;
}
// optional .paymentchannels.Error error = 10;
private org.bitcoin.paymentchannel.Protos.Error error_ = org.bitcoin.paymentchannel.Protos.Error.getDefaultInstance();
private com.google.protobuf.SingleFieldBuilder<
@ -2241,7 +2457,7 @@ public final class Protos {
* <code>optional .paymentchannels.Error error = 10;</code>
*/
public boolean hasError() {
return ((bitField0_ & 0x00000100) == 0x00000100);
return ((bitField0_ & 0x00000200) == 0x00000200);
}
/**
* <code>optional .paymentchannels.Error error = 10;</code>
@ -2266,7 +2482,7 @@ public final class Protos {
} else {
errorBuilder_.setMessage(value);
}
bitField0_ |= 0x00000100;
bitField0_ |= 0x00000200;
return this;
}
/**
@ -2280,7 +2496,7 @@ public final class Protos {
} else {
errorBuilder_.setMessage(builderForValue.build());
}
bitField0_ |= 0x00000100;
bitField0_ |= 0x00000200;
return this;
}
/**
@ -2288,7 +2504,7 @@ public final class Protos {
*/
public Builder mergeError(org.bitcoin.paymentchannel.Protos.Error value) {
if (errorBuilder_ == null) {
if (((bitField0_ & 0x00000100) == 0x00000100) &&
if (((bitField0_ & 0x00000200) == 0x00000200) &&
error_ != org.bitcoin.paymentchannel.Protos.Error.getDefaultInstance()) {
error_ =
org.bitcoin.paymentchannel.Protos.Error.newBuilder(error_).mergeFrom(value).buildPartial();
@ -2299,7 +2515,7 @@ public final class Protos {
} else {
errorBuilder_.mergeFrom(value);
}
bitField0_ |= 0x00000100;
bitField0_ |= 0x00000200;
return this;
}
/**
@ -2312,14 +2528,14 @@ public final class Protos {
} else {
errorBuilder_.clear();
}
bitField0_ = (bitField0_ & ~0x00000100);
bitField0_ = (bitField0_ & ~0x00000200);
return this;
}
/**
* <code>optional .paymentchannels.Error error = 10;</code>
*/
public org.bitcoin.paymentchannel.Protos.Error.Builder getErrorBuilder() {
bitField0_ |= 0x00000100;
bitField0_ |= 0x00000200;
onChanged();
return getErrorFieldBuilder().getBuilder();
}
@ -6447,6 +6663,453 @@ public final class Protos {
// @@protoc_insertion_point(class_scope:paymentchannels.UpdatePayment)
}
public interface CloseOrBuilder
extends com.google.protobuf.MessageOrBuilder {
// required bytes tx = 3;
/**
* <code>required bytes tx = 3;</code>
*
* <pre>
* A copy of the fully signed final contract that closes the channel. The client can verify
* the transaction is correct and then commit it to their wallet.
* </pre>
*/
boolean hasTx();
/**
* <code>required bytes tx = 3;</code>
*
* <pre>
* A copy of the fully signed final contract that closes the channel. The client can verify
* the transaction is correct and then commit it to their wallet.
* </pre>
*/
com.google.protobuf.ByteString getTx();
}
/**
* Protobuf type {@code paymentchannels.Close}
*/
public static final class Close extends
com.google.protobuf.GeneratedMessage
implements CloseOrBuilder {
// Use Close.newBuilder() to construct.
private Close(com.google.protobuf.GeneratedMessage.Builder<?> builder) {
super(builder);
this.unknownFields = builder.getUnknownFields();
}
private Close(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); }
private static final Close defaultInstance;
public static Close getDefaultInstance() {
return defaultInstance;
}
public Close getDefaultInstanceForType() {
return defaultInstance;
}
private final com.google.protobuf.UnknownFieldSet unknownFields;
@java.lang.Override
public final com.google.protobuf.UnknownFieldSet
getUnknownFields() {
return this.unknownFields;
}
private Close(
com.google.protobuf.CodedInputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws com.google.protobuf.InvalidProtocolBufferException {
initFields();
int mutable_bitField0_ = 0;
com.google.protobuf.UnknownFieldSet.Builder unknownFields =
com.google.protobuf.UnknownFieldSet.newBuilder();
try {
boolean done = false;
while (!done) {
int tag = input.readTag();
switch (tag) {
case 0:
done = true;
break;
default: {
if (!parseUnknownField(input, unknownFields,
extensionRegistry, tag)) {
done = true;
}
break;
}
case 26: {
bitField0_ |= 0x00000001;
tx_ = input.readBytes();
break;
}
}
}
} catch (com.google.protobuf.InvalidProtocolBufferException e) {
throw e.setUnfinishedMessage(this);
} catch (java.io.IOException e) {
throw new com.google.protobuf.InvalidProtocolBufferException(
e.getMessage()).setUnfinishedMessage(this);
} finally {
this.unknownFields = unknownFields.build();
makeExtensionsImmutable();
}
}
public static final com.google.protobuf.Descriptors.Descriptor
getDescriptor() {
return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_Close_descriptor;
}
protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
internalGetFieldAccessorTable() {
return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_Close_fieldAccessorTable
.ensureFieldAccessorsInitialized(
org.bitcoin.paymentchannel.Protos.Close.class, org.bitcoin.paymentchannel.Protos.Close.Builder.class);
}
public static com.google.protobuf.Parser<Close> PARSER =
new com.google.protobuf.AbstractParser<Close>() {
public Close parsePartialFrom(
com.google.protobuf.CodedInputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws com.google.protobuf.InvalidProtocolBufferException {
return new Close(input, extensionRegistry);
}
};
@java.lang.Override
public com.google.protobuf.Parser<Close> getParserForType() {
return PARSER;
}
private int bitField0_;
// required bytes tx = 3;
public static final int TX_FIELD_NUMBER = 3;
private com.google.protobuf.ByteString tx_;
/**
* <code>required bytes tx = 3;</code>
*
* <pre>
* A copy of the fully signed final contract that closes the channel. The client can verify
* the transaction is correct and then commit it to their wallet.
* </pre>
*/
public boolean hasTx() {
return ((bitField0_ & 0x00000001) == 0x00000001);
}
/**
* <code>required bytes tx = 3;</code>
*
* <pre>
* A copy of the fully signed final contract that closes the channel. The client can verify
* the transaction is correct and then commit it to their wallet.
* </pre>
*/
public com.google.protobuf.ByteString getTx() {
return tx_;
}
private void initFields() {
tx_ = com.google.protobuf.ByteString.EMPTY;
}
private byte memoizedIsInitialized = -1;
public final boolean isInitialized() {
byte isInitialized = memoizedIsInitialized;
if (isInitialized != -1) return isInitialized == 1;
if (!hasTx()) {
memoizedIsInitialized = 0;
return false;
}
memoizedIsInitialized = 1;
return true;
}
public void writeTo(com.google.protobuf.CodedOutputStream output)
throws java.io.IOException {
getSerializedSize();
if (((bitField0_ & 0x00000001) == 0x00000001)) {
output.writeBytes(3, tx_);
}
getUnknownFields().writeTo(output);
}
private int memoizedSerializedSize = -1;
public int getSerializedSize() {
int size = memoizedSerializedSize;
if (size != -1) return size;
size = 0;
if (((bitField0_ & 0x00000001) == 0x00000001)) {
size += com.google.protobuf.CodedOutputStream
.computeBytesSize(3, tx_);
}
size += getUnknownFields().getSerializedSize();
memoizedSerializedSize = size;
return size;
}
private static final long serialVersionUID = 0L;
@java.lang.Override
protected java.lang.Object writeReplace()
throws java.io.ObjectStreamException {
return super.writeReplace();
}
public static org.bitcoin.paymentchannel.Protos.Close parseFrom(
com.google.protobuf.ByteString data)
throws com.google.protobuf.InvalidProtocolBufferException {
return PARSER.parseFrom(data);
}
public static org.bitcoin.paymentchannel.Protos.Close parseFrom(
com.google.protobuf.ByteString data,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws com.google.protobuf.InvalidProtocolBufferException {
return PARSER.parseFrom(data, extensionRegistry);
}
public static org.bitcoin.paymentchannel.Protos.Close parseFrom(byte[] data)
throws com.google.protobuf.InvalidProtocolBufferException {
return PARSER.parseFrom(data);
}
public static org.bitcoin.paymentchannel.Protos.Close parseFrom(
byte[] data,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws com.google.protobuf.InvalidProtocolBufferException {
return PARSER.parseFrom(data, extensionRegistry);
}
public static org.bitcoin.paymentchannel.Protos.Close parseFrom(java.io.InputStream input)
throws java.io.IOException {
return PARSER.parseFrom(input);
}
public static org.bitcoin.paymentchannel.Protos.Close parseFrom(
java.io.InputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws java.io.IOException {
return PARSER.parseFrom(input, extensionRegistry);
}
public static org.bitcoin.paymentchannel.Protos.Close parseDelimitedFrom(java.io.InputStream input)
throws java.io.IOException {
return PARSER.parseDelimitedFrom(input);
}
public static org.bitcoin.paymentchannel.Protos.Close parseDelimitedFrom(
java.io.InputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws java.io.IOException {
return PARSER.parseDelimitedFrom(input, extensionRegistry);
}
public static org.bitcoin.paymentchannel.Protos.Close parseFrom(
com.google.protobuf.CodedInputStream input)
throws java.io.IOException {
return PARSER.parseFrom(input);
}
public static org.bitcoin.paymentchannel.Protos.Close parseFrom(
com.google.protobuf.CodedInputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws java.io.IOException {
return PARSER.parseFrom(input, extensionRegistry);
}
public static Builder newBuilder() { return Builder.create(); }
public Builder newBuilderForType() { return newBuilder(); }
public static Builder newBuilder(org.bitcoin.paymentchannel.Protos.Close prototype) {
return newBuilder().mergeFrom(prototype);
}
public Builder toBuilder() { return newBuilder(this); }
@java.lang.Override
protected Builder newBuilderForType(
com.google.protobuf.GeneratedMessage.BuilderParent parent) {
Builder builder = new Builder(parent);
return builder;
}
/**
* Protobuf type {@code paymentchannels.Close}
*/
public static final class Builder extends
com.google.protobuf.GeneratedMessage.Builder<Builder>
implements org.bitcoin.paymentchannel.Protos.CloseOrBuilder {
public static final com.google.protobuf.Descriptors.Descriptor
getDescriptor() {
return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_Close_descriptor;
}
protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
internalGetFieldAccessorTable() {
return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_Close_fieldAccessorTable
.ensureFieldAccessorsInitialized(
org.bitcoin.paymentchannel.Protos.Close.class, org.bitcoin.paymentchannel.Protos.Close.Builder.class);
}
// Construct using org.bitcoin.paymentchannel.Protos.Close.newBuilder()
private Builder() {
maybeForceBuilderInitialization();
}
private Builder(
com.google.protobuf.GeneratedMessage.BuilderParent parent) {
super(parent);
maybeForceBuilderInitialization();
}
private void maybeForceBuilderInitialization() {
if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
}
}
private static Builder create() {
return new Builder();
}
public Builder clear() {
super.clear();
tx_ = com.google.protobuf.ByteString.EMPTY;
bitField0_ = (bitField0_ & ~0x00000001);
return this;
}
public Builder clone() {
return create().mergeFrom(buildPartial());
}
public com.google.protobuf.Descriptors.Descriptor
getDescriptorForType() {
return org.bitcoin.paymentchannel.Protos.internal_static_paymentchannels_Close_descriptor;
}
public org.bitcoin.paymentchannel.Protos.Close getDefaultInstanceForType() {
return org.bitcoin.paymentchannel.Protos.Close.getDefaultInstance();
}
public org.bitcoin.paymentchannel.Protos.Close build() {
org.bitcoin.paymentchannel.Protos.Close result = buildPartial();
if (!result.isInitialized()) {
throw newUninitializedMessageException(result);
}
return result;
}
public org.bitcoin.paymentchannel.Protos.Close buildPartial() {
org.bitcoin.paymentchannel.Protos.Close result = new org.bitcoin.paymentchannel.Protos.Close(this);
int from_bitField0_ = bitField0_;
int to_bitField0_ = 0;
if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
to_bitField0_ |= 0x00000001;
}
result.tx_ = tx_;
result.bitField0_ = to_bitField0_;
onBuilt();
return result;
}
public Builder mergeFrom(com.google.protobuf.Message other) {
if (other instanceof org.bitcoin.paymentchannel.Protos.Close) {
return mergeFrom((org.bitcoin.paymentchannel.Protos.Close)other);
} else {
super.mergeFrom(other);
return this;
}
}
public Builder mergeFrom(org.bitcoin.paymentchannel.Protos.Close other) {
if (other == org.bitcoin.paymentchannel.Protos.Close.getDefaultInstance()) return this;
if (other.hasTx()) {
setTx(other.getTx());
}
this.mergeUnknownFields(other.getUnknownFields());
return this;
}
public final boolean isInitialized() {
if (!hasTx()) {
return false;
}
return true;
}
public Builder mergeFrom(
com.google.protobuf.CodedInputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws java.io.IOException {
org.bitcoin.paymentchannel.Protos.Close parsedMessage = null;
try {
parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
} catch (com.google.protobuf.InvalidProtocolBufferException e) {
parsedMessage = (org.bitcoin.paymentchannel.Protos.Close) e.getUnfinishedMessage();
throw e;
} finally {
if (parsedMessage != null) {
mergeFrom(parsedMessage);
}
}
return this;
}
private int bitField0_;
// required bytes tx = 3;
private com.google.protobuf.ByteString tx_ = com.google.protobuf.ByteString.EMPTY;
/**
* <code>required bytes tx = 3;</code>
*
* <pre>
* A copy of the fully signed final contract that closes the channel. The client can verify
* the transaction is correct and then commit it to their wallet.
* </pre>
*/
public boolean hasTx() {
return ((bitField0_ & 0x00000001) == 0x00000001);
}
/**
* <code>required bytes tx = 3;</code>
*
* <pre>
* A copy of the fully signed final contract that closes the channel. The client can verify
* the transaction is correct and then commit it to their wallet.
* </pre>
*/
public com.google.protobuf.ByteString getTx() {
return tx_;
}
/**
* <code>required bytes tx = 3;</code>
*
* <pre>
* A copy of the fully signed final contract that closes the channel. The client can verify
* the transaction is correct and then commit it to their wallet.
* </pre>
*/
public Builder setTx(com.google.protobuf.ByteString value) {
if (value == null) {
throw new NullPointerException();
}
bitField0_ |= 0x00000001;
tx_ = value;
onChanged();
return this;
}
/**
* <code>required bytes tx = 3;</code>
*
* <pre>
* A copy of the fully signed final contract that closes the channel. The client can verify
* the transaction is correct and then commit it to their wallet.
* </pre>
*/
public Builder clearTx() {
bitField0_ = (bitField0_ & ~0x00000001);
tx_ = getDefaultInstance().getTx();
onChanged();
return this;
}
// @@protoc_insertion_point(builder_scope:paymentchannels.Close)
}
static {
defaultInstance = new Close(true);
defaultInstance.initFields();
}
// @@protoc_insertion_point(class_scope:paymentchannels.Close)
}
public interface ErrorOrBuilder
extends com.google.protobuf.MessageOrBuilder {
@ -7285,6 +7948,11 @@ public final class Protos {
private static
com.google.protobuf.GeneratedMessage.FieldAccessorTable
internal_static_paymentchannels_UpdatePayment_fieldAccessorTable;
private static com.google.protobuf.Descriptors.Descriptor
internal_static_paymentchannels_Close_descriptor;
private static
com.google.protobuf.GeneratedMessage.FieldAccessorTable
internal_static_paymentchannels_Close_fieldAccessorTable;
private static com.google.protobuf.Descriptors.Descriptor
internal_static_paymentchannels_Error_descriptor;
private static
@ -7300,7 +7968,7 @@ public final class Protos {
static {
java.lang.String[] descriptorData = {
"\n\024paymentchannel.proto\022\017paymentchannels\"" +
"\274\005\n\024TwoWayChannelMessage\022?\n\004type\030\001 \002(\01621" +
"\343\005\n\024TwoWayChannelMessage\022?\n\004type\030\001 \002(\01621" +
".paymentchannels.TwoWayChannelMessage.Me" +
"ssageType\0226\n\016client_version\030\002 \001(\0132\036.paym" +
"entchannels.ClientVersion\0226\n\016server_vers" +
@ -7311,31 +7979,32 @@ public final class Protos {
"\006 \001(\0132\035.paymentchannels.ReturnRefund\022:\n\020",
"provide_contract\030\007 \001(\0132 .paymentchannels" +
".ProvideContract\0226\n\016update_payment\030\010 \001(\013" +
"2\036.paymentchannels.UpdatePayment\022%\n\005erro" +
"r\030\n \001(\0132\026.paymentchannels.Error\"\274\001\n\013Mess" +
"ageType\022\022\n\016CLIENT_VERSION\020\001\022\022\n\016SERVER_VE" +
"RSION\020\002\022\014\n\010INITIATE\020\003\022\022\n\016PROVIDE_REFUND\020" +
"\004\022\021\n\rRETURN_REFUND\020\005\022\024\n\020PROVIDE_CONTRACT" +
"\020\006\022\020\n\014CHANNEL_OPEN\020\007\022\022\n\016UPDATE_PAYMENT\020\010" +
"\022\t\n\005CLOSE\020\t\022\t\n\005ERROR\020\n\"X\n\rClientVersion\022" +
"\r\n\005major\030\001 \002(\005\022\020\n\005minor\030\002 \001(\005:\0010\022&\n\036prev",
"ious_channel_contract_hash\030\003 \001(\014\"0\n\rServ" +
"erVersion\022\r\n\005major\030\001 \002(\005\022\020\n\005minor\030\002 \001(\005:" +
"\0010\"]\n\010Initiate\022\024\n\014multisig_key\030\001 \002(\014\022!\n\031" +
"min_accepted_channel_size\030\002 \002(\004\022\030\n\020expir" +
"e_time_secs\030\003 \002(\004\"1\n\rProvideRefund\022\024\n\014mu" +
"ltisig_key\030\001 \002(\014\022\n\n\002tx\030\002 \002(\014\"!\n\014ReturnRe" +
"fund\022\021\n\tsignature\030\001 \002(\014\"\035\n\017ProvideContra" +
"ct\022\n\n\002tx\030\001 \002(\014\"?\n\rUpdatePayment\022\033\n\023clien" +
"t_change_value\030\001 \002(\004\022\021\n\tsignature\030\002 \002(\014\"" +
"\363\001\n\005Error\0225\n\004code\030\001 \001(\0162 .paymentchannel",
"s.Error.ErrorCode:\005OTHER\022\023\n\013explanation\030" +
"\002 \001(\t\"\235\001\n\tErrorCode\022\013\n\007TIMEOUT\020\001\022\020\n\014SYNT" +
"AX_ERROR\020\002\022\031\n\025NO_ACCEPTABLE_VERSION\020\003\022\023\n" +
"\017BAD_TRANSACTION\020\004\022\031\n\025TIME_WINDOW_TOO_LA" +
"RGE\020\005\022\033\n\027CHANNEL_VALUE_TOO_LARGE\020\006\022\t\n\005OT" +
"HER\020\007B$\n\032org.bitcoin.paymentchannelB\006Pro" +
"tos"
"2\036.paymentchannels.UpdatePayment\022%\n\005clos" +
"e\030\t \001(\0132\026.paymentchannels.Close\022%\n\005error" +
"\030\n \001(\0132\026.paymentchannels.Error\"\274\001\n\013Messa" +
"geType\022\022\n\016CLIENT_VERSION\020\001\022\022\n\016SERVER_VER" +
"SION\020\002\022\014\n\010INITIATE\020\003\022\022\n\016PROVIDE_REFUND\020\004" +
"\022\021\n\rRETURN_REFUND\020\005\022\024\n\020PROVIDE_CONTRACT\020" +
"\006\022\020\n\014CHANNEL_OPEN\020\007\022\022\n\016UPDATE_PAYMENT\020\010\022" +
"\t\n\005CLOSE\020\t\022\t\n\005ERROR\020\n\"X\n\rClientVersion\022\r",
"\n\005major\030\001 \002(\005\022\020\n\005minor\030\002 \001(\005:\0010\022&\n\036previ" +
"ous_channel_contract_hash\030\003 \001(\014\"0\n\rServe" +
"rVersion\022\r\n\005major\030\001 \002(\005\022\020\n\005minor\030\002 \001(\005:\001" +
"0\"]\n\010Initiate\022\024\n\014multisig_key\030\001 \002(\014\022!\n\031m" +
"in_accepted_channel_size\030\002 \002(\004\022\030\n\020expire" +
"_time_secs\030\003 \002(\004\"1\n\rProvideRefund\022\024\n\014mul" +
"tisig_key\030\001 \002(\014\022\n\n\002tx\030\002 \002(\014\"!\n\014ReturnRef" +
"und\022\021\n\tsignature\030\001 \002(\014\"\035\n\017ProvideContrac" +
"t\022\n\n\002tx\030\001 \002(\014\"?\n\rUpdatePayment\022\033\n\023client" +
"_change_value\030\001 \002(\004\022\021\n\tsignature\030\002 \002(\014\"\023",
"\n\005Close\022\n\n\002tx\030\003 \002(\014\"\363\001\n\005Error\0225\n\004code\030\001 " +
"\001(\0162 .paymentchannels.Error.ErrorCode:\005O" +
"THER\022\023\n\013explanation\030\002 \001(\t\"\235\001\n\tErrorCode\022" +
"\013\n\007TIMEOUT\020\001\022\020\n\014SYNTAX_ERROR\020\002\022\031\n\025NO_ACC" +
"EPTABLE_VERSION\020\003\022\023\n\017BAD_TRANSACTION\020\004\022\031" +
"\n\025TIME_WINDOW_TOO_LARGE\020\005\022\033\n\027CHANNEL_VAL" +
"UE_TOO_LARGE\020\006\022\t\n\005OTHER\020\007B$\n\032org.bitcoin" +
".paymentchannelB\006Protos"
};
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
@ -7347,7 +8016,7 @@ public final class Protos {
internal_static_paymentchannels_TwoWayChannelMessage_fieldAccessorTable = new
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
internal_static_paymentchannels_TwoWayChannelMessage_descriptor,
new java.lang.String[] { "Type", "ClientVersion", "ServerVersion", "Initiate", "ProvideRefund", "ReturnRefund", "ProvideContract", "UpdatePayment", "Error", });
new java.lang.String[] { "Type", "ClientVersion", "ServerVersion", "Initiate", "ProvideRefund", "ReturnRefund", "ProvideContract", "UpdatePayment", "Close", "Error", });
internal_static_paymentchannels_ClientVersion_descriptor =
getDescriptor().getMessageTypes().get(1);
internal_static_paymentchannels_ClientVersion_fieldAccessorTable = new
@ -7390,8 +8059,14 @@ public final class Protos {
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
internal_static_paymentchannels_UpdatePayment_descriptor,
new java.lang.String[] { "ClientChangeValue", "Signature", });
internal_static_paymentchannels_Error_descriptor =
internal_static_paymentchannels_Close_descriptor =
getDescriptor().getMessageTypes().get(8);
internal_static_paymentchannels_Close_fieldAccessorTable = new
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
internal_static_paymentchannels_Close_descriptor,
new java.lang.String[] { "Tx", });
internal_static_paymentchannels_Error_descriptor =
getDescriptor().getMessageTypes().get(9);
internal_static_paymentchannels_Error_fieldAccessorTable = new
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
internal_static_paymentchannels_Error_descriptor,

View File

@ -47,15 +47,21 @@ message TwoWayChannelMessage {
// message.
CHANNEL_OPEN = 7;
UPDATE_PAYMENT = 8;
// Note that there are no optional fields set for CLOSE, it is sent by either party to
// indicate that the channel is now closed and no further updates can happen. After this,
// the secondary takes the most recent signature it received in an UPDATE_PAYMENT and
// uses it to create a valid transaction, which it then broadcasts on the network.
// Either side can send this message. If the client sends it to the server, then the server
// takes the most recent signature it received in an UPDATE_PAYMENT and uses it to create a
// valid transaction, which it then broadcasts on the network.
//
// If the server sends it to the client, then it can either be a simple "good bye" message or
// it can include the fully signed close transaction and give it back so the client can
// eventually broadcast it as well, in case the server fails to do so for some reason (and
// more usefully so the client can build on it). In that case the server replies to a CLOSE
// with another CLOSE and then disconnects.
CLOSE = 9;
// Used to indicate an error condition.
// Both parties should make an effort to send either an ERROR or a CLOSE immediately
// before closing the socket (unless they just received an ERROR or a CLOSE)
// before closing the socket (unless they just received an ERROR or a CLOSE). This is important
// because the protocol may not run over TCP.
ERROR = 10;
};
@ -72,6 +78,7 @@ message TwoWayChannelMessage {
optional ReturnRefund return_refund = 6;
optional ProvideContract provide_contract = 7;
optional UpdatePayment update_payment = 8;
optional Close close = 9;
optional Error error = 10;
}
@ -185,6 +192,11 @@ message UpdatePayment {
required bytes signature = 2;
}
message Close {
// A copy of the fully signed final contract that closes the channel. The client can verify
// the transaction is correct and then commit it to their wallet.
required bytes tx = 3;
}
// An Error can be sent by either party at any time
// Both parties should make an effort to send either an ERROR or a CLOSE immediately before

View File

@ -644,13 +644,23 @@ public class ChannelConnectionTest extends TestWithWallet {
client.incrementPayment(Utils.CENT);
client.incrementPayment(Utils.CENT);
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.UPDATE_PAYMENT));
pair.serverRecorder.q.take();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.UPDATE_PAYMENT));
pair.serverRecorder.q.take();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.UPDATE_PAYMENT));
pair.serverRecorder.q.take();
// Close it and verify it's considered to be closed.
broadcastTxPause.release();
client.close();
server.receiveMessage(pair.clientRecorder.checkNextMsg(MessageType.CLOSE));
Transaction close = broadcasts.take();
// Server sends back the close TX it just broadcast.
final Protos.TwoWayChannelMessage closeMsg = pair.serverRecorder.checkNextMsg(MessageType.CLOSE);
final Transaction closeTx = new Transaction(params, closeMsg.getClose().getTx().toByteArray());
assertEquals(close, closeTx);
client.receiveMessage(closeMsg);
assertNotNull(wallet.getTransaction(closeTx.getHash())); // Close TX entered the wallet.
sendMoneyToWallet(close, AbstractBlockChain.NewBlockType.BEST_CHAIN);
client.connectionClosed();
server.connectionClosed();

View File

@ -20,7 +20,6 @@ import com.google.bitcoin.core.*;
import com.google.bitcoin.script.Script;
import com.google.bitcoin.script.ScriptBuilder;
import com.google.bitcoin.utils.TestWithWallet;
import com.google.bitcoin.wallet.DefaultCoinSelector;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
@ -189,29 +188,19 @@ public class PaymentChannelStateTest extends TestWithWallet {
final TxFuturePair pair2 = broadcasts.take();
Transaction closeTx = pair2.tx;
pair2.future.set(closeTx);
final Transaction reserializedCloseTx = new Transaction(params, closeTx.bitcoinSerialize());
assertEquals(PaymentChannelServerState.State.CLOSED, serverState.getState());
// ... and on the client side.
wallet.receivePending(reserializedCloseTx, null);
assertEquals(PaymentChannelClientState.State.CLOSED, clientState.getState());
// Create a block with the payment transaction in it and give it to both wallets
chain.add(makeSolvedTestBlock(blockStore.getChainHead().getHeader(), new Transaction(params, closeTx.bitcoinSerialize())));
chain.add(makeSolvedTestBlock(blockStore.getChainHead().getHeader(), reserializedCloseTx));
assertEquals(size.multiply(BigInteger.valueOf(5)), serverWallet.getBalance(new DefaultCoinSelector() {
@Override
protected boolean shouldSelect(Transaction tx) {
if (tx.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING)
return true;
return false;
}
}));
assertEquals(size.multiply(BigInteger.valueOf(5)), serverWallet.getBalance());
assertEquals(0, serverWallet.getPendingTransactions().size());
assertEquals(Utils.COIN.subtract(size.multiply(BigInteger.valueOf(5))), wallet.getBalance(new DefaultCoinSelector() {
@Override
protected boolean shouldSelect(Transaction tx) {
if (tx.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING)
return true;
return false;
}
}));
assertEquals(Utils.COIN.subtract(size.multiply(BigInteger.valueOf(5))), wallet.getBalance());
assertEquals(0, wallet.getPendingTransactions().size());
assertEquals(3, wallet.getTransactions(false).size());