正在查看: imToken v3.28.8 应用的 PaymentChannelClient.java JAVA 源代码文件
本页面展示 JAVA 反编译生成的源代码文件,支持语法高亮显示。 仅供安全研究与技术分析使用,严禁用于任何非法用途。请遵守相关法律法规。
正在查看: imToken v3.28.8 应用的 PaymentChannelClient.java JAVA 源代码文件
本页面展示 JAVA 反编译生成的源代码文件,支持语法高亮显示。 仅供安全研究与技术分析使用,严禁用于任何非法用途。请遵守相关法律法规。
package org.bitcoinj.protocols.channels;
import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture;
import com.google.protobuf.ByteString;
import java.util.concurrent.locks.ReentrantLock;
import javax.annotation.Nullable;
import net.jcip.annotations.GuardedBy;
import org.bitcoin.paymentchannel.Protos;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.Utils;
import org.bitcoinj.core.VerificationException;
import org.bitcoinj.protocols.channels.IPaymentChannelClient;
import org.bitcoinj.protocols.channels.PaymentChannelClientState;
import org.bitcoinj.protocols.channels.PaymentChannelCloseException;
import org.bitcoinj.utils.Threading;
import org.bitcoinj.wallet.Wallet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.crypto.params.KeyParameter;
public class PaymentChannelClient implements IPaymentChannelClient {
public static final long DEFAULT_TIME_WINDOW = 86340;
private static final Logger log = LoggerFactory.getLogger(PaymentChannelClient.class);
@GuardedBy("lock")
private final IPaymentChannelClient.ClientConnection conn;
@GuardedBy("lock")
boolean connectionOpen;
@GuardedBy("lock")
SettableFuture<PaymentIncrementAck> increasePaymentFuture;
@GuardedBy("lock")
Coin lastPaymentActualAmount;
protected final ReentrantLock lock;
@GuardedBy("lock")
private int majorVersion;
private final Coin maxValue;
@GuardedBy("lock")
private long minPayment;
private Coin missing;
private final ECKey myKey;
private final Sha256Hash serverId;
@GuardedBy("lock")
private PaymentChannelClientState state;
@GuardedBy("lock")
private InitStep step;
private StoredClientChannel storedChannel;
private final long timeWindow;
private KeyParameter userKeySetup;
private final VersionSelector versionSelector;
private final Wallet wallet;
private enum InitStep {
WAITING_FOR_CONNECTION_OPEN,
WAITING_FOR_VERSION_NEGOTIATION,
WAITING_FOR_INITIATE,
WAITING_FOR_REFUND_RETURN,
WAITING_FOR_CHANNEL_OPEN,
CHANNEL_OPEN,
WAITING_FOR_CHANNEL_CLOSE,
CHANNEL_CLOSED
}
public enum VersionSelector {
VERSION_1,
VERSION_2_ALLOW_1,
VERSION_2;
public int getRequestedMinorVersion() {
return 0;
}
public int getRequestedMajorVersion() {
return AnonymousClass2.$SwitchMap$org$bitcoinj$protocols$channels$PaymentChannelClient$VersionSelector[ordinal()] != 1 ? 2 : 1;
}
public boolean isServerVersionAccepted(int i, int i2) {
int i3 = AnonymousClass2.$SwitchMap$org$bitcoinj$protocols$channels$PaymentChannelClient$VersionSelector[ordinal()];
return i3 != 1 ? i3 != 2 ? i3 == 3 && i == 2 : i == 1 || i == 2 : i == 1;
}
}
public PaymentChannelClient(Wallet wallet, ECKey eCKey, Coin coin, Sha256Hash sha256Hash, IPaymentChannelClient.ClientConnection clientConnection) {
this(wallet, eCKey, coin, sha256Hash, clientConnection, VersionSelector.VERSION_2_ALLOW_1);
}
public PaymentChannelClient(Wallet wallet, ECKey eCKey, Coin coin, Sha256Hash sha256Hash, IPaymentChannelClient.ClientConnection clientConnection, VersionSelector versionSelector) {
this(wallet, eCKey, coin, sha256Hash, DEFAULT_TIME_WINDOW, null, clientConnection, versionSelector);
}
public PaymentChannelClient(Wallet wallet, ECKey eCKey, Coin coin, Sha256Hash sha256Hash, long j, @Nullable KeyParameter keyParameter, IPaymentChannelClient.ClientConnection clientConnection) {
this(wallet, eCKey, coin, sha256Hash, j, keyParameter, clientConnection, VersionSelector.VERSION_2_ALLOW_1);
}
public PaymentChannelClient(Wallet wallet, ECKey eCKey, Coin coin, Sha256Hash sha256Hash, long j, @Nullable KeyParameter keyParameter, IPaymentChannelClient.ClientConnection clientConnection, VersionSelector versionSelector) {
this.lock = Threading.lock("channelclient");
this.connectionOpen = false;
this.step = InitStep.WAITING_FOR_CONNECTION_OPEN;
this.wallet = (Wallet) Preconditions.checkNotNull(wallet);
this.myKey = (ECKey) Preconditions.checkNotNull(eCKey);
this.maxValue = (Coin) Preconditions.checkNotNull(coin);
this.serverId = (Sha256Hash) Preconditions.checkNotNull(sha256Hash);
Preconditions.checkState(j >= 0);
this.timeWindow = j;
this.conn = (IPaymentChannelClient.ClientConnection) Preconditions.checkNotNull(clientConnection);
this.userKeySetup = keyParameter;
this.versionSelector = versionSelector;
}
public Coin getMissing() {
return this.missing;
}
@GuardedBy("lock")
@Nullable
private PaymentChannelCloseException.CloseReason receiveInitiate(Protos.Initiate initiate, Coin coin, Protos.Error.Builder builder) throws VerificationException, InsufficientMoneyException, ECKey.KeyIsEncryptedException {
log.info("Got INITIATE message:\n{}", initiate.toString());
if (this.wallet.isEncrypted() && this.userKeySetup == null) {
throw new ECKey.KeyIsEncryptedException();
}
long expireTimeSecs = initiate.getExpireTimeSecs();
Preconditions.checkState(expireTimeSecs >= 0 && initiate.getMinAcceptedChannelSize() >= 0);
if (!this.conn.acceptExpireTime(expireTimeSecs)) {
log.error("Server suggested expire time was out of our allowed bounds: {} ({} s)", Utils.dateTimeFormat(1000 * expireTimeSecs), Long.valueOf(expireTimeSecs));
builder.setCode(Protos.Error.ErrorCode.TIME_WINDOW_UNACCEPTABLE);
return PaymentChannelCloseException.CloseReason.TIME_WINDOW_UNACCEPTABLE;
}
Coin valueOf = Coin.valueOf(initiate.getMinAcceptedChannelSize());
if (coin.compareTo(valueOf) < 0) {
log.error("Server requested too much value");
builder.setCode(Protos.Error.ErrorCode.CHANNEL_VALUE_TOO_LARGE);
this.missing = valueOf.subtract(coin);
return PaymentChannelCloseException.CloseReason.SERVER_REQUESTED_TOO_MUCH_VALUE;
}
long j = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.value;
if (initiate.getMinPayment() != j) {
log.error("Server requested a min payment of {} but we expected {}", Long.valueOf(initiate.getMinPayment()), Long.valueOf(j));
builder.setCode(Protos.Error.ErrorCode.MIN_PAYMENT_TOO_LARGE);
builder.setExpectedValue(j);
this.missing = Coin.valueOf(initiate.getMinPayment() - j);
return PaymentChannelCloseException.CloseReason.SERVER_REQUESTED_TOO_MUCH_VALUE;
}
byte[] byteArray = initiate.getMultisigKey().toByteArray();
if (!ECKey.isPubKeyCanonical(byteArray)) {
throw new VerificationException("Server gave us a non-canonical public key, protocol error.");
}
int i = this.majorVersion;
if (i == 1) {
this.state = new PaymentChannelV1ClientState(this.wallet, this.myKey, ECKey.fromPublicOnly(byteArray), coin, expireTimeSecs);
} else if (i == 2) {
this.state = new PaymentChannelV2ClientState(this.wallet, this.myKey, ECKey.fromPublicOnly(byteArray), coin, expireTimeSecs);
} else {
return PaymentChannelCloseException.CloseReason.NO_ACCEPTABLE_VERSION;
}
try {
this.state.initiate(this.userKeySetup);
this.minPayment = initiate.getMinPayment();
int i2 = this.majorVersion;
if (i2 == 1) {
this.step = InitStep.WAITING_FOR_REFUND_RETURN;
this.conn.sendToServer(Protos.TwoWayChannelMessage.newBuilder().setProvideRefund(Protos.ProvideRefund.newBuilder().setMultisigKey(ByteString.copyFrom(this.myKey.getPubKey())).setTx(ByteString.copyFrom(((PaymentChannelV1ClientState) this.state).getIncompleteRefundTransaction().unsafeBitcoinSerialize()))).setType(Protos.TwoWayChannelMessage.MessageType.PROVIDE_REFUND).m1809build());
} else if (i2 == 2) {
this.step = InitStep.WAITING_FOR_CHANNEL_OPEN;
this.state.storeChannelInWallet(this.serverId);
Protos.ProvideContract.Builder clientKey = Protos.ProvideContract.newBuilder().setTx(ByteString.copyFrom(this.state.getContract().unsafeBitcoinSerialize())).setClientKey(ByteString.copyFrom(this.myKey.getPubKey()));
try {
PaymentChannelClientState.IncrementedPayment incrementPaymentBy = state().incrementPaymentBy(Coin.valueOf(this.minPayment), this.userKeySetup);
Protos.UpdatePayment.Builder initialPaymentBuilder = clientKey.getInitialPaymentBuilder();
initialPaymentBuilder.setSignature(ByteString.copyFrom(incrementPaymentBy.signature.encodeToBitcoin()));
initialPaymentBuilder.setClientChangeValue(this.state.getValueRefunded().value);
this.userKeySetup = null;
Protos.TwoWayChannelMessage.Builder newBuilder = Protos.TwoWayChannelMessage.newBuilder();
newBuilder.setProvideContract(clientKey);
newBuilder.setType(Protos.TwoWayChannelMessage.MessageType.PROVIDE_CONTRACT);
this.conn.sendToServer(newBuilder.m1809build());
} catch (ValueOutOfRangeException e) {
throw new IllegalStateException(e);
}
} else {
return PaymentChannelCloseException.CloseReason.NO_ACCEPTABLE_VERSION;
}
return null;
} catch (ValueOutOfRangeException e2) {
log.error("Value out of range when trying to initiate", e2);
builder.setCode(Protos.Error.ErrorCode.CHANNEL_VALUE_TOO_LARGE);
return PaymentChannelCloseException.CloseReason.SERVER_REQUESTED_TOO_MUCH_VALUE;
}
}
@GuardedBy("lock")
private void receiveRefund(Protos.TwoWayChannelMessage twoWayChannelMessage, @Nullable KeyParameter keyParameter) throws VerificationException {
boolean z = false;
Preconditions.checkState(this.majorVersion == 1);
if (this.step == InitStep.WAITING_FOR_REFUND_RETURN && twoWayChannelMessage.hasReturnRefund()) {
z = true;
}
Preconditions.checkState(z);
log.info("Got RETURN_REFUND message, providing signed contract");
((PaymentChannelV1ClientState) this.state).provideRefundSignature(twoWayChannelMessage.getReturnRefund().getSignature().toByteArray(), keyParameter);
this.step = InitStep.WAITING_FOR_CHANNEL_OPEN;
this.state.storeChannelInWallet(this.serverId);
Protos.ProvideContract.Builder tx = Protos.ProvideContract.newBuilder().setTx(ByteString.copyFrom(this.state.getContract().unsafeBitcoinSerialize()));
try {
PaymentChannelClientState.IncrementedPayment incrementPaymentBy = state().incrementPaymentBy(Coin.valueOf(this.minPayment), keyParameter);
Protos.UpdatePayment.Builder initialPaymentBuilder = tx.getInitialPaymentBuilder();
initialPaymentBuilder.setSignature(ByteString.copyFrom(incrementPaymentBy.signature.encodeToBitcoin()));
initialPaymentBuilder.setClientChangeValue(this.state.getValueRefunded().value);
Protos.TwoWayChannelMessage.Builder newBuilder = Protos.TwoWayChannelMessage.newBuilder();
newBuilder.setProvideContract(tx);
newBuilder.setType(Protos.TwoWayChannelMessage.MessageType.PROVIDE_CONTRACT);
this.conn.sendToServer(newBuilder.m1809build());
} catch (ValueOutOfRangeException e) {
throw new IllegalStateException(e);
}
}
@GuardedBy("lock")
private void receiveChannelOpen() throws VerificationException {
boolean z = false;
Preconditions.checkState(this.step == InitStep.WAITING_FOR_CHANNEL_OPEN || (this.step == InitStep.WAITING_FOR_INITIATE && this.storedChannel != null), this.step);
log.info("Got CHANNEL_OPEN message, ready to pay");
if (this.step == InitStep.WAITING_FOR_INITIATE) {
int i = this.majorVersion;
if (i == 1) {
this.state = new PaymentChannelV1ClientState(this.storedChannel, this.wallet);
} else if (i == 2) {
this.state = new PaymentChannelV2ClientState(this.storedChannel, this.wallet);
} else {
throw new IllegalStateException("Invalid version number " + this.majorVersion);
}
} else {
z = true;
}
this.step = InitStep.CHANNEL_OPEN;
this.conn.channelOpen(z);
}
@Override
public void receiveMessage(Protos.TwoWayChannelMessage twoWayChannelMessage) throws InsufficientMoneyException {
Protos.Error.Builder explanation;
PaymentChannelCloseException.CloseReason closeReason;
this.lock.lock();
try {
Preconditions.checkState(this.connectionOpen);
try {
boolean z = true;
switch (AnonymousClass2.$SwitchMap$org$bitcoin$paymentchannel$Protos$TwoWayChannelMessage$MessageType[twoWayChannelMessage.getType().ordinal()]) {
case 1:
if (this.step != InitStep.WAITING_FOR_VERSION_NEGOTIATION || !twoWayChannelMessage.hasServerVersion()) {
z = false;
}
Preconditions.checkState(z);
int major = twoWayChannelMessage.getServerVersion().getMajor();
this.majorVersion = major;
if (!this.versionSelector.isServerVersionAccepted(major, twoWayChannelMessage.getServerVersion().getMinor())) {
explanation = Protos.Error.newBuilder().setCode(Protos.Error.ErrorCode.NO_ACCEPTABLE_VERSION);
closeReason = PaymentChannelCloseException.CloseReason.NO_ACCEPTABLE_VERSION;
break;
} else {
log.info("Got version handshake, awaiting INITIATE or resume CHANNEL_OPEN");
this.step = InitStep.WAITING_FOR_INITIATE;
return;
}
case 2:
if (this.step != InitStep.WAITING_FOR_INITIATE || !twoWayChannelMessage.hasInitiate()) {
z = false;
}
Preconditions.checkState(z);
Protos.Initiate initiate = twoWayChannelMessage.getInitiate();
explanation = Protos.Error.newBuilder();
closeReason = receiveInitiate(initiate, this.maxValue, explanation);
if (closeReason != null) {
log.error("Initiate failed with error: {}", explanation.m1568build().toString());
break;
} else {
return;
}
case 3:
receiveRefund(twoWayChannelMessage, this.userKeySetup);
this.userKeySetup = null;
return;
case 4:
receiveChannelOpen();
return;
case 5:
receivePaymentAck(twoWayChannelMessage.getPaymentAck());
return;
case 6:
receiveClose(twoWayChannelMessage);
return;
case 7:
Preconditions.checkState(twoWayChannelMessage.hasError());
log.error("Server sent ERROR {} with explanation {}", twoWayChannelMessage.getError().getCode().name(), twoWayChannelMessage.getError().hasExplanation() ? twoWayChannelMessage.getError().getExplanation() : "");
setIncreasePaymentFutureIfNeeded(PaymentChannelCloseException.CloseReason.REMOTE_SENT_ERROR, twoWayChannelMessage.getError().getCode().name());
this.conn.destroyConnection(PaymentChannelCloseException.CloseReason.REMOTE_SENT_ERROR);
return;
default:
log.error("Got unknown message type or type that doesn't apply to clients.");
explanation = Protos.Error.newBuilder().setCode(Protos.Error.ErrorCode.SYNTAX_ERROR);
setIncreasePaymentFutureIfNeeded(PaymentChannelCloseException.CloseReason.REMOTE_SENT_INVALID_MESSAGE, "");
closeReason = PaymentChannelCloseException.CloseReason.REMOTE_SENT_INVALID_MESSAGE;
break;
}
} catch (IllegalStateException e) {
log.error("Caught illegal state exception handling message from server", e);
explanation = Protos.Error.newBuilder().setCode(Protos.Error.ErrorCode.SYNTAX_ERROR);
closeReason = PaymentChannelCloseException.CloseReason.REMOTE_SENT_INVALID_MESSAGE;
} catch (VerificationException e2) {
log.error("Caught verification exception handling message from server", e2);
explanation = Protos.Error.newBuilder().setCode(Protos.Error.ErrorCode.BAD_TRANSACTION).setExplanation(e2.getMessage());
closeReason = PaymentChannelCloseException.CloseReason.REMOTE_SENT_INVALID_MESSAGE;
}
this.conn.sendToServer(Protos.TwoWayChannelMessage.newBuilder().setError(explanation).setType(Protos.TwoWayChannelMessage.MessageType.ERROR).m1809build());
this.conn.destroyConnection(closeReason);
} finally {
this.lock.unlock();
}
}
static class AnonymousClass2 {
static final int[] $SwitchMap$org$bitcoin$paymentchannel$Protos$TwoWayChannelMessage$MessageType;
static final int[] $SwitchMap$org$bitcoinj$protocols$channels$PaymentChannelClient$VersionSelector;
static {
int[] iArr = new int[Protos.TwoWayChannelMessage.MessageType.values().length];
$SwitchMap$org$bitcoin$paymentchannel$Protos$TwoWayChannelMessage$MessageType = iArr;
try {
iArr[Protos.TwoWayChannelMessage.MessageType.SERVER_VERSION.ordinal()] = 1;
} catch (NoSuchFieldError unused) {
}
try {
$SwitchMap$org$bitcoin$paymentchannel$Protos$TwoWayChannelMessage$MessageType[Protos.TwoWayChannelMessage.MessageType.INITIATE.ordinal()] = 2;
} catch (NoSuchFieldError unused2) {
}
try {
$SwitchMap$org$bitcoin$paymentchannel$Protos$TwoWayChannelMessage$MessageType[Protos.TwoWayChannelMessage.MessageType.RETURN_REFUND.ordinal()] = 3;
} catch (NoSuchFieldError unused3) {
}
try {
$SwitchMap$org$bitcoin$paymentchannel$Protos$TwoWayChannelMessage$MessageType[Protos.TwoWayChannelMessage.MessageType.CHANNEL_OPEN.ordinal()] = 4;
} catch (NoSuchFieldError unused4) {
}
try {
$SwitchMap$org$bitcoin$paymentchannel$Protos$TwoWayChannelMessage$MessageType[Protos.TwoWayChannelMessage.MessageType.PAYMENT_ACK.ordinal()] = 5;
} catch (NoSuchFieldError unused5) {
}
try {
$SwitchMap$org$bitcoin$paymentchannel$Protos$TwoWayChannelMessage$MessageType[Protos.TwoWayChannelMessage.MessageType.CLOSE.ordinal()] = 6;
} catch (NoSuchFieldError unused6) {
}
try {
$SwitchMap$org$bitcoin$paymentchannel$Protos$TwoWayChannelMessage$MessageType[Protos.TwoWayChannelMessage.MessageType.ERROR.ordinal()] = 7;
} catch (NoSuchFieldError unused7) {
}
int[] iArr2 = new int[VersionSelector.values().length];
$SwitchMap$org$bitcoinj$protocols$channels$PaymentChannelClient$VersionSelector = iArr2;
try {
iArr2[VersionSelector.VERSION_1.ordinal()] = 1;
} catch (NoSuchFieldError unused8) {
}
try {
$SwitchMap$org$bitcoinj$protocols$channels$PaymentChannelClient$VersionSelector[VersionSelector.VERSION_2_ALLOW_1.ordinal()] = 2;
} catch (NoSuchFieldError unused9) {
}
try {
$SwitchMap$org$bitcoinj$protocols$channels$PaymentChannelClient$VersionSelector[VersionSelector.VERSION_2.ordinal()] = 3;
} catch (NoSuchFieldError unused10) {
}
}
}
private void setIncreasePaymentFutureIfNeeded(PaymentChannelCloseException.CloseReason closeReason, String str) {
SettableFuture<PaymentIncrementAck> settableFuture = this.increasePaymentFuture;
if (settableFuture == null || settableFuture.isDone()) {
return;
}
this.increasePaymentFuture.setException(new PaymentChannelCloseException(str, closeReason));
}
@GuardedBy("lock")
private void receiveClose(Protos.TwoWayChannelMessage twoWayChannelMessage) throws VerificationException {
Preconditions.checkState(this.lock.isHeldByCurrentThread());
if (twoWayChannelMessage.hasSettlement()) {
Transaction makeTransaction = this.wallet.getParams().getDefaultSerializer().makeTransaction(twoWayChannelMessage.getSettlement().getTx().toByteArray());
log.info("CLOSE message received with settlement tx {}", makeTransaction.getHash());
if (this.state != null && state().isSettlementTransaction(makeTransaction)) {
this.wallet.receivePending(makeTransaction, null);
}
} else {
log.info("CLOSE message received without settlement tx");
}
if (this.step == InitStep.WAITING_FOR_CHANNEL_CLOSE) {
this.conn.destroyConnection(PaymentChannelCloseException.CloseReason.CLIENT_REQUESTED_CLOSE);
} else {
this.conn.destroyConnection(PaymentChannelCloseException.CloseReason.SERVER_REQUESTED_CLOSE);
}
this.step = InitStep.CHANNEL_CLOSED;
}
@Override
public void connectionClosed() {
this.lock.lock();
try {
this.connectionOpen = false;
if (this.state != null) {
this.state.disconnectFromChannel();
}
} finally {
this.lock.unlock();
}
}
@Override
public void settle() throws IllegalStateException {
this.lock.lock();
try {
Preconditions.checkState(this.connectionOpen);
this.step = InitStep.WAITING_FOR_CHANNEL_CLOSE;
log.info("Sending a CLOSE message to the server and waiting for response indicating successful settlement.");
this.conn.sendToServer(Protos.TwoWayChannelMessage.newBuilder().setType(Protos.TwoWayChannelMessage.MessageType.CLOSE).m1809build());
} finally {
this.lock.unlock();
}
}
@Override
public void connectionOpen() {
this.lock.lock();
try {
this.connectionOpen = true;
StoredPaymentChannelClientStates storedPaymentChannelClientStates = (StoredPaymentChannelClientStates) this.wallet.getExtensions().get(StoredPaymentChannelClientStates.EXTENSION_ID);
if (storedPaymentChannelClientStates != null) {
this.storedChannel = storedPaymentChannelClientStates.getUsableChannelForServerID(this.serverId);
}
this.step = InitStep.WAITING_FOR_VERSION_NEGOTIATION;
Protos.ClientVersion.Builder timeWindowSecs = Protos.ClientVersion.newBuilder().setMajor(this.versionSelector.getRequestedMajorVersion()).setMinor(this.versionSelector.getRequestedMinorVersion()).setTimeWindowSecs(this.timeWindow);
if (this.storedChannel != null) {
timeWindowSecs.setPreviousChannelContractHash(ByteString.copyFrom(this.storedChannel.contract.getHash().getBytes()));
log.info("Begun version handshake, attempting to reopen channel with contract hash {}", this.storedChannel.contract.getHash());
} else {
log.info("Begun version handshake creating new channel");
}
this.conn.sendToServer(Protos.TwoWayChannelMessage.newBuilder().setType(Protos.TwoWayChannelMessage.MessageType.CLIENT_VERSION).setClientVersion(timeWindowSecs).m1809build());
} finally {
this.lock.unlock();
}
}
public PaymentChannelClientState state() {
this.lock.lock();
try {
return this.state;
} finally {
this.lock.unlock();
}
}
public ListenableFuture<PaymentIncrementAck> incrementPayment(Coin coin) throws ValueOutOfRangeException, IllegalStateException {
return incrementPayment(coin, null, null);
}
@Override
public ListenableFuture<PaymentIncrementAck> incrementPayment(Coin coin, @Nullable ByteString byteString, @Nullable KeyParameter keyParameter) throws ValueOutOfRangeException, IllegalStateException, ECKey.KeyIsEncryptedException {
this.lock.lock();
try {
if (state() == null || !this.connectionOpen || this.step != InitStep.CHANNEL_OPEN) {
throw new IllegalStateException("Channel is not fully initialized/has already been closed");
}
if (this.increasePaymentFuture != null) {
throw new IllegalStateException("Already incrementing paying, wait for previous payment to complete.");
}
if (this.wallet.isEncrypted() && keyParameter == null) {
throw new ECKey.KeyIsEncryptedException();
}
PaymentChannelClientState.IncrementedPayment incrementPaymentBy = state().incrementPaymentBy(coin, keyParameter);
Protos.UpdatePayment.Builder clientChangeValue = Protos.UpdatePayment.newBuilder().setSignature(ByteString.copyFrom(incrementPaymentBy.signature.encodeToBitcoin())).setClientChangeValue(this.state.getValueRefunded().value);
if (byteString != null) {
clientChangeValue.setInfo(byteString);
}
SettableFuture<PaymentIncrementAck> create = SettableFuture.create();
this.increasePaymentFuture = create;
create.addListener(new Runnable() {
@Override
public void run() {
PaymentChannelClient.this.lock.lock();
PaymentChannelClient.this.increasePaymentFuture = null;
PaymentChannelClient.this.lock.unlock();
}
}, MoreExecutors.sameThreadExecutor());
this.conn.sendToServer(Protos.TwoWayChannelMessage.newBuilder().setUpdatePayment(clientChangeValue).setType(Protos.TwoWayChannelMessage.MessageType.UPDATE_PAYMENT).m1809build());
this.lastPaymentActualAmount = incrementPaymentBy.amount;
return this.increasePaymentFuture;
} finally {
this.lock.unlock();
}
}
private void receivePaymentAck(Protos.PaymentAck paymentAck) {
this.lock.lock();
try {
if (this.increasePaymentFuture == null) {
return;
}
Preconditions.checkNotNull(this.increasePaymentFuture, "Server sent a PAYMENT_ACK with no outstanding payment");
log.info("Received a PAYMENT_ACK from the server");
SettableFuture<PaymentIncrementAck> settableFuture = this.increasePaymentFuture;
Coin coin = this.lastPaymentActualAmount;
this.lock.unlock();
settableFuture.set(new PaymentIncrementAck(coin, paymentAck.getInfo()));
} finally {
this.lock.unlock();
}
}
}