Add listener on BuyerSendCounterCurrencyTransferStartedMessage to resend msg case it has not arrived

Add signed witness to PayoutTxPublishedMessage
Remove usage of RefreshTradeStateRequest but leave it for backward compatibility
Move removeMailboxMessageAfterProcessing calls in finally branch
Rename methods
This commit is contained in:
chimp1984 2020-09-22 00:32:28 -05:00
parent 33c816acf6
commit 527f1537a9
No known key found for this signature in database
GPG key ID: 9801B4EC591F90E3
26 changed files with 358 additions and 446 deletions

View file

@ -216,20 +216,20 @@ public class SignedWitnessService {
} }
// Arbitrators sign with EC key // Arbitrators sign with EC key
public void signAccountAgeWitness(Coin tradeAmount, public void signAndPublishAccountAgeWitness(Coin tradeAmount,
AccountAgeWitness accountAgeWitness, AccountAgeWitness accountAgeWitness,
ECKey key, ECKey key,
PublicKey peersPubKey) { PublicKey peersPubKey) {
signAccountAgeWitness(tradeAmount, accountAgeWitness, key, peersPubKey.getEncoded(), new Date().getTime()); signAndPublishAccountAgeWitness(tradeAmount, accountAgeWitness, key, peersPubKey.getEncoded(), new Date().getTime());
} }
// Arbitrators sign with EC key // Arbitrators sign with EC key
public String signAccountAgeWitness(AccountAgeWitness accountAgeWitness, public String signAndPublishAccountAgeWitness(AccountAgeWitness accountAgeWitness,
ECKey key, ECKey key,
byte[] peersPubKey, byte[] peersPubKey,
long time) { long time) {
var witnessPubKey = peersPubKey == null ? ownerPubKey(accountAgeWitness) : peersPubKey; var witnessPubKey = peersPubKey == null ? ownerPubKey(accountAgeWitness) : peersPubKey;
return signAccountAgeWitness(MINIMUM_TRADE_AMOUNT_FOR_SIGNING, accountAgeWitness, key, witnessPubKey, time); return signAndPublishAccountAgeWitness(MINIMUM_TRADE_AMOUNT_FOR_SIGNING, accountAgeWitness, key, witnessPubKey, time);
} }
// Arbitrators sign with EC key // Arbitrators sign with EC key
@ -238,11 +238,11 @@ public class SignedWitnessService {
long childSignTime) { long childSignTime) {
var time = childSignTime - SIGNER_AGE - 1; var time = childSignTime - SIGNER_AGE - 1;
var dummyAccountAgeWitness = new AccountAgeWitness(Hash.getRipemd160hash(peersPubKey), time); var dummyAccountAgeWitness = new AccountAgeWitness(Hash.getRipemd160hash(peersPubKey), time);
return signAccountAgeWitness(MINIMUM_TRADE_AMOUNT_FOR_SIGNING, dummyAccountAgeWitness, key, peersPubKey, time); return signAndPublishAccountAgeWitness(MINIMUM_TRADE_AMOUNT_FOR_SIGNING, dummyAccountAgeWitness, key, peersPubKey, time);
} }
// Arbitrators sign with EC key // Arbitrators sign with EC key
private String signAccountAgeWitness(Coin tradeAmount, private String signAndPublishAccountAgeWitness(Coin tradeAmount,
AccountAgeWitness accountAgeWitness, AccountAgeWitness accountAgeWitness,
ECKey key, ECKey key,
byte[] peersPubKey, byte[] peersPubKey,
@ -272,14 +272,14 @@ public class SignedWitnessService {
return ""; return "";
} }
public void selfSignAccountAgeWitness(AccountAgeWitness accountAgeWitness) throws CryptoException { public void selfSignAndPublishAccountAgeWitness(AccountAgeWitness accountAgeWitness) throws CryptoException {
log.info("Sign own accountAgeWitness {}", accountAgeWitness); log.info("Sign own accountAgeWitness {}", accountAgeWitness);
signAccountAgeWitness(MINIMUM_TRADE_AMOUNT_FOR_SIGNING, accountAgeWitness, signAndPublishAccountAgeWitness(MINIMUM_TRADE_AMOUNT_FOR_SIGNING, accountAgeWitness,
keyRing.getSignatureKeyPair().getPublic()); keyRing.getSignatureKeyPair().getPublic());
} }
// Any peer can sign with DSA key // Any peer can sign with DSA key
public Optional<SignedWitness> signAccountAgeWitness(Coin tradeAmount, public Optional<SignedWitness> signAndPublishAccountAgeWitness(Coin tradeAmount,
AccountAgeWitness accountAgeWitness, AccountAgeWitness accountAgeWitness,
PublicKey peersPubKey) throws CryptoException { PublicKey peersPubKey) throws CryptoException {
if (isSignedAccountAgeWitness(accountAgeWitness)) { if (isSignedAccountAgeWitness(accountAgeWitness)) {
@ -494,7 +494,8 @@ public class SignedWitnessService {
private void publishSignedWitness(SignedWitness signedWitness) { private void publishSignedWitness(SignedWitness signedWitness) {
if (!signedWitnessMap.containsKey(signedWitness.getHashAsByteArray())) { if (!signedWitnessMap.containsKey(signedWitness.getHashAsByteArray())) {
log.info("broadcast signed witness {}", signedWitness.toString()); log.info("broadcast signed witness {}", signedWitness.toString());
p2PService.addPersistableNetworkPayload(signedWitness, false); // We set reBroadcast to true to achieve better resilience.
p2PService.addPersistableNetworkPayload(signedWitness, true);
addToMap(signedWitness); addToMap(signedWitness);
} }
} }

View file

@ -35,14 +35,11 @@ import bisq.core.support.dispute.DisputeResult;
import bisq.core.support.dispute.arbitration.TraderDataItem; import bisq.core.support.dispute.arbitration.TraderDataItem;
import bisq.core.trade.Contract; import bisq.core.trade.Contract;
import bisq.core.trade.Trade; import bisq.core.trade.Trade;
import bisq.core.trade.messages.TraderSignedWitnessMessage;
import bisq.core.trade.protocol.TradingPeer; import bisq.core.trade.protocol.TradingPeer;
import bisq.core.user.User; import bisq.core.user.User;
import bisq.network.p2p.BootstrapListener; import bisq.network.p2p.BootstrapListener;
import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.P2PService; import bisq.network.p2p.P2PService;
import bisq.network.p2p.SendMailboxMessageListener;
import bisq.network.p2p.storage.P2PDataStorage; import bisq.network.p2p.storage.P2PDataStorage;
import bisq.network.p2p.storage.persistence.AppendOnlyDataStoreService; import bisq.network.p2p.storage.persistence.AppendOnlyDataStoreService;
@ -76,7 +73,6 @@ import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Random; import java.util.Random;
import java.util.Set; import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -202,7 +198,7 @@ public class AccountAgeWitnessService {
private void onBootStrapped() { private void onBootStrapped() {
republishAllFiatAccounts(); republishAllFiatAccounts();
signSameNameAccounts(); signAndPublishSameNameAccounts();
} }
@ -643,7 +639,7 @@ public class AccountAgeWitnessService {
AccountAgeWitness accountAgeWitness, AccountAgeWitness accountAgeWitness,
ECKey key, ECKey key,
PublicKey peersPubKey) { PublicKey peersPubKey) {
signedWitnessService.signAccountAgeWitness(tradeAmount, accountAgeWitness, key, peersPubKey); signedWitnessService.signAndPublishAccountAgeWitness(tradeAmount, accountAgeWitness, key, peersPubKey);
} }
public String arbitratorSignOrphanWitness(AccountAgeWitness accountAgeWitness, public String arbitratorSignOrphanWitness(AccountAgeWitness accountAgeWitness,
@ -655,7 +651,7 @@ public class AccountAgeWitnessService {
.findAny() .findAny()
.orElse(null); .orElse(null);
checkNotNull(signedWitness); checkNotNull(signedWitness);
return signedWitnessService.signAccountAgeWitness(accountAgeWitness, key, signedWitness.getWitnessOwnerPubKey(), return signedWitnessService.signAndPublishAccountAgeWitness(accountAgeWitness, key, signedWitness.getWitnessOwnerPubKey(),
time); time);
} }
@ -669,10 +665,10 @@ public class AccountAgeWitnessService {
ECKey key, ECKey key,
byte[] tradersPubKey, byte[] tradersPubKey,
long time) { long time) {
signedWitnessService.signAccountAgeWitness(accountAgeWitness, key, tradersPubKey, time); signedWitnessService.signAndPublishAccountAgeWitness(accountAgeWitness, key, tradersPubKey, time);
} }
public Optional<SignedWitness> traderSignPeersAccountAgeWitness(Trade trade) { public Optional<SignedWitness> traderSignAndPublishPeersAccountAgeWitness(Trade trade) {
AccountAgeWitness peersWitness = findTradePeerWitness(trade).orElse(null); AccountAgeWitness peersWitness = findTradePeerWitness(trade).orElse(null);
Coin tradeAmount = trade.getTradeAmount(); Coin tradeAmount = trade.getTradeAmount();
checkNotNull(trade.getProcessModel().getTradingPeer().getPubKeyRing(), "Peer must have a keyring"); checkNotNull(trade.getProcessModel().getTradingPeer().getPubKeyRing(), "Peer must have a keyring");
@ -682,7 +678,7 @@ public class AccountAgeWitnessService {
checkNotNull(peersPubKey, "Peers pub key must not be null"); checkNotNull(peersPubKey, "Peers pub key must not be null");
try { try {
return signedWitnessService.signAccountAgeWitness(tradeAmount, peersWitness, peersPubKey); return signedWitnessService.signAndPublishAccountAgeWitness(tradeAmount, peersWitness, peersPubKey);
} catch (CryptoException e) { } catch (CryptoException e) {
log.warn("Trader failed to sign witness, exception {}", e.toString()); log.warn("Trader failed to sign witness, exception {}", e.toString());
} }
@ -827,7 +823,7 @@ public class AccountAgeWitnessService {
.collect(Collectors.toSet()); .collect(Collectors.toSet());
} }
public void signSameNameAccounts() { public void signAndPublishSameNameAccounts() {
// Collect accounts that have ownerId to sign unsigned accounts with the same ownderId // Collect accounts that have ownerId to sign unsigned accounts with the same ownderId
var signerAccounts = Objects.requireNonNull(user.getPaymentAccounts()).stream() var signerAccounts = Objects.requireNonNull(user.getPaymentAccounts()).stream()
.filter(account -> account.getOwnerId() != null && .filter(account -> account.getOwnerId() != null &&
@ -842,7 +838,7 @@ public class AccountAgeWitnessService {
signerAccounts.forEach(signer -> unsignedAccounts.forEach(unsigned -> { signerAccounts.forEach(signer -> unsignedAccounts.forEach(unsigned -> {
if (signer.getOwnerId().equals(unsigned.getOwnerId())) { if (signer.getOwnerId().equals(unsigned.getOwnerId())) {
try { try {
signedWitnessService.selfSignAccountAgeWitness( signedWitnessService.selfSignAndPublishAccountAgeWitness(
getMyWitness(unsigned.getPaymentAccountPayload())); getMyWitness(unsigned.getPaymentAccountPayload()));
} catch (CryptoException e) { } catch (CryptoException e) {
log.warn("Self signing failed, exception {}", e.toString()); log.warn("Self signing failed, exception {}", e.toString());
@ -868,44 +864,4 @@ public class AccountAgeWitnessService {
!peerHasSignedWitness(trade) && !peerHasSignedWitness(trade) &&
tradeAmountIsSufficient(trade.getTradeAmount()); tradeAmountIsSufficient(trade.getTradeAmount());
} }
public void maybeSignWitness(Trade trade) {
if (isSignWitnessTrade(trade)) {
var signedWitnessOptional = traderSignPeersAccountAgeWitness(trade);
signedWitnessOptional.ifPresent(signedWitness -> sendSignedWitnessToPeer(signedWitness, trade));
}
}
private void sendSignedWitnessToPeer(SignedWitness signedWitness, Trade trade) {
if (trade == null) return;
NodeAddress tradingPeerNodeAddress = trade.getTradingPeerNodeAddress();
var traderSignedWitnessMessage = new TraderSignedWitnessMessage(UUID.randomUUID().toString(), trade.getId(),
tradingPeerNodeAddress, signedWitness);
p2PService.sendEncryptedMailboxMessage(
tradingPeerNodeAddress,
trade.getProcessModel().getTradingPeer().getPubKeyRing(),
traderSignedWitnessMessage,
new SendMailboxMessageListener() {
@Override
public void onArrived() {
log.info("SendMailboxMessageListener onArrived tradeId={} at peer {} SignedWitness {}",
trade.getId(), tradingPeerNodeAddress, signedWitness);
}
@Override
public void onStoredInMailbox() {
log.info("SendMailboxMessageListener onStoredInMailbox tradeId={} at peer {} SignedWitness {}",
trade.getId(), tradingPeerNodeAddress, signedWitness);
}
@Override
public void onFault(String errorMessage) {
log.error("SendMailboxMessageListener onFault tradeId={} at peer {} SignedWitness {}",
trade.getId(), tradingPeerNodeAddress, signedWitness);
}
}
);
}
} }

View file

@ -350,9 +350,6 @@ public class TradeManager implements PersistedDataHost {
ResultHandler resultHandler, ResultHandler resultHandler,
ErrorMessageHandler errorMessageHandler) { ErrorMessageHandler errorMessageHandler) {
sellerTrade.onFiatPaymentReceived(resultHandler, errorMessageHandler); sellerTrade.onFiatPaymentReceived(resultHandler, errorMessageHandler);
//TODO move to trade protocol task
accountAgeWitnessService.maybeSignWitness(sellerTrade);
} }
private void initPendingTrade(Trade trade) { private void initPendingTrade(Trade trade) {

View file

@ -17,6 +17,8 @@
package bisq.core.trade.messages; package bisq.core.trade.messages;
import bisq.core.account.sign.SignedWitness;
import bisq.network.p2p.MailboxMessage; import bisq.network.p2p.MailboxMessage;
import bisq.network.p2p.NodeAddress; import bisq.network.p2p.NodeAddress;
@ -26,23 +28,35 @@ import bisq.common.util.Utilities;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import java.util.Optional;
import java.util.UUID;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Value; import lombok.Value;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
@Slf4j
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@Value @Value
public final class PayoutTxPublishedMessage extends TradeMessage implements MailboxMessage { public final class PayoutTxPublishedMessage extends TradeMessage implements MailboxMessage {
private final byte[] payoutTx; private final byte[] payoutTx;
private final NodeAddress senderNodeAddress; private final NodeAddress senderNodeAddress;
// Added in v1.4.0
@Nullable
private final SignedWitness signedWitness;
public PayoutTxPublishedMessage(String tradeId, public PayoutTxPublishedMessage(String tradeId,
byte[] payoutTx, byte[] payoutTx,
NodeAddress senderNodeAddress, NodeAddress senderNodeAddress,
String uid) { @Nullable SignedWitness signedWitness) {
this(tradeId, this(tradeId,
payoutTx, payoutTx,
senderNodeAddress, senderNodeAddress,
uid, signedWitness,
UUID.randomUUID().toString(),
Version.getP2PMessageVersion()); Version.getP2PMessageVersion());
} }
@ -54,28 +68,37 @@ public final class PayoutTxPublishedMessage extends TradeMessage implements Mail
private PayoutTxPublishedMessage(String tradeId, private PayoutTxPublishedMessage(String tradeId,
byte[] payoutTx, byte[] payoutTx,
NodeAddress senderNodeAddress, NodeAddress senderNodeAddress,
@Nullable SignedWitness signedWitness,
String uid, String uid,
int messageVersion) { int messageVersion) {
super(messageVersion, tradeId, uid); super(messageVersion, tradeId, uid);
this.payoutTx = payoutTx; this.payoutTx = payoutTx;
this.senderNodeAddress = senderNodeAddress; this.senderNodeAddress = senderNodeAddress;
this.signedWitness = signedWitness;
} }
@Override @Override
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
return getNetworkEnvelopeBuilder() protobuf.PayoutTxPublishedMessage.Builder builder = protobuf.PayoutTxPublishedMessage.newBuilder()
.setPayoutTxPublishedMessage(protobuf.PayoutTxPublishedMessage.newBuilder()
.setTradeId(tradeId) .setTradeId(tradeId)
.setPayoutTx(ByteString.copyFrom(payoutTx)) .setPayoutTx(ByteString.copyFrom(payoutTx))
.setSenderNodeAddress(senderNodeAddress.toProtoMessage()) .setSenderNodeAddress(senderNodeAddress.toProtoMessage())
.setUid(uid)) .setUid(uid);
.build(); Optional.ofNullable(signedWitness).ifPresent(signedWitness -> builder.setSignedWitness(signedWitness.toProtoSignedWitness()));
return getNetworkEnvelopeBuilder().setPayoutTxPublishedMessage(builder).build();
} }
public static NetworkEnvelope fromProto(protobuf.PayoutTxPublishedMessage proto, int messageVersion) { public static NetworkEnvelope fromProto(protobuf.PayoutTxPublishedMessage proto, int messageVersion) {
// There is no method to check for a nullable non-primitive data type object but we know that all fields
// are empty/null, so we check for the signature to see if we got a valid signedWitness.
protobuf.SignedWitness protoSignedWitness = proto.getSignedWitness();
SignedWitness signedWitness = !protoSignedWitness.getSignature().isEmpty() ?
SignedWitness.fromProto(protoSignedWitness) :
null;
return new PayoutTxPublishedMessage(proto.getTradeId(), return new PayoutTxPublishedMessage(proto.getTradeId(),
proto.getPayoutTx().toByteArray(), proto.getPayoutTx().toByteArray(),
NodeAddress.fromProto(proto.getSenderNodeAddress()), NodeAddress.fromProto(proto.getSenderNodeAddress()),
signedWitness,
proto.getUid(), proto.getUid(),
messageVersion); messageVersion);
} }
@ -85,7 +108,7 @@ public final class PayoutTxPublishedMessage extends TradeMessage implements Mail
return "PayoutTxPublishedMessage{" + return "PayoutTxPublishedMessage{" +
"\n payoutTx=" + Utilities.bytesAsHexString(payoutTx) + "\n payoutTx=" + Utilities.bytesAsHexString(payoutTx) +
",\n senderNodeAddress=" + senderNodeAddress + ",\n senderNodeAddress=" + senderNodeAddress +
",\n uid='" + uid + '\'' + ",\n signedWitness=" + signedWitness +
"\n} " + super.toString(); "\n} " + super.toString();
} }
} }

View file

@ -20,25 +20,19 @@ package bisq.core.trade.messages;
import bisq.network.p2p.MailboxMessage; import bisq.network.p2p.MailboxMessage;
import bisq.network.p2p.NodeAddress; import bisq.network.p2p.NodeAddress;
import bisq.common.app.Version;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Value; import lombok.Value;
/**
* Not used anymore since v1.4.0
* We do the re-sending of the payment sent message via the BuyerSendCounterCurrencyTransferStartedMessage task on the
* buyer side, so seller do not need to do anything interactively.
*/
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@Value @Value
public class RefreshTradeStateRequest extends TradeMessage implements MailboxMessage { public class RefreshTradeStateRequest extends TradeMessage implements MailboxMessage {
private final NodeAddress senderNodeAddress; private final NodeAddress senderNodeAddress;
public RefreshTradeStateRequest(String uid,
String tradeId,
NodeAddress senderNodeAddress) {
this(Version.getP2PMessageVersion(),
uid,
tradeId,
senderNodeAddress);
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER // PROTO BUFFER
@ -67,12 +61,4 @@ public class RefreshTradeStateRequest extends TradeMessage implements MailboxMes
proto.getTradeId(), proto.getTradeId(),
NodeAddress.fromProto(proto.getSenderNodeAddress())); NodeAddress.fromProto(proto.getSenderNodeAddress()));
} }
@Override
public String toString() {
return "RefreshTradeStateRequest{" +
"\n senderNodeAddress=" + senderNodeAddress +
"\n} " + super.toString();
}
} }

View file

@ -23,7 +23,6 @@ import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage; import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
import bisq.core.trade.messages.InputsForDepositTxRequest; import bisq.core.trade.messages.InputsForDepositTxRequest;
import bisq.core.trade.messages.PayoutTxPublishedMessage; import bisq.core.trade.messages.PayoutTxPublishedMessage;
import bisq.core.trade.messages.RefreshTradeStateRequest;
import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.ApplyFilter; import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.PublishTradeStatistics; import bisq.core.trade.protocol.tasks.PublishTradeStatistics;
@ -87,6 +86,19 @@ public class BuyerAsMakerProtocol extends TradeProtocol implements BuyerProtocol
taskRunner.addTasks(BuyerSetupPayoutTxListener.class); taskRunner.addTasks(BuyerSetupPayoutTxListener.class);
taskRunner.run(); taskRunner.run();
} }
// We might have 2 taskRunners as BuyerSetupPayoutTxListener might have been started as well
if (trade.getState() == Trade.State.BUYER_STORED_IN_MAILBOX_FIAT_PAYMENT_INITIATED_MSG ||
trade.getState() == Trade.State.BUYER_SEND_FAILED_FIAT_PAYMENT_INITIATED_MSG) {
// In case we have not received an ACK from the CounterCurrencyTransferStartedMessage we re-send it
// periodically in BuyerSendCounterCurrencyTransferStartedMessage
TradeTaskRunner taskRunner = new TradeTaskRunner(trade,
() -> handleTaskRunnerSuccess("BuyerSendCounterCurrencyTransferStartedMessage"),
this::handleTaskRunnerFault);
taskRunner.addTasks(BuyerSendCounterCurrencyTransferStartedMessage.class);
taskRunner.run();
}
} }
@ -102,8 +114,6 @@ public class BuyerAsMakerProtocol extends TradeProtocol implements BuyerProtocol
handle((DepositTxAndDelayedPayoutTxMessage) tradeMessage, peerNodeAddress); handle((DepositTxAndDelayedPayoutTxMessage) tradeMessage, peerNodeAddress);
} else if (tradeMessage instanceof PayoutTxPublishedMessage) { } else if (tradeMessage instanceof PayoutTxPublishedMessage) {
handle((PayoutTxPublishedMessage) tradeMessage, peerNodeAddress); handle((PayoutTxPublishedMessage) tradeMessage, peerNodeAddress);
} else if (tradeMessage instanceof RefreshTradeStateRequest) {
handle();
} }
} }
@ -234,23 +244,6 @@ public class BuyerAsMakerProtocol extends TradeProtocol implements BuyerProtocol
} }
///////////////////////////////////////////////////////////////////////////////////////////
// Incoming message Handle missing messages
///////////////////////////////////////////////////////////////////////////////////////////
private void handle() {
// Resend CounterCurrencyTransferStartedMessage if it hasn't been acked yet and counterparty asked for a refresh
if (trade.getState().getPhase() == Trade.Phase.FIAT_SENT &&
trade.getState().ordinal() >= Trade.State.BUYER_SENT_FIAT_PAYMENT_INITIATED_MSG.ordinal()) {
TradeTaskRunner taskRunner = new TradeTaskRunner(buyerAsMakerTrade,
() -> handleTaskRunnerSuccess("RefreshTradeStateRequest"),
this::handleTaskRunnerFault);
taskRunner.addTasks(BuyerSendCounterCurrencyTransferStartedMessage.class);
taskRunner.run();
}
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Message dispatcher // Message dispatcher
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -268,8 +261,6 @@ public class BuyerAsMakerProtocol extends TradeProtocol implements BuyerProtocol
handle((DepositTxAndDelayedPayoutTxMessage) tradeMessage, sender); handle((DepositTxAndDelayedPayoutTxMessage) tradeMessage, sender);
} else if (tradeMessage instanceof PayoutTxPublishedMessage) { } else if (tradeMessage instanceof PayoutTxPublishedMessage) {
handle((PayoutTxPublishedMessage) tradeMessage, sender); handle((PayoutTxPublishedMessage) tradeMessage, sender);
} else if (tradeMessage instanceof RefreshTradeStateRequest) {
handle();
} }
} }
} }

View file

@ -25,7 +25,6 @@ import bisq.core.trade.messages.DelayedPayoutTxSignatureRequest;
import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage; import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
import bisq.core.trade.messages.InputsForDepositTxResponse; import bisq.core.trade.messages.InputsForDepositTxResponse;
import bisq.core.trade.messages.PayoutTxPublishedMessage; import bisq.core.trade.messages.PayoutTxPublishedMessage;
import bisq.core.trade.messages.RefreshTradeStateRequest;
import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.ApplyFilter; import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.PublishTradeStatistics; import bisq.core.trade.protocol.tasks.PublishTradeStatistics;
@ -95,6 +94,19 @@ public class BuyerAsTakerProtocol extends TradeProtocol implements BuyerProtocol
taskRunner.addTasks(BuyerSetupPayoutTxListener.class); taskRunner.addTasks(BuyerSetupPayoutTxListener.class);
taskRunner.run(); taskRunner.run();
} }
// We might have 2 taskRunners as BuyerSetupPayoutTxListener might have been started as well
if (trade.getState() == Trade.State.BUYER_STORED_IN_MAILBOX_FIAT_PAYMENT_INITIATED_MSG ||
trade.getState() == Trade.State.BUYER_SEND_FAILED_FIAT_PAYMENT_INITIATED_MSG) {
// In case we have not received an ACK from the CounterCurrencyTransferStartedMessage we re-send it
// periodically in BuyerSendCounterCurrencyTransferStartedMessage
TradeTaskRunner taskRunner = new TradeTaskRunner(trade,
() -> handleTaskRunnerSuccess("BuyerSendCounterCurrencyTransferStartedMessage"),
this::handleTaskRunnerFault);
taskRunner.addTasks(BuyerSendCounterCurrencyTransferStartedMessage.class);
taskRunner.run();
}
} }
@ -110,8 +122,6 @@ public class BuyerAsTakerProtocol extends TradeProtocol implements BuyerProtocol
handle((DepositTxAndDelayedPayoutTxMessage) tradeMessage, peerNodeAddress); handle((DepositTxAndDelayedPayoutTxMessage) tradeMessage, peerNodeAddress);
} else if (tradeMessage instanceof PayoutTxPublishedMessage) { } else if (tradeMessage instanceof PayoutTxPublishedMessage) {
handle((PayoutTxPublishedMessage) tradeMessage, peerNodeAddress); handle((PayoutTxPublishedMessage) tradeMessage, peerNodeAddress);
} else if (tradeMessage instanceof RefreshTradeStateRequest) {
handle();
} }
} }
@ -264,23 +274,6 @@ public class BuyerAsTakerProtocol extends TradeProtocol implements BuyerProtocol
} }
///////////////////////////////////////////////////////////////////////////////////////////
// Incoming message Handle missing messages
///////////////////////////////////////////////////////////////////////////////////////////
private void handle() {
// Resend CounterCurrencyTransferStartedMessage if it hasn't been acked yet and counterparty asked for a refresh
if (trade.getState().getPhase() == Trade.Phase.FIAT_SENT &&
trade.getState().ordinal() >= Trade.State.BUYER_SENT_FIAT_PAYMENT_INITIATED_MSG.ordinal()) {
TradeTaskRunner taskRunner = new TradeTaskRunner(buyerAsTakerTrade,
() -> handleTaskRunnerSuccess("RefreshTradeStateRequest"),
this::handleTaskRunnerFault);
taskRunner.addTasks(BuyerSendCounterCurrencyTransferStartedMessage.class);
taskRunner.run();
}
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Message dispatcher // Message dispatcher
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -300,8 +293,6 @@ public class BuyerAsTakerProtocol extends TradeProtocol implements BuyerProtocol
handle((DepositTxAndDelayedPayoutTxMessage) tradeMessage, sender); handle((DepositTxAndDelayedPayoutTxMessage) tradeMessage, sender);
} else if (tradeMessage instanceof PayoutTxPublishedMessage) { } else if (tradeMessage instanceof PayoutTxPublishedMessage) {
handle((PayoutTxPublishedMessage) tradeMessage, sender); handle((PayoutTxPublishedMessage) tradeMessage, sender);
} else if (tradeMessage instanceof RefreshTradeStateRequest) {
handle();
} }
} }
} }

View file

@ -25,7 +25,6 @@ import bisq.core.trade.messages.MediatedPayoutTxPublishedMessage;
import bisq.core.trade.messages.MediatedPayoutTxSignatureMessage; import bisq.core.trade.messages.MediatedPayoutTxSignatureMessage;
import bisq.core.trade.messages.PeerPublishedDelayedPayoutTxMessage; import bisq.core.trade.messages.PeerPublishedDelayedPayoutTxMessage;
import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.messages.TraderSignedWitnessMessage;
import bisq.core.trade.protocol.tasks.ApplyFilter; import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.ProcessPeerPublishedDelayedPayoutTxMessage; import bisq.core.trade.protocol.tasks.ProcessPeerPublishedDelayedPayoutTxMessage;
import bisq.core.trade.protocol.tasks.mediation.BroadcastMediatedPayoutTx; import bisq.core.trade.protocol.tasks.mediation.BroadcastMediatedPayoutTx;
@ -227,15 +226,6 @@ public abstract class TradeProtocol {
taskRunner.run(); taskRunner.run();
} }
///////////////////////////////////////////////////////////////////////////////////////////
// Peer has sent a SignedWitness
///////////////////////////////////////////////////////////////////////////////////////////
private void handle(TraderSignedWitnessMessage tradeMessage) {
// Publish signed witness, if it is valid and ours
processModel.getAccountAgeWitnessService().publishOwnSignedWitness(tradeMessage.getSignedWitness());
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Dispatcher // Dispatcher
@ -248,8 +238,6 @@ public abstract class TradeProtocol {
handle((MediatedPayoutTxPublishedMessage) tradeMessage, sender); handle((MediatedPayoutTxPublishedMessage) tradeMessage, sender);
} else if (tradeMessage instanceof PeerPublishedDelayedPayoutTxMessage) { } else if (tradeMessage instanceof PeerPublishedDelayedPayoutTxMessage) {
handle((PeerPublishedDelayedPayoutTxMessage) tradeMessage, sender); handle((PeerPublishedDelayedPayoutTxMessage) tradeMessage, sender);
} else if (tradeMessage instanceof TraderSignedWitnessMessage) {
handle((TraderSignedWitnessMessage) tradeMessage);
} }
} }
@ -297,8 +285,6 @@ public abstract class TradeProtocol {
handle((MediatedPayoutTxPublishedMessage) tradeMessage, peerNodeAddress); handle((MediatedPayoutTxPublishedMessage) tradeMessage, peerNodeAddress);
} else if (tradeMessage instanceof PeerPublishedDelayedPayoutTxMessage) { } else if (tradeMessage instanceof PeerPublishedDelayedPayoutTxMessage) {
handle((PeerPublishedDelayedPayoutTxMessage) tradeMessage, peerNodeAddress); handle((PeerPublishedDelayedPayoutTxMessage) tradeMessage, peerNodeAddress);
} else if (tradeMessage instanceof TraderSignedWitnessMessage) {
handle((TraderSignedWitnessMessage) tradeMessage);
} }
} }

View file

@ -47,7 +47,6 @@ public class ProcessPeerPublishedDelayedPayoutTxMessage extends TradeTask {
// update to the latest peer address of our peer if the message is correct // update to the latest peer address of our peer if the message is correct
trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress()); trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
processModel.removeMailboxMessageAfterProcessing(trade);
// We add the tx to our wallet. // We add the tx to our wallet.
Transaction delayedPayoutTx = checkNotNull(trade.getDelayedPayoutTx()); Transaction delayedPayoutTx = checkNotNull(trade.getDelayedPayoutTx());
@ -58,6 +57,8 @@ public class ProcessPeerPublishedDelayedPayoutTxMessage extends TradeTask {
complete(); complete();
} catch (Throwable t) { } catch (Throwable t) {
failed(t); failed(t);
} finally {
processModel.removeMailboxMessageAfterProcessing(trade);
} }
} }
} }

View file

@ -0,0 +1,92 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.trade.protocol.tasks;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.TradeMessage;
import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.SendMailboxMessageListener;
import bisq.common.taskrunner.TaskRunner;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public abstract class SendMailboxMessageTask extends TradeTask {
public SendMailboxMessageTask(TaskRunner<Trade> taskHandler, Trade trade) {
super(taskHandler, trade);
}
protected abstract TradeMessage getMessage(String id);
protected abstract void setStateSent();
protected abstract void setStateArrived();
protected abstract void setStateStoredInMailbox();
protected abstract void setStateFault();
@Override
protected void run() {
try {
runInterceptHook();
String id = processModel.getOfferId();
TradeMessage message = getMessage(id);
setStateSent();
NodeAddress peersNodeAddress = trade.getTradingPeerNodeAddress();
log.info("Send {} to peer {}. tradeId={}, uid={}",
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
processModel.getP2PService().sendEncryptedMailboxMessage(
peersNodeAddress,
processModel.getTradingPeer().getPubKeyRing(),
message,
new SendMailboxMessageListener() {
@Override
public void onArrived() {
log.info("{} arrived at peer {}. tradeId={}, uid={}",
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
setStateArrived();
complete();
}
@Override
public void onStoredInMailbox() {
log.info("{} stored in mailbox for peer {}. tradeId={}, uid={}",
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
setStateStoredInMailbox();
complete();
}
@Override
public void onFault(String errorMessage) {
log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}",
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid(), errorMessage);
setStateFault();
appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage);
failed(errorMessage);
}
}
);
} catch (Throwable t) {
failed(t);
}
}
}

View file

@ -1,97 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.trade.protocol.tasks;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.TradeMessage;
import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.SendMailboxMessageListener;
import bisq.common.taskrunner.TaskRunner;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public abstract class SendPayoutTxPublishedMessage extends TradeTask {
public SendPayoutTxPublishedMessage(TaskRunner<Trade> taskHandler, Trade trade) {
super(taskHandler, trade);
}
protected abstract TradeMessage getMessage(String id);
protected abstract void setStateSent();
protected abstract void setStateArrived();
protected abstract void setStateStoredInMailbox();
protected abstract void setStateFault();
@Override
protected void run() {
try {
runInterceptHook();
if (trade.getPayoutTx() != null) {
String id = processModel.getOfferId();
TradeMessage message = getMessage(id);
setStateSent();
NodeAddress peersNodeAddress = trade.getTradingPeerNodeAddress();
log.info("Send {} to peer {}. tradeId={}, uid={}",
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
processModel.getP2PService().sendEncryptedMailboxMessage(
peersNodeAddress,
processModel.getTradingPeer().getPubKeyRing(),
message,
new SendMailboxMessageListener() {
@Override
public void onArrived() {
log.info("{} arrived at peer {}. tradeId={}, uid={}",
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
setStateArrived();
complete();
}
@Override
public void onStoredInMailbox() {
log.info("{} stored in mailbox for peer {}. tradeId={}, uid={}",
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
setStateStoredInMailbox();
complete();
}
@Override
public void onFault(String errorMessage) {
log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}",
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid(), errorMessage);
setStateFault();
appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage);
failed(errorMessage);
}
}
);
} else {
log.error("trade.getPayoutTx() = " + trade.getPayoutTx());
failed("PayoutTx is null");
}
} catch (Throwable t) {
failed(t);
}
}
}

View file

@ -65,8 +65,6 @@ public class BuyerProcessDepositTxAndDelayedPayoutTxMessage extends TradeTask {
trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress()); trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
processModel.removeMailboxMessageAfterProcessing(trade);
// If we got already the confirmation we don't want to apply an earlier state // If we got already the confirmation we don't want to apply an earlier state
if (trade.getState().ordinal() < Trade.State.BUYER_SAW_DEPOSIT_TX_IN_NETWORK.ordinal()) { if (trade.getState().ordinal() < Trade.State.BUYER_SAW_DEPOSIT_TX_IN_NETWORK.ordinal()) {
trade.setState(Trade.State.BUYER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG); trade.setState(Trade.State.BUYER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG);
@ -78,6 +76,8 @@ public class BuyerProcessDepositTxAndDelayedPayoutTxMessage extends TradeTask {
complete(); complete();
} catch (Throwable t) { } catch (Throwable t) {
failed(t); failed(t);
} finally {
processModel.removeMailboxMessageAfterProcessing(trade);
} }
} }
} }

View file

@ -17,6 +17,7 @@
package bisq.core.trade.protocol.tasks.buyer; package bisq.core.trade.protocol.tasks.buyer;
import bisq.core.account.sign.SignedWitness;
import bisq.core.btc.model.AddressEntry; import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.wallet.BtcWalletService; import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.WalletService; import bisq.core.btc.wallet.WalletService;
@ -63,10 +64,20 @@ public class BuyerProcessPayoutTxPublishedMessage extends TradeTask {
} else { } else {
log.info("We got the payout tx already set from BuyerSetupPayoutTxListener and do nothing here. trade ID={}", trade.getId()); log.info("We got the payout tx already set from BuyerSetupPayoutTxListener and do nothing here. trade ID={}", trade.getId());
} }
processModel.removeMailboxMessageAfterProcessing(trade);
SignedWitness signedWitness = message.getSignedWitness();
if (signedWitness != null) {
// We received the signedWitness from the seller and publish the data to the network.
// The signer has published it as well but we prefer to re-do it on our side as well to achieve higher
// resilience.
processModel.getAccountAgeWitnessService().publishOwnSignedWitness(signedWitness);
}
complete(); complete();
} catch (Throwable t) { } catch (Throwable t) {
failed(t); failed(t);
} finally {
processModel.removeMailboxMessageAfterProcessing(trade);
} }
} }
} }

View file

@ -18,81 +18,122 @@
package bisq.core.trade.protocol.tasks.buyer; package bisq.core.trade.protocol.tasks.buyer;
import bisq.core.btc.model.AddressEntry; import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.wallet.BtcWalletService; import bisq.core.network.MessageState;
import bisq.core.payment.payload.PaymentMethod;
import bisq.core.trade.Trade; import bisq.core.trade.Trade;
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage; import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
import bisq.core.trade.protocol.tasks.TradeTask; import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.SendMailboxMessageTask;
import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.SendMailboxMessageListener;
import bisq.common.Timer;
import bisq.common.UserThread;
import bisq.common.taskrunner.TaskRunner; import bisq.common.taskrunner.TaskRunner;
import java.util.UUID; import javafx.beans.value.ChangeListener;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j @Slf4j
public class BuyerSendCounterCurrencyTransferStartedMessage extends TradeTask { public class BuyerSendCounterCurrencyTransferStartedMessage extends SendMailboxMessageTask {
private static final long MAX_REFRESH_INTERVAL = TimeUnit.HOURS.toMillis(4);
private ChangeListener<MessageState> listener;
private Timer timer;
private CounterCurrencyTransferStartedMessage counterCurrencyTransferStartedMessage;
public BuyerSendCounterCurrencyTransferStartedMessage(TaskRunner<Trade> taskHandler, Trade trade) { public BuyerSendCounterCurrencyTransferStartedMessage(TaskRunner<Trade> taskHandler, Trade trade) {
super(taskHandler, trade); super(taskHandler, trade);
} }
@Override
protected TradeMessage getMessage(String tradeId) {
if (counterCurrencyTransferStartedMessage == null) {
AddressEntry payoutAddressEntry = processModel.getBtcWalletService().getOrCreateAddressEntry(tradeId,
AddressEntry.Context.TRADE_PAYOUT);
// We do not use a real unique ID here as we want to be able to re-send the exact same message in case the
// peer does not respond with an ACK msg in a certain time interval. To avoid that we get dangling mailbox
// messages where only the one which gets processed by the peer would be removed we use the same uid. All
// other data stays the same when we re-send the message at any time later.
String deterministicId = tradeId + processModel.getMyNodeAddress().getFullAddress();
counterCurrencyTransferStartedMessage = new CounterCurrencyTransferStartedMessage(
tradeId,
payoutAddressEntry.getAddressString(),
processModel.getMyNodeAddress(),
processModel.getPayoutTxSignature(),
trade.getCounterCurrencyTxId(),
trade.getCounterCurrencyExtraData(),
deterministicId
);
}
return counterCurrencyTransferStartedMessage;
}
@Override
protected void setStateSent() {
trade.setState(Trade.State.BUYER_SENT_FIAT_PAYMENT_INITIATED_MSG);
}
@Override
protected void setStateArrived() {
trade.setState(Trade.State.BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG);
stop();
}
@Override
protected void setStateStoredInMailbox() {
trade.setState(Trade.State.BUYER_STORED_IN_MAILBOX_FIAT_PAYMENT_INITIATED_MSG);
start();
}
@Override
protected void setStateFault() {
trade.setState(Trade.State.BUYER_SEND_FAILED_FIAT_PAYMENT_INITIATED_MSG);
start();
}
@Override @Override
protected void run() { protected void run() {
try { try {
runInterceptHook(); runInterceptHook();
BtcWalletService walletService = processModel.getBtcWalletService(); super.run();
final String id = processModel.getOfferId();
AddressEntry payoutAddressEntry = walletService.getOrCreateAddressEntry(id,
AddressEntry.Context.TRADE_PAYOUT);
final CounterCurrencyTransferStartedMessage message = new CounterCurrencyTransferStartedMessage(
id,
payoutAddressEntry.getAddressString(),
processModel.getMyNodeAddress(),
processModel.getPayoutTxSignature(),
trade.getCounterCurrencyTxId(),
trade.getCounterCurrencyExtraData(),
UUID.randomUUID().toString()
);
NodeAddress peersNodeAddress = trade.getTradingPeerNodeAddress();
log.info("Send {} to peer {}. tradeId={}, uid={}",
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
trade.setState(Trade.State.BUYER_SENT_FIAT_PAYMENT_INITIATED_MSG);
processModel.getP2PService().sendEncryptedMailboxMessage(
peersNodeAddress,
processModel.getTradingPeer().getPubKeyRing(),
message,
new SendMailboxMessageListener() {
@Override
public void onArrived() {
log.info("{} arrived at peer {}. tradeId={}, uid={}",
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
trade.setState(Trade.State.BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG);
complete();
}
@Override
public void onStoredInMailbox() {
log.info("{} stored in mailbox for peer {}. tradeId={}, uid={}",
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
trade.setState(Trade.State.BUYER_STORED_IN_MAILBOX_FIAT_PAYMENT_INITIATED_MSG);
complete();
}
@Override
public void onFault(String errorMessage) {
log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}",
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid(), errorMessage);
trade.setState(Trade.State.BUYER_SEND_FAILED_FIAT_PAYMENT_INITIATED_MSG);
appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage);
failed(errorMessage);
}
}
);
} catch (Throwable t) { } catch (Throwable t) {
failed(t); failed(t);
} }
} }
private void stop() {
if (timer != null) {
timer.stop();
processModel.getPaymentStartedMessageStateProperty().removeListener(listener);
}
}
// The listeners ensure we don't get GCed even we have completed the task.
private void start() {
if (timer != null) {
return;
}
PaymentMethod paymentMethod = checkNotNull(trade.getOffer()).getPaymentMethod();
// For instant trades with 1 hour we want a short interval, otherwise a few hours should be ok.
long interval = Math.min(paymentMethod.getMaxTradePeriod() / 5, MAX_REFRESH_INTERVAL);
timer = UserThread.runPeriodically(this::run, interval, TimeUnit.MILLISECONDS);
listener = (observable, oldValue, newValue) -> {
// Once we receive an ACK from our msg we know the peer has received the msg and we stop.
if (newValue == MessageState.ACKNOWLEDGED) {
// We treat a ACK like BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG
trade.setState(Trade.State.BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG);
// Ensure listener construction is completed before remove call
UserThread.execute(this::stop);
}
};
processModel.getPaymentStartedMessageStateProperty().addListener(listener);
}
} }

View file

@ -48,13 +48,14 @@ public class ProcessMediatedPayoutSignatureMessage extends TradeTask {
// update to the latest peer address of our peer if the message is correct // update to the latest peer address of our peer if the message is correct
trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress()); trade.setTradingPeerNodeAddress(processModel.getTempTradingPeerNodeAddress());
processModel.removeMailboxMessageAfterProcessing(trade);
trade.setMediationResultState(MediationResultState.RECEIVED_SIG_MSG); trade.setMediationResultState(MediationResultState.RECEIVED_SIG_MSG);
complete(); complete();
} catch (Throwable t) { } catch (Throwable t) {
failed(t); failed(t);
} finally {
processModel.removeMailboxMessageAfterProcessing(trade);
} }
} }
} }

View file

@ -74,10 +74,11 @@ public class ProcessMediatedPayoutTxPublishedMessage extends TradeTask {
} else { } else {
log.info("We got the payout tx already set from BuyerSetupPayoutTxListener and do nothing here. trade ID={}", trade.getId()); log.info("We got the payout tx already set from BuyerSetupPayoutTxListener and do nothing here. trade ID={}", trade.getId());
} }
processModel.removeMailboxMessageAfterProcessing(trade);
complete(); complete();
} catch (Throwable t) { } catch (Throwable t) {
failed(t); failed(t);
} finally {
processModel.removeMailboxMessageAfterProcessing(trade);
} }
} }
} }

View file

@ -21,7 +21,7 @@ import bisq.core.support.dispute.mediation.MediationResultState;
import bisq.core.trade.Trade; import bisq.core.trade.Trade;
import bisq.core.trade.messages.MediatedPayoutTxPublishedMessage; import bisq.core.trade.messages.MediatedPayoutTxPublishedMessage;
import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.SendPayoutTxPublishedMessage; import bisq.core.trade.protocol.tasks.SendMailboxMessageTask;
import bisq.common.taskrunner.TaskRunner; import bisq.common.taskrunner.TaskRunner;
@ -35,7 +35,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j @Slf4j
public class SendMediatedPayoutTxPublishedMessage extends SendPayoutTxPublishedMessage { public class SendMediatedPayoutTxPublishedMessage extends SendMailboxMessageTask {
public SendMediatedPayoutTxPublishedMessage(TaskRunner<Trade> taskHandler, Trade trade) { public SendMediatedPayoutTxPublishedMessage(TaskRunner<Trade> taskHandler, Trade trade) {
super(taskHandler, trade); super(taskHandler, trade);
} }
@ -76,6 +76,12 @@ public class SendMediatedPayoutTxPublishedMessage extends SendPayoutTxPublishedM
try { try {
runInterceptHook(); runInterceptHook();
if (trade.getPayoutTx() == null) {
log.error("PayoutTx is null");
failed("PayoutTx is null");
return;
}
super.run(); super.run();
} catch (Throwable t) { } catch (Throwable t) {
failed(t); failed(t);

View file

@ -59,13 +59,13 @@ public class SellerProcessCounterCurrencyTransferStartedMessage extends TradeTas
trade.setCounterCurrencyExtraData(counterCurrencyExtraData); trade.setCounterCurrencyExtraData(counterCurrencyExtraData);
} }
processModel.removeMailboxMessageAfterProcessing(trade);
trade.setState(Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG); trade.setState(Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG);
complete(); complete();
} catch (Throwable t) { } catch (Throwable t) {
failed(t); failed(t);
} finally {
processModel.removeMailboxMessageAfterProcessing(trade);
} }
} }
} }

View file

@ -17,23 +17,27 @@
package bisq.core.trade.protocol.tasks.seller; package bisq.core.trade.protocol.tasks.seller;
import bisq.core.account.sign.SignedWitness;
import bisq.core.account.witness.AccountAgeWitnessService;
import bisq.core.trade.Trade; import bisq.core.trade.Trade;
import bisq.core.trade.messages.PayoutTxPublishedMessage; import bisq.core.trade.messages.PayoutTxPublishedMessage;
import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.SendPayoutTxPublishedMessage; import bisq.core.trade.protocol.tasks.SendMailboxMessageTask;
import bisq.common.taskrunner.TaskRunner; import bisq.common.taskrunner.TaskRunner;
import org.bitcoinj.core.Transaction; import org.bitcoinj.core.Transaction;
import java.util.UUID; import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
@EqualsAndHashCode(callSuper = true)
@Slf4j @Slf4j
public class SellerSendPayoutTxPublishedMessage extends SendPayoutTxPublishedMessage { public class SellerSendPayoutTxPublishedMessage extends SendMailboxMessageTask {
SignedWitness signedWitness = null;
public SellerSendPayoutTxPublishedMessage(TaskRunner<Trade> taskHandler, Trade trade) { public SellerSendPayoutTxPublishedMessage(TaskRunner<Trade> taskHandler, Trade trade) {
super(taskHandler, trade); super(taskHandler, trade);
} }
@ -41,32 +45,47 @@ public class SellerSendPayoutTxPublishedMessage extends SendPayoutTxPublishedMes
@Override @Override
protected TradeMessage getMessage(String id) { protected TradeMessage getMessage(String id) {
Transaction payoutTx = checkNotNull(trade.getPayoutTx(), "trade.getPayoutTx() must not be null"); Transaction payoutTx = checkNotNull(trade.getPayoutTx(), "trade.getPayoutTx() must not be null");
AccountAgeWitnessService accountAgeWitnessService = processModel.getAccountAgeWitnessService();
if (accountAgeWitnessService.isSignWitnessTrade(trade)) {
// Broadcast is done in accountAgeWitness domain.
accountAgeWitnessService.traderSignAndPublishPeersAccountAgeWitness(trade).ifPresent(witness -> signedWitness = witness);
}
return new PayoutTxPublishedMessage( return new PayoutTxPublishedMessage(
id, id,
payoutTx.bitcoinSerialize(), payoutTx.bitcoinSerialize(),
processModel.getMyNodeAddress(), processModel.getMyNodeAddress(),
UUID.randomUUID().toString() signedWitness
); );
} }
@Override @Override
protected void setStateSent() { protected void setStateSent() {
trade.setState(Trade.State.SELLER_SENT_PAYOUT_TX_PUBLISHED_MSG); trade.setState(Trade.State.SELLER_SENT_PAYOUT_TX_PUBLISHED_MSG);
log.info("Sent PayoutTxPublishedMessage: tradeId={} at peer {} SignedWitness {}",
trade.getId(), trade.getTradingPeerNodeAddress(), signedWitness);
} }
@Override @Override
protected void setStateArrived() { protected void setStateArrived() {
trade.setState(Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG); trade.setState(Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG);
log.info("PayoutTxPublishedMessage arrived: tradeId={} at peer {} SignedWitness {}",
trade.getId(), trade.getTradingPeerNodeAddress(), signedWitness);
} }
@Override @Override
protected void setStateStoredInMailbox() { protected void setStateStoredInMailbox() {
trade.setState(Trade.State.SELLER_STORED_IN_MAILBOX_PAYOUT_TX_PUBLISHED_MSG); trade.setState(Trade.State.SELLER_STORED_IN_MAILBOX_PAYOUT_TX_PUBLISHED_MSG);
log.info("PayoutTxPublishedMessage storedInMailbox: tradeId={} at peer {} SignedWitness {}",
trade.getId(), trade.getTradingPeerNodeAddress(), signedWitness);
} }
@Override @Override
protected void setStateFault() { protected void setStateFault() {
trade.setState(Trade.State.SELLER_SEND_FAILED_PAYOUT_TX_PUBLISHED_MSG); trade.setState(Trade.State.SELLER_SEND_FAILED_PAYOUT_TX_PUBLISHED_MSG);
log.error("PayoutTxPublishedMessage failed: tradeId={} at peer {} SignedWitness {}",
trade.getId(), trade.getTradingPeerNodeAddress(), signedWitness);
} }
@Override @Override
@ -74,6 +93,12 @@ public class SellerSendPayoutTxPublishedMessage extends SendPayoutTxPublishedMes
try { try {
runInterceptHook(); runInterceptHook();
if (trade.getPayoutTx() == null) {
log.error("PayoutTx is null");
failed("PayoutTx is null");
return;
}
super.run(); super.run();
} catch (Throwable t) { } catch (Throwable t) {
failed(t); failed(t);

View file

@ -354,7 +354,7 @@ public class SignedWitnessServiceTest {
when(keyRing.getSignatureKeyPair()).thenReturn(signerKeyPair); when(keyRing.getSignatureKeyPair()).thenReturn(signerKeyPair);
AccountAgeWitness accountAgeWitness = new AccountAgeWitness(account1DataHash, accountCreationTime); AccountAgeWitness accountAgeWitness = new AccountAgeWitness(account1DataHash, accountCreationTime);
signedWitnessService.signAccountAgeWitness(Coin.ZERO, accountAgeWitness, peerKeyPair.getPublic()); signedWitnessService.signAndPublishAccountAgeWitness(Coin.ZERO, accountAgeWitness, peerKeyPair.getPublic());
verify(p2pService, never()).addPersistableNetworkPayload(any(PersistableNetworkPayload.class), anyBoolean()); verify(p2pService, never()).addPersistableNetworkPayload(any(PersistableNetworkPayload.class), anyBoolean());
} }
@ -370,7 +370,7 @@ public class SignedWitnessServiceTest {
AccountAgeWitness accountAgeWitness = new AccountAgeWitness(account1DataHash, accountCreationTime); AccountAgeWitness accountAgeWitness = new AccountAgeWitness(account1DataHash, accountCreationTime);
signedWitnessService.signAccountAgeWitness(SignedWitnessService.MINIMUM_TRADE_AMOUNT_FOR_SIGNING, accountAgeWitness, peerKeyPair.getPublic()); signedWitnessService.signAndPublishAccountAgeWitness(SignedWitnessService.MINIMUM_TRADE_AMOUNT_FOR_SIGNING, accountAgeWitness, peerKeyPair.getPublic());
verify(p2pService, times(1)).addPersistableNetworkPayload(any(PersistableNetworkPayload.class), anyBoolean()); verify(p2pService, times(1)).addPersistableNetworkPayload(any(PersistableNetworkPayload.class), anyBoolean());
} }

View file

@ -301,7 +301,7 @@ public class AccountAgeWitnessServiceTest {
signAccountAgeWitness(aew2, pubKeyRing2.getSignaturePubKey(), aew2.getDate(), user1KeyRing); signAccountAgeWitness(aew2, pubKeyRing2.getSignaturePubKey(), aew2.getDate(), user1KeyRing);
// user2 signs user3 // user2 signs user3
signAccountAgeWitness(aew3, pubKeyRing3.getSignaturePubKey(), aew3.getDate(), user2KeyRing); signAccountAgeWitness(aew3, pubKeyRing3.getSignaturePubKey(), aew3.getDate(), user2KeyRing);
signedWitnessService.signAccountAgeWitness(SignedWitnessService.MINIMUM_TRADE_AMOUNT_FOR_SIGNING, aew2, signedWitnessService.signAndPublishAccountAgeWitness(SignedWitnessService.MINIMUM_TRADE_AMOUNT_FOR_SIGNING, aew2,
pubKeyRing2.getSignaturePubKey()); pubKeyRing2.getSignaturePubKey());
assertTrue(service.accountIsSigner(aew1)); assertTrue(service.accountIsSigner(aew1));
assertTrue(service.accountIsSigner(aew2)); assertTrue(service.accountIsSigner(aew2));

View file

@ -130,7 +130,7 @@ class FiatAccountsDataModel extends ActivatableDataModel {
user.addPaymentAccount(paymentAccount); user.addPaymentAccount(paymentAccount);
accountAgeWitnessService.publishMyAccountAgeWitness(paymentAccount.getPaymentAccountPayload()); accountAgeWitnessService.publishMyAccountAgeWitness(paymentAccount.getPaymentAccountPayload());
accountAgeWitnessService.signSameNameAccounts(); accountAgeWitnessService.signAndPublishSameNameAccounts();
} }
public boolean onDeleteAccount(PaymentAccount paymentAccount) { public boolean onDeleteAccount(PaymentAccount paymentAccount) {

View file

@ -50,13 +50,10 @@ import bisq.core.trade.BuyerTrade;
import bisq.core.trade.SellerTrade; import bisq.core.trade.SellerTrade;
import bisq.core.trade.Trade; import bisq.core.trade.Trade;
import bisq.core.trade.TradeManager; import bisq.core.trade.TradeManager;
import bisq.core.trade.messages.RefreshTradeStateRequest;
import bisq.core.user.Preferences; import bisq.core.user.Preferences;
import bisq.core.util.FormattingUtils; import bisq.core.util.FormattingUtils;
import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.P2PService; import bisq.network.p2p.P2PService;
import bisq.network.p2p.SendMailboxMessageListener;
import bisq.common.crypto.PubKeyRing; import bisq.common.crypto.PubKeyRing;
import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ErrorMessageHandler;
@ -81,7 +78,6 @@ import javafx.collections.ObservableList;
import org.bouncycastle.crypto.params.KeyParameter; import org.bouncycastle.crypto.params.KeyParameter;
import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import lombok.Getter; import lombok.Getter;
@ -237,43 +233,6 @@ public class PendingTradesDataModel extends ActivatableDataModel {
tradeManager.addTradeToFailedTrades(getTrade()); tradeManager.addTradeToFailedTrades(getTrade());
} }
// Ask counterparty to resend last action (in case message was lost)
public void refreshTradeState() {
Trade trade = getTrade();
if (trade == null || !trade.allowedRefresh()) return;
trade.logRefresh();
NodeAddress tradingPeerNodeAddress = trade.getTradingPeerNodeAddress();
RefreshTradeStateRequest refreshReq = new RefreshTradeStateRequest(UUID.randomUUID().toString(),
trade.getId(),
tradingPeerNodeAddress);
p2PService.sendEncryptedMailboxMessage(
tradingPeerNodeAddress,
trade.getProcessModel().getTradingPeer().getPubKeyRing(),
refreshReq,
new SendMailboxMessageListener() {
@Override
public void onArrived() {
log.info("SendMailboxMessageListener onArrived tradeId={} at peer {}",
trade.getId(), tradingPeerNodeAddress);
}
@Override
public void onStoredInMailbox() {
log.info("SendMailboxMessageListener onStoredInMailbox tradeId={} at peer {}",
trade.getId(), tradingPeerNodeAddress);
}
@Override
public void onFault(String errorMessage) {
log.error("SendMailboxMessageListener onFault tradeId={} at peer {}",
trade.getId(), tradingPeerNodeAddress);
}
}
);
tradeManager.persistTrades();
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Getters // Getters

View file

@ -216,8 +216,6 @@ public class PendingTradesView extends ActivatableViewAndModel<VBox, PendingTrad
new Popup().warning(Res.get("portfolio.pending.removeFailedTrade")) new Popup().warning(Res.get("portfolio.pending.removeFailedTrade"))
.onAction(model.dataModel::onMoveToFailedTrades) .onAction(model.dataModel::onMoveToFailedTrades)
.show(); .show();
} else if (Utilities.isAltOrCtrlPressed(KeyCode.R, keyEvent)) {
model.dataModel.refreshTradeState();
} }
}; };

View file

@ -24,27 +24,12 @@ import bisq.desktop.util.Layout;
import bisq.core.locale.Res; import bisq.core.locale.Res;
import bisq.core.payment.payload.F2FAccountPayload; import bisq.core.payment.payload.F2FAccountPayload;
import bisq.core.trade.Trade;
import bisq.common.Timer;
import bisq.common.UserThread;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import static bisq.desktop.util.FormBuilder.addButtonAfterGroup;
import static bisq.desktop.util.FormBuilder.addMultilineLabel;
import static bisq.desktop.util.FormBuilder.addTitledGroupBg; import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
public class SellerStep2View extends TradeStepView { public class SellerStep2View extends TradeStepView {
private GridPane refreshButtonPane;
private Timer timer;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, Initialisation // Constructor, Initialisation
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -65,61 +50,16 @@ public class SellerStep2View extends TradeStepView {
gridRow = F2FForm.addFormForBuyer(gridPane, --gridRow, model.dataModel.getSellersPaymentAccountPayload(), gridRow = F2FForm.addFormForBuyer(gridPane, --gridRow, model.dataModel.getSellersPaymentAccountPayload(),
model.dataModel.getTrade().getOffer(), Layout.COMPACT_FIRST_ROW_AND_GROUP_DISTANCE); model.dataModel.getTrade().getOffer(), Layout.COMPACT_FIRST_ROW_AND_GROUP_DISTANCE);
} }
addRefreshBlock();
} }
private void addRefreshBlock() {
refreshButtonPane = new GridPane();
addTitledGroupBg(refreshButtonPane, 0, 1,
Res.get("portfolio.pending.step2_seller.refresh"), Layout.COMPACT_GROUP_DISTANCE);
addMultilineLabel(refreshButtonPane, 1, Res.get("portfolio.pending.step2_seller.refreshInfo"),
Layout.COMPACT_FIRST_ROW_DISTANCE);
Button refreshButton = addButtonAfterGroup(refreshButtonPane, 2, Res.get("portfolio.pending.step2_seller.refresh"));
refreshButton.setOnAction(event -> onRefreshButton());
GridPane.setRowIndex(refreshButtonPane, ++gridRow);
GridPane.setColumnIndex(refreshButtonPane, 0);
GridPane.setColumnSpan(refreshButtonPane, 2);
gridPane.getChildren().add(refreshButtonPane);
}
@Override @Override
public void activate() { public void activate() {
super.activate(); super.activate();
activateRefreshButton();
} }
@Override @Override
public void deactivate() { public void deactivate() {
super.deactivate(); super.deactivate();
deActivateRefreshButtonTimer();
}
private void activateRefreshButton() {
checkNotNull(model.dataModel.getTrade(), "No trade found");
Trade trade = model.dataModel.getTrade();
var timeToNextRefresh =
trade.getLastRefreshRequestDate() + trade.getRefreshInterval() - new Date().getTime();
if (timeToNextRefresh <= 0) {
refreshButtonPane.setVisible(true);
} else {
refreshButtonPane.setVisible(false);
timer = UserThread.runAfter(this::activateRefreshButton, timeToNextRefresh, TimeUnit.MILLISECONDS);
}
}
private void deActivateRefreshButtonTimer() {
if (timer != null) {
timer.stop();
}
}
private void onRefreshButton() {
model.dataModel.refreshTradeState();
activateRefreshButton();
} }

View file

@ -76,7 +76,7 @@ message NetworkEnvelope {
DepositTxAndDelayedPayoutTxMessage deposit_tx_and_delayed_payout_tx_message = 48; DepositTxAndDelayedPayoutTxMessage deposit_tx_and_delayed_payout_tx_message = 48;
PeerPublishedDelayedPayoutTxMessage peer_published_delayed_payout_tx_message = 49; PeerPublishedDelayedPayoutTxMessage peer_published_delayed_payout_tx_message = 49;
RefreshTradeStateRequest refresh_trade_state_request = 50; RefreshTradeStateRequest refresh_trade_state_request = 50 [deprecated = true];
TraderSignedWitnessMessage trader_signed_witness_message = 51; TraderSignedWitnessMessage trader_signed_witness_message = 51;
} }
} }
@ -309,6 +309,7 @@ message PayoutTxPublishedMessage {
bytes payout_tx = 2; bytes payout_tx = 2;
NodeAddress sender_node_address = 3; NodeAddress sender_node_address = 3;
string uid = 4; string uid = 4;
SignedWitness signed_witness = 5; // Added in v1.4.0
} }
message MediatedPayoutTxPublishedMessage { message MediatedPayoutTxPublishedMessage {
@ -325,17 +326,19 @@ message MediatedPayoutTxSignatureMessage {
NodeAddress sender_node_address = 4; NodeAddress sender_node_address = 4;
} }
// Deprecated since 1.4.0
message RefreshTradeStateRequest { message RefreshTradeStateRequest {
string uid = 1; string uid = 1 [deprecated = true];
string trade_id = 2; string trade_id = 2 [deprecated = true];
NodeAddress sender_node_address = 3; NodeAddress sender_node_address = 3 [deprecated = true];
} }
// Deprecated since 1.4.0
message TraderSignedWitnessMessage { message TraderSignedWitnessMessage {
string uid = 1; string uid = 1 [deprecated = true];
string trade_id = 2; string trade_id = 2 [deprecated = true];
NodeAddress sender_node_address = 3; NodeAddress sender_node_address = 3 [deprecated = true];
SignedWitness signed_witness = 4; SignedWitness signed_witness = 4 [deprecated = true];
} }
// dispute // dispute