diff --git a/apitest/src/test/java/bisq/apitest/method/trade/AbstractTradeTest.java b/apitest/src/test/java/bisq/apitest/method/trade/AbstractTradeTest.java index 58d0ca0481..36be727969 100644 --- a/apitest/src/test/java/bisq/apitest/method/trade/AbstractTradeTest.java +++ b/apitest/src/test/java/bisq/apitest/method/trade/AbstractTradeTest.java @@ -14,7 +14,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.TestInfo; import static bisq.cli.table.builder.TableType.TRADE_DETAIL_TBL; -import static bisq.core.trade.Trade.Phase.DEPOSIT_UNLOCKED; +import static bisq.core.trade.Trade.Phase.DEPOSITS_UNLOCKED; import static bisq.core.trade.Trade.Phase.PAYMENT_SENT; import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED; import static bisq.core.trade.Trade.State.BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG; @@ -80,7 +80,7 @@ public class AbstractTradeTest extends AbstractOfferTest { String tradeId) { Predicate isTradeInDepositUnlockedStateAndPhase = (t) -> t.getState().equals(DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN.name()) - && t.getPhase().equals(DEPOSIT_UNLOCKED.name()); + && t.getPhase().equals(DEPOSITS_UNLOCKED.name()); String userName = toUserName.apply(grpcClient); for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) { @@ -95,7 +95,7 @@ public class AbstractTradeTest extends AbstractOfferTest { genBtcBlocksThenWait(1, 4_000); } else { EXPECTED_PROTOCOL_STATUS.setState(DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN) - .setPhase(DEPOSIT_UNLOCKED) + .setPhase(DEPOSITS_UNLOCKED) .setDepositPublished(true) .setDepositConfirmed(true); verifyExpectedProtocolStatus(trade); diff --git a/core/src/main/java/bisq/core/api/CoreApi.java b/core/src/main/java/bisq/core/api/CoreApi.java index c0e92c8bc9..e6acd109b0 100644 --- a/core/src/main/java/bisq/core/api/CoreApi.java +++ b/core/src/main/java/bisq/core/api/CoreApi.java @@ -406,8 +406,12 @@ public class CoreApi { // Dispute Agents /////////////////////////////////////////////////////////////////////////////////////////// - public void registerDisputeAgent(String disputeAgentType, String registrationKey) { - coreDisputeAgentsService.registerDisputeAgent(disputeAgentType, registrationKey); + public void registerDisputeAgent(String disputeAgentType, String registrationKey, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { + coreDisputeAgentsService.registerDisputeAgent(disputeAgentType, registrationKey, resultHandler, errorMessageHandler); + } + + public void unregisterDisputeAgent(String disputeAgentType, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { + coreDisputeAgentsService.unregisterDisputeAgent(disputeAgentType, resultHandler, errorMessageHandler); } /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/api/CoreDisputeAgentsService.java b/core/src/main/java/bisq/core/api/CoreDisputeAgentsService.java index 8c5d6cfe3c..6de591a7aa 100644 --- a/core/src/main/java/bisq/core/api/CoreDisputeAgentsService.java +++ b/core/src/main/java/bisq/core/api/CoreDisputeAgentsService.java @@ -31,7 +31,8 @@ import bisq.network.p2p.P2PService; import bisq.common.config.Config; import bisq.common.crypto.KeyRing; - +import bisq.common.handlers.ErrorMessageHandler; +import bisq.common.handlers.ResultHandler; import org.bitcoinj.core.ECKey; import javax.inject.Inject; @@ -88,14 +89,10 @@ class CoreDisputeAgentsService { this.languageCodes = asList("de", "en", "es", "fr"); } - void registerDisputeAgent(String disputeAgentType, String registrationKey) { + void registerDisputeAgent(String disputeAgentType, String registrationKey, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { if (!p2PService.isBootstrapped()) throw new IllegalStateException("p2p service is not bootstrapped yet"); - if (config.baseCurrencyNetwork.isMainnet() - || !config.useLocalhostForP2P) - throw new IllegalStateException("dispute agents must be registered in a Bisq UI"); - Optional supportType = getSupportType(disputeAgentType); if (supportType.isPresent()) { ECKey ecKey; @@ -104,16 +101,18 @@ class CoreDisputeAgentsService { case ARBITRATION: if (user.getRegisteredArbitrator() != null) { log.warn("ignoring request to re-register as arbitrator"); + resultHandler.handleResult(); return; } ecKey = arbitratorManager.getRegistrationKey(registrationKey); if (ecKey == null) throw new IllegalStateException("invalid registration key"); signature = arbitratorManager.signStorageSignaturePubKey(Objects.requireNonNull(ecKey)); - registerArbitrator(nodeAddress, languageCodes, ecKey, signature); + registerArbitrator(nodeAddress, languageCodes, ecKey, signature, resultHandler, errorMessageHandler); return; case MEDIATION: if (user.getRegisteredMediator() != null) { log.warn("ignoring request to re-register as mediator"); + resultHandler.handleResult(); return; } ecKey = mediatorManager.getRegistrationKey(registrationKey); @@ -124,6 +123,7 @@ class CoreDisputeAgentsService { case REFUND: if (user.getRegisteredRefundAgent() != null) { log.warn("ignoring request to re-register as refund agent"); + resultHandler.handleResult(); return; } ecKey = refundAgentManager.getRegistrationKey(registrationKey); @@ -139,10 +139,38 @@ class CoreDisputeAgentsService { } } + void unregisterDisputeAgent(String disputeAgentType, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { + if (!p2PService.isBootstrapped()) + throw new IllegalStateException("p2p service is not bootstrapped yet"); + + Optional supportType = getSupportType(disputeAgentType); + if (supportType.isPresent()) { + switch (supportType.get()) { + case ARBITRATION: + if (user.getRegisteredArbitrator() == null) { + errorMessageHandler.handleErrorMessage("User is not arbitrator"); + return; + } + unregisterDisputeAgent(resultHandler, errorMessageHandler); + return; + case MEDIATION: + throw new IllegalStateException("unregister mediator not implemented"); + case REFUND: + throw new IllegalStateException("unregister refund agent not implemented"); + case TRADE: + throw new IllegalArgumentException("trade agent registration not supported"); + } + } else { + throw new IllegalArgumentException(format("unknown dispute agent type '%s'", disputeAgentType)); + } + } + private void registerArbitrator(NodeAddress nodeAddress, List languageCodes, ECKey ecKey, - String signature) { + String signature, + ResultHandler resultHandler, + ErrorMessageHandler errorMessageHandler) { Arbitrator arbitrator = new Arbitrator( p2PService.getAddress(), xmrWalletService.getWallet().getPrimaryAddress(), // TODO: how is this used? @@ -155,10 +183,9 @@ class CoreDisputeAgentsService { null, null); arbitratorManager.addDisputeAgent(arbitrator, () -> { - }, errorMessage -> { - }); - arbitratorManager.getDisputeAgentByNodeAddress(nodeAddress).orElseThrow(() -> - new IllegalStateException("could not register arbitrator")); + if (!arbitratorManager.getDisputeAgentByNodeAddress(nodeAddress).isPresent()) errorMessageHandler.handleErrorMessage("could not register arbitrator"); + else resultHandler.handleResult(); + }, errorMessageHandler); } private void registerMediator(NodeAddress nodeAddress, @@ -219,4 +246,10 @@ class CoreDisputeAgentsService { return Optional.empty(); } } + + private void unregisterDisputeAgent(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { + arbitratorManager.removeDisputeAgent(resultHandler, errorMesage -> { + errorMessageHandler.handleErrorMessage("Error unregistering dispute agent: " + errorMesage); + }); + } } diff --git a/core/src/main/java/bisq/core/api/model/OfferInfo.java b/core/src/main/java/bisq/core/api/model/OfferInfo.java index 88ec23ab63..3fe9adcec0 100644 --- a/core/src/main/java/bisq/core/api/model/OfferInfo.java +++ b/core/src/main/java/bisq/core/api/model/OfferInfo.java @@ -74,6 +74,8 @@ public class OfferInfo implements Payload { private final String pubKeyRing; private final String versionNumber; private final int protocolVersion; + @Nullable + private final String arbitratorSigner; public OfferInfo(OfferInfoBuilder builder) { this.id = builder.getId(); @@ -104,6 +106,7 @@ public class OfferInfo implements Payload { this.pubKeyRing = builder.getPubKeyRing(); this.versionNumber = builder.getVersionNumber(); this.protocolVersion = builder.getProtocolVersion(); + this.arbitratorSigner = builder.getArbitratorSigner(); } public static OfferInfo toOfferInfo(Offer offer) { @@ -166,7 +169,8 @@ public class OfferInfo implements Payload { .withOwnerNodeAddress(offer.getOfferPayload().getOwnerNodeAddress().getFullAddress()) .withPubKeyRing(offer.getOfferPayload().getPubKeyRing().toString()) .withVersionNumber(offer.getOfferPayload().getVersionNr()) - .withProtocolVersion(offer.getOfferPayload().getProtocolVersion()); + .withProtocolVersion(offer.getOfferPayload().getProtocolVersion()) + .withArbitratorSigner(offer.getOfferPayload().getArbitratorSigner() == null ? null : offer.getOfferPayload().getArbitratorSigner().getFullAddress()); } /////////////////////////////////////////////////////////////////////////////////////////// @@ -203,6 +207,7 @@ public class OfferInfo implements Payload { .setPubKeyRing(pubKeyRing) .setVersionNr(versionNumber) .setProtocolVersion(protocolVersion); + Optional.ofNullable(arbitratorSigner).ifPresent(builder::setArbitratorSigner); Optional.ofNullable(offerFeePaymentTxId).ifPresent(builder::setOfferFeePaymentTxId); return builder.build(); } @@ -238,6 +243,7 @@ public class OfferInfo implements Payload { .withPubKeyRing(proto.getPubKeyRing()) .withVersionNumber(proto.getVersionNr()) .withProtocolVersion(proto.getProtocolVersion()) + .withArbitratorSigner(proto.getArbitratorSigner()) .build(); } } diff --git a/core/src/main/java/bisq/core/api/model/TradeInfo.java b/core/src/main/java/bisq/core/api/model/TradeInfo.java index b53142edcc..17e25daee7 100644 --- a/core/src/main/java/bisq/core/api/model/TradeInfo.java +++ b/core/src/main/java/bisq/core/api/model/TradeInfo.java @@ -46,6 +46,11 @@ public class TradeInfo implements Payload { ? "" : trade.getTradingPeerNodeAddress().getFullAddress(); + private static final Function toArbitratorNodeAddress = (trade) -> + trade.getArbitratorNodeAddress() == null + ? "" + : trade.getArbitratorNodeAddress().getFullAddress(); + private static final Function toRoundedVolume = (trade) -> trade.getVolume() == null ? "" @@ -70,6 +75,7 @@ public class TradeInfo implements Payload { private final long amountAsLong; private final String price; private final String volume; + private final String arbitratorNodeAddress; private final String tradingPeerNodeAddress; private final String state; private final String phase; @@ -98,6 +104,7 @@ public class TradeInfo implements Payload { this.amountAsLong = builder.getAmountAsLong(); this.price = builder.getPrice(); this.volume = builder.getVolume(); + this.arbitratorNodeAddress = builder.getArbitratorNodeAddress(); this.tradingPeerNodeAddress = builder.getTradingPeerNodeAddress(); this.state = builder.getState(); this.phase = builder.getPhase(); @@ -149,6 +156,7 @@ public class TradeInfo implements Payload { .withAmountAsLong(trade.getAmountAsLong()) .withPrice(toPreciseTradePrice.apply(trade)) .withVolume(toRoundedVolume.apply(trade)) + .withArbitratorNodeAddress(toArbitratorNodeAddress.apply(trade)) .withTradingPeerNodeAddress(toPeerNodeAddress.apply(trade)) .withState(trade.getState().name()) .withPhase(trade.getPhase().name()) @@ -186,6 +194,7 @@ public class TradeInfo implements Payload { .setAmountAsLong(amountAsLong) .setPrice(price) .setTradeVolume(volume) + .setArbitratorNodeAddress(arbitratorNodeAddress) .setTradingPeerNodeAddress(tradingPeerNodeAddress) .setState(state) .setPhase(phase) @@ -220,6 +229,7 @@ public class TradeInfo implements Payload { .withPeriodState(proto.getPeriodState()) .withState(proto.getState()) .withPhase(proto.getPhase()) + .withArbitratorNodeAddress(proto.getArbitratorNodeAddress()) .withTradingPeerNodeAddress(proto.getTradingPeerNodeAddress()) .withIsDepositPublished(proto.getIsDepositPublished()) .withIsDepositUnlocked(proto.getIsDepositUnlocked()) @@ -247,6 +257,7 @@ public class TradeInfo implements Payload { ", payoutTxId='" + payoutTxId + '\'' + "\n" + ", amountAsLong='" + amountAsLong + '\'' + "\n" + ", price='" + price + '\'' + "\n" + + ", arbitratorNodeAddress='" + arbitratorNodeAddress + '\'' + "\n" + ", tradingPeerNodeAddress='" + tradingPeerNodeAddress + '\'' + "\n" + ", state='" + state + '\'' + "\n" + ", phase='" + phase + '\'' + "\n" + diff --git a/core/src/main/java/bisq/core/api/model/builder/OfferInfoBuilder.java b/core/src/main/java/bisq/core/api/model/builder/OfferInfoBuilder.java index 3948db907d..cef3431259 100644 --- a/core/src/main/java/bisq/core/api/model/builder/OfferInfoBuilder.java +++ b/core/src/main/java/bisq/core/api/model/builder/OfferInfoBuilder.java @@ -61,6 +61,7 @@ public final class OfferInfoBuilder { private String pubKeyRing; private String versionNumber; private int protocolVersion; + private String arbitratorSigner; public OfferInfoBuilder withId(String id) { this.id = id; @@ -217,6 +218,11 @@ public final class OfferInfoBuilder { return this; } + public OfferInfoBuilder withArbitratorSigner(String arbitratorSigner) { + this.arbitratorSigner = arbitratorSigner; + return this; + } + public OfferInfo build() { return new OfferInfo(this); } diff --git a/core/src/main/java/bisq/core/api/model/builder/TradeInfoV1Builder.java b/core/src/main/java/bisq/core/api/model/builder/TradeInfoV1Builder.java index eefd0e296c..512045b320 100644 --- a/core/src/main/java/bisq/core/api/model/builder/TradeInfoV1Builder.java +++ b/core/src/main/java/bisq/core/api/model/builder/TradeInfoV1Builder.java @@ -47,6 +47,7 @@ public final class TradeInfoV1Builder { private long amountAsLong; private String price; private String volume; + private String arbitratorNodeAddress; private String tradingPeerNodeAddress; private String state; private String phase; @@ -151,6 +152,11 @@ public final class TradeInfoV1Builder { return this; } + public TradeInfoV1Builder withArbitratorNodeAddress(String arbitratorNodeAddress) { + this.arbitratorNodeAddress = arbitratorNodeAddress; + return this; + } + public TradeInfoV1Builder withTradingPeerNodeAddress(String tradingPeerNodeAddress) { this.tradingPeerNodeAddress = tradingPeerNodeAddress; return this; diff --git a/core/src/main/java/bisq/core/notifications/alerts/TradeEvents.java b/core/src/main/java/bisq/core/notifications/alerts/TradeEvents.java index 459f3b7769..8731e309fc 100644 --- a/core/src/main/java/bisq/core/notifications/alerts/TradeEvents.java +++ b/core/src/main/java/bisq/core/notifications/alerts/TradeEvents.java @@ -72,7 +72,7 @@ public class TradeEvents { case DEPOSIT_REQUESTED: case DEPOSITS_PUBLISHED: break; - case DEPOSIT_UNLOCKED: + case DEPOSITS_UNLOCKED: if (trade.getContract() != null && pubKeyRingProvider.get().equals(trade.getContract().getBuyerPubKeyRing())) msg = Res.get("account.notifications.trade.message.msg.conf", shortId); break; diff --git a/core/src/main/java/bisq/core/offer/CreateOfferService.java b/core/src/main/java/bisq/core/offer/CreateOfferService.java index 2e8f1c9750..95f2c04470 100644 --- a/core/src/main/java/bisq/core/offer/CreateOfferService.java +++ b/core/src/main/java/bisq/core/offer/CreateOfferService.java @@ -154,14 +154,6 @@ public class CreateOfferService { boolean isCryptoCurrency = CurrencyUtil.isCryptoCurrency(currencyCode); String baseCurrencyCode = isCryptoCurrency ? currencyCode : Res.getBaseCurrencyCode(); String counterCurrencyCode = isCryptoCurrency ? Res.getBaseCurrencyCode() : currencyCode; - List acceptedArbitratorAddresses = user.getAcceptedArbitratorAddresses(); - ArrayList arbitratorNodeAddresses = acceptedArbitratorAddresses != null ? - Lists.newArrayList(acceptedArbitratorAddresses) : - new ArrayList<>(); - List acceptedMediatorAddresses = user.getAcceptedMediatorAddresses(); - ArrayList mediatorNodeAddresses = acceptedMediatorAddresses != null ? - Lists.newArrayList(acceptedMediatorAddresses) : - new ArrayList<>(); String countryCode = PaymentAccountUtil.getCountryCode(paymentAccount); List acceptedCountryCodes = PaymentAccountUtil.getAcceptedCountryCodes(paymentAccount); String bankId = PaymentAccountUtil.getBankId(paymentAccount); @@ -191,10 +183,6 @@ public class CreateOfferService { currencyCode, makerFeeAsCoin); - // select signing arbitrator - Arbitrator arbitrator = DisputeAgentSelection.getLeastUsedArbitrator(tradeStatisticsManager, arbitratorManager); - if (arbitrator == null) throw new RuntimeException("No arbitrators available"); - OfferPayload offerPayload = new OfferPayload(offerId, creationTime, makerAddress, @@ -230,7 +218,7 @@ public class CreateOfferService { hashOfChallenge, extraDataMap, Version.TRADE_PROTOCOL_VERSION, - arbitrator.getNodeAddress(), + null, null, null); Offer offer = new Offer(offerPayload); diff --git a/core/src/main/java/bisq/core/offer/OfferPayload.java b/core/src/main/java/bisq/core/offer/OfferPayload.java index 6e1ce18e39..cb19a87ffa 100644 --- a/core/src/main/java/bisq/core/offer/OfferPayload.java +++ b/core/src/main/java/bisq/core/offer/OfferPayload.java @@ -78,6 +78,7 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay // address and signature of signing arbitrator @Setter + @Nullable protected NodeAddress arbitratorSigner; @Setter @Nullable @@ -192,7 +193,7 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay @Nullable String hashOfChallenge, @Nullable Map extraDataMap, int protocolVersion, - NodeAddress arbitratorSigner, + @Nullable NodeAddress arbitratorSigner, @Nullable String arbitratorSignature, @Nullable List reserveTxKeyImages) { this.id = id; @@ -297,8 +298,8 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay .setLowerClosePrice(lowerClosePrice) .setUpperClosePrice(upperClosePrice) .setIsPrivateOffer(isPrivateOffer) - .setProtocolVersion(protocolVersion) - .setArbitratorSigner(arbitratorSigner.toProtoMessage()); + .setProtocolVersion(protocolVersion); + Optional.ofNullable(arbitratorSigner).ifPresent(e -> builder.setArbitratorSigner(arbitratorSigner.toProtoMessage())); Optional.ofNullable(offerFeePaymentTxId).ifPresent(builder::setOfferFeePaymentTxId); Optional.ofNullable(countryCode).ifPresent(builder::setCountryCode); Optional.ofNullable(bankId).ifPresent(builder::setBankId); @@ -356,7 +357,7 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay hashOfChallenge, extraDataMapMap, proto.getProtocolVersion(), - NodeAddress.fromProto(proto.getArbitratorSigner()), + proto.hasArbitratorSigner() ? NodeAddress.fromProto(proto.getArbitratorSigner()) : null, ProtoUtil.stringOrNullFromProto(proto.getArbitratorSignature()), proto.getReserveTxKeyImagesList() == null ? null : new ArrayList(proto.getReserveTxKeyImagesList())); } diff --git a/core/src/main/java/bisq/core/offer/OpenOffer.java b/core/src/main/java/bisq/core/offer/OpenOffer.java index d18922966b..365a9c686c 100644 --- a/core/src/main/java/bisq/core/offer/OpenOffer.java +++ b/core/src/main/java/bisq/core/offer/OpenOffer.java @@ -19,8 +19,6 @@ package bisq.core.offer; import bisq.core.trade.Tradable; -import bisq.network.p2p.NodeAddress; - import bisq.common.Timer; import bisq.common.UserThread; import bisq.common.proto.ProtoUtil; @@ -55,10 +53,6 @@ public final class OpenOffer implements Tradable { private final Offer offer; @Getter private State state; - @Getter - @Setter - @Nullable - private NodeAddress backupArbitrator; @Setter @Getter private boolean autoSplit; @@ -113,7 +107,6 @@ public final class OpenOffer implements Tradable { private OpenOffer(Offer offer, State state, - @Nullable NodeAddress backupArbitrator, long triggerPrice, boolean autoSplit, @Nullable String scheduledAmount, @@ -123,7 +116,6 @@ public final class OpenOffer implements Tradable { @Nullable String reserveTxKey) { this.offer = offer; this.state = state; - this.backupArbitrator = backupArbitrator; this.triggerPrice = triggerPrice; this.autoSplit = autoSplit; this.scheduledTxHashes = scheduledTxHashes; @@ -144,7 +136,6 @@ public final class OpenOffer implements Tradable { .setAutoSplit(autoSplit); Optional.ofNullable(scheduledAmount).ifPresent(e -> builder.setScheduledAmount(scheduledAmount)); - Optional.ofNullable(backupArbitrator).ifPresent(nodeAddress -> builder.setBackupArbitrator(nodeAddress.toProtoMessage())); Optional.ofNullable(scheduledTxHashes).ifPresent(e -> builder.addAllScheduledTxHashes(scheduledTxHashes)); Optional.ofNullable(reserveTxHash).ifPresent(e -> builder.setReserveTxHash(reserveTxHash)); Optional.ofNullable(reserveTxHex).ifPresent(e -> builder.setReserveTxHex(reserveTxHex)); @@ -156,7 +147,6 @@ public final class OpenOffer implements Tradable { public static Tradable fromProto(protobuf.OpenOffer proto) { OpenOffer openOffer = new OpenOffer(Offer.fromProto(proto.getOffer()), ProtoUtil.enumFromProto(OpenOffer.State.class, proto.getState().name()), - proto.hasBackupArbitrator() ? NodeAddress.fromProto(proto.getBackupArbitrator()) : null, proto.getTriggerPrice(), proto.getAutoSplit(), proto.getScheduledAmount(), @@ -227,7 +217,6 @@ public final class OpenOffer implements Tradable { return "OpenOffer{" + ",\n offer=" + offer + ",\n state=" + state + - ",\n arbitratorNodeAddress=" + backupArbitrator + ",\n triggerPrice=" + triggerPrice + "\n}"; } diff --git a/core/src/main/java/bisq/core/offer/OpenOfferManager.java b/core/src/main/java/bisq/core/offer/OpenOfferManager.java index 0cf1b6c748..7133a1ba98 100644 --- a/core/src/main/java/bisq/core/offer/OpenOfferManager.java +++ b/core/src/main/java/bisq/core/offer/OpenOfferManager.java @@ -24,7 +24,6 @@ import bisq.core.btc.wallet.TradeWalletService; import bisq.core.btc.wallet.XmrWalletService; import bisq.core.exceptions.TradePriceOutOfToleranceException; import bisq.core.filter.FilterManager; -import bisq.core.offer.availability.DisputeAgentSelection; import bisq.core.offer.messages.OfferAvailabilityRequest; import bisq.core.offer.messages.OfferAvailabilityResponse; import bisq.core.offer.messages.SignOfferRequest; @@ -703,6 +702,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe // handle result resultHandler.handleResult(null); } catch (Exception e) { + e.printStackTrace(); errorMessageHandler.handleErrorMessage(e.getMessage()); } } @@ -760,7 +760,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe // set reserve tx on open offer openOffer.setReserveTxHash(model.getReserveTx().getHash()); - openOffer.setReserveTxHex(model.getReserveTx().getHash()); + openOffer.setReserveTxHex(model.getReserveTx().getFullHex()); openOffer.setReserveTxKey(model.getReserveTx().getKey()); // set offer state @@ -948,7 +948,6 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe Optional openOfferOptional = getOpenOfferById(request.offerId); AvailabilityResult availabilityResult; String makerSignature = null; - NodeAddress backupArbitratorNodeAddress = null; if (openOfferOptional.isPresent()) { OpenOffer openOffer = openOfferOptional.get(); if (!apiUserDeniedByOffer(request)) { @@ -957,12 +956,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe Offer offer = openOffer.getOffer(); if (preferences.getIgnoreTradersList().stream().noneMatch(fullAddress -> fullAddress.equals(peer.getFullAddress()))) { - // set backup arbitrator if signer is not available - Mediator backupMediator = DisputeAgentSelection.getLeastUsedArbitrator(tradeStatisticsManager, mediatorManager, offer.getOfferPayload().getArbitratorSigner()); - backupArbitratorNodeAddress = backupMediator == null ? null : backupMediator.getNodeAddress(); - openOffer.setBackupArbitrator(backupArbitratorNodeAddress); - - // maker signs taker's request // TODO (woodser): should maker signature include selected arbitrator? + // maker signs taker's request String tradeRequestAsJson = JsonUtil.objectToJson(request.getTradeRequest()); makerSignature = Sig.sign(keyRing.getSignatureKeyPair().getPrivate(), tradeRequestAsJson); @@ -1005,8 +999,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe OfferAvailabilityResponse offerAvailabilityResponse = new OfferAvailabilityResponse(request.offerId, availabilityResult, - makerSignature, - backupArbitratorNodeAddress); + makerSignature); log.info("Send {} with offerId {} and uid {} to peer {}", offerAvailabilityResponse.getClass().getSimpleName(), offerAvailabilityResponse.getOfferId(), offerAvailabilityResponse.getUid(), peer); diff --git a/core/src/main/java/bisq/core/offer/availability/DisputeAgentSelection.java b/core/src/main/java/bisq/core/offer/availability/DisputeAgentSelection.java index be991e8356..db92d9f46a 100644 --- a/core/src/main/java/bisq/core/offer/availability/DisputeAgentSelection.java +++ b/core/src/main/java/bisq/core/offer/availability/DisputeAgentSelection.java @@ -53,7 +53,7 @@ public class DisputeAgentSelection { public static T getLeastUsedArbitrator(TradeStatisticsManager tradeStatisticsManager, DisputeAgentManager disputeAgentManager, - NodeAddress excludedArbitrator) { + Set excludedArbitrator) { return getLeastUsedDisputeAgent(tradeStatisticsManager, disputeAgentManager, excludedArbitrator); @@ -61,7 +61,7 @@ public class DisputeAgentSelection { private static T getLeastUsedDisputeAgent(TradeStatisticsManager tradeStatisticsManager, DisputeAgentManager disputeAgentManager, - NodeAddress excludedDisputeAgent) { + Set excludedDisputeAgents) { // We take last 100 entries from trade statistics List list = new ArrayList<>(tradeStatisticsManager.getObservableTradeStatisticsSet()); list.sort(Comparator.comparing(TradeStatistics3::getDateAsLong)); @@ -81,7 +81,7 @@ public class DisputeAgentSelection { .map(disputeAgent -> disputeAgent.getNodeAddress().getFullAddress()) .collect(Collectors.toSet()); - if (excludedDisputeAgent != null) disputeAgents.remove(excludedDisputeAgent.getFullAddress()); + if (excludedDisputeAgents != null) disputeAgents.removeAll(excludedDisputeAgents.stream().map(NodeAddress::getFullAddress).collect(Collectors.toList())); if (disputeAgents.isEmpty()) return null; String result = getLeastUsedDisputeAgent(lastAddressesUsedInTrades, disputeAgents); diff --git a/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java b/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java index 3148e65704..35e2808f09 100644 --- a/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java +++ b/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java @@ -65,9 +65,6 @@ public class OfferAvailabilityModel implements Model { @Setter @Getter private String makerSignature; - @Setter - @Getter - private NodeAddress backupArbitrator; // Added in v1.5.5 @Getter diff --git a/core/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java b/core/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java index f787b52dc0..7a601de998 100644 --- a/core/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java +++ b/core/src/main/java/bisq/core/offer/availability/tasks/ProcessOfferAvailabilityResponse.java @@ -62,7 +62,6 @@ public class ProcessOfferAvailabilityResponse extends Task builder.addAllSupportedCapabilities(Capabilities.toIntList(supportedCapabilities))); Optional.ofNullable(uid).ifPresent(e -> builder.setUid(uid)); Optional.ofNullable(makerSignature).ifPresent(e -> builder.setMakerSignature(makerSignature)); - Optional.ofNullable(backupArbitrator).ifPresent(nodeAddress -> builder.setBackupArbitrator(nodeAddress.toProtoMessage())); return getNetworkEnvelopeBuilder() .setOfferAvailabilityResponse(builder) @@ -102,7 +95,6 @@ public final class OfferAvailabilityResponse extends OfferMessage implements Sup Capabilities.fromIntList(proto.getSupportedCapabilitiesList()), messageVersion, proto.getUid().isEmpty() ? null : proto.getUid(), - proto.getMakerSignature().isEmpty() ? null : proto.getMakerSignature(), - proto.hasBackupArbitrator() ? NodeAddress.fromProto(proto.getBackupArbitrator()) : null); + proto.getMakerSignature().isEmpty() ? null : proto.getMakerSignature()); } } diff --git a/core/src/main/java/bisq/core/offer/placeoffer/tasks/MakerSendsSignOfferRequest.java b/core/src/main/java/bisq/core/offer/placeoffer/tasks/MakerSendsSignOfferRequest.java index 2d2807bc65..a9c1f52b35 100644 --- a/core/src/main/java/bisq/core/offer/placeoffer/tasks/MakerSendsSignOfferRequest.java +++ b/core/src/main/java/bisq/core/offer/placeoffer/tasks/MakerSendsSignOfferRequest.java @@ -17,13 +17,15 @@ package bisq.core.offer.placeoffer.tasks; -import static com.google.common.base.Preconditions.checkNotNull; import bisq.common.app.Version; +import bisq.common.handlers.ErrorMessageHandler; +import bisq.common.handlers.ResultHandler; import bisq.common.taskrunner.Task; import bisq.common.taskrunner.TaskRunner; import bisq.core.btc.model.XmrAddressEntry; import bisq.core.offer.Offer; +import bisq.core.offer.availability.DisputeAgentSelection; import bisq.core.offer.messages.SignOfferRequest; import bisq.core.offer.placeoffer.PlaceOfferModel; import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator; @@ -34,6 +36,8 @@ import bisq.network.p2p.NodeAddress; import bisq.network.p2p.P2PService; import bisq.network.p2p.SendDirectMessageListener; import java.util.Date; +import java.util.HashSet; +import java.util.Set; import java.util.UUID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,12 +54,10 @@ public class MakerSendsSignOfferRequest extends Task { @Override protected void run() { - Offer offer = model.getOffer(); - try { runInterceptHook(); - + // create request for arbitrator to sign offer String returnAddress = model.getXmrWalletService().getOrCreateAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString(); SignOfferRequest request = new SignOfferRequest( @@ -73,45 +75,14 @@ public class MakerSendsSignOfferRequest extends Task { offer.getOfferPayload().getReserveTxKeyImages(), returnAddress); - // get signing arbitrator - Arbitrator arbitrator = checkNotNull(model.getUser().getAcceptedArbitratorByAddress(offer.getOfferPayload().getArbitratorSigner()), "user.getAcceptedArbitratorByAddress(arbitratorNodeAddress) must not be null"); - - // complete on successful ack message - DecryptedDirectMessageListener ackListener = new DecryptedDirectMessageListener() { - @Override - public void onDirectMessage(DecryptedMessageWithPubKey decryptedMessageWithPubKey, NodeAddress sender) { - if (!(decryptedMessageWithPubKey.getNetworkEnvelope() instanceof AckMessage)) return; - if (!sender.equals(arbitrator.getNodeAddress())) return; - AckMessage ackMessage = (AckMessage) decryptedMessageWithPubKey.getNetworkEnvelope(); - if (!ackMessage.getSourceMsgClassName().equals(SignOfferRequest.class.getSimpleName())) return; - if (!ackMessage.getSourceUid().equals(request.getUid())) return; - if (ackMessage.isSuccess()) { - offer.setState(Offer.State.OFFER_FEE_RESERVED); - model.getP2PService().removeDecryptedDirectMessageListener(this); - complete(); - } else { - if (!failed) { - failed = true; - failed(ackMessage.getErrorMessage()); // TODO: (woodser): only fail once? build into task? - } - } - } - }; - model.getP2PService().addDecryptedDirectMessageListener(ackListener); - - // send request - model.getP2PService().sendEncryptedDirectMessage(arbitrator.getNodeAddress(), arbitrator.getPubKeyRing(), request, new SendDirectMessageListener() { - @Override - public void onArrived() { - log.info("{} arrived: arbitrator={}; offerId={}; uid={}", request.getClass().getSimpleName(), arbitrator.getNodeAddress(), offer.getId()); - } - @Override - public void onFault(String errorMessage) { - log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), arbitrator.getNodeAddress(), offer.getId(), errorMessage); - appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage); - failed(); - } - }); + // send request to least used arbitrators until success + sendSignOfferRequests(request, () -> { + complete(); + }, (errorMessage) -> { + log.warn("Error signing offer: " + errorMessage); + appendToErrorMessage("Error signing offer: " + errorMessage); + failed(errorMessage); + }); } catch (Throwable t) { offer.setErrorMessage("An error occurred.\n" + "Error message:\n" @@ -119,4 +90,77 @@ public class MakerSendsSignOfferRequest extends Task { failed(t); } } + + private void sendSignOfferRequests(SignOfferRequest request, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { + Arbitrator leastUsedArbitrator = DisputeAgentSelection.getLeastUsedArbitrator(model.getTradeStatisticsManager(), model.getArbitratorManager()); + sendSignOfferRequests(request, leastUsedArbitrator.getNodeAddress(), new HashSet(), resultHandler, errorMessageHandler); + } + + private void sendSignOfferRequests(SignOfferRequest request, NodeAddress arbitratorNodeAddress, Set excludedArbitrators, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { + + // complete on successful ack message + DecryptedDirectMessageListener ackListener = new DecryptedDirectMessageListener() { + @Override + public void onDirectMessage(DecryptedMessageWithPubKey decryptedMessageWithPubKey, NodeAddress sender) { + if (!(decryptedMessageWithPubKey.getNetworkEnvelope() instanceof AckMessage)) return; + if (!sender.equals(arbitratorNodeAddress)) return; + AckMessage ackMessage = (AckMessage) decryptedMessageWithPubKey.getNetworkEnvelope(); + if (!ackMessage.getSourceMsgClassName().equals(SignOfferRequest.class.getSimpleName())) return; + if (!ackMessage.getSourceUid().equals(request.getUid())) return; + if (ackMessage.isSuccess()) { + model.getP2PService().removeDecryptedDirectMessageListener(this); + model.getOffer().getOfferPayload().setArbitratorSigner(arbitratorNodeAddress); + model.getOffer().setState(Offer.State.OFFER_FEE_RESERVED); + resultHandler.handleResult(); + } else { + log.warn("Arbitrator nacked request: {}", errorMessage); + handleArbitratorFailure(request, arbitratorNodeAddress, excludedArbitrators, resultHandler, errorMessageHandler); + } + } + }; + model.getP2PService().addDecryptedDirectMessageListener(ackListener); + + // send sign offer request + sendSignOfferRequest(request, arbitratorNodeAddress, new SendDirectMessageListener() { + @Override + public void onArrived() { + log.info("{} arrived at arbitrator: offerId={}", request.getClass().getSimpleName(), model.getOffer().getId()); + } + + // if unavailable, try alternative arbitrator + @Override + public void onFault(String errorMessage) { + log.warn("Arbitrator unavailable: {}", errorMessage); + handleArbitratorFailure(request, arbitratorNodeAddress, excludedArbitrators, resultHandler, errorMessageHandler); + } + }); + } + + private void sendSignOfferRequest(SignOfferRequest request, NodeAddress arbitratorNodeAddress, SendDirectMessageListener listener) { + + // get registered arbitrator + Arbitrator arbitrator = model.getUser().getAcceptedArbitratorByAddress(arbitratorNodeAddress); + if (arbitrator == null) throw new RuntimeException("Node address " + arbitratorNodeAddress + " is not a registered arbitrator"); + request.getOfferPayload().setArbitratorSigner(arbitratorNodeAddress); + + // send request to arbitrator + log.info("Sending {} with offerId {} and uid {} to arbitrator {}", request.getClass().getSimpleName(), request.getOfferId(), request.getUid(), arbitratorNodeAddress); + model.getP2PService().sendEncryptedDirectMessage( + arbitratorNodeAddress, + arbitrator.getPubKeyRing(), + request, + listener + ); + } + + private void handleArbitratorFailure(SignOfferRequest request, NodeAddress arbitratorNodeAddress, Set excludedArbitrators, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { + excludedArbitrators.add(arbitratorNodeAddress); + Arbitrator altArbitrator = DisputeAgentSelection.getLeastUsedArbitrator(model.getTradeStatisticsManager(), model.getArbitratorManager(), excludedArbitrators); + if (altArbitrator == null) { + errorMessageHandler.handleErrorMessage("Offer could not be signed by any arbitrator"); + return; + } + log.info("Using alternative arbitrator {}", altArbitrator.getNodeAddress()); + sendSignOfferRequests(request, altArbitrator.getNodeAddress(), excludedArbitrators, resultHandler, errorMessageHandler); + } } diff --git a/core/src/main/java/bisq/core/support/dispute/agent/DisputeAgentManager.java b/core/src/main/java/bisq/core/support/dispute/agent/DisputeAgentManager.java index 60437cd42b..01b03f5485 100644 --- a/core/src/main/java/bisq/core/support/dispute/agent/DisputeAgentManager.java +++ b/core/src/main/java/bisq/core/support/dispute/agent/DisputeAgentManager.java @@ -47,7 +47,6 @@ import java.math.BigInteger; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; @@ -94,13 +93,12 @@ public abstract class DisputeAgentManager { public DisputeAgentManager(KeyRing keyRing, DisputeAgentService disputeAgentService, User user, - FilterManager filterManager, - boolean useDevPrivilegeKeys) { + FilterManager filterManager) { this.keyRing = keyRing; this.disputeAgentService = disputeAgentService; this.user = user; this.filterManager = filterManager; - publicKeys = useDevPrivilegeKeys ? Collections.singletonList(DevEnv.DEV_PRIVILEGE_PUB_KEY) : getPubKeyList(); + publicKeys = getPubKeyList(); } @@ -245,6 +243,8 @@ public abstract class DisputeAgentManager { resultHandler.handleResult(); }, errorMessageHandler); + } else { + errorMessageHandler.handleErrorMessage("User is not registered dispute agent"); } } diff --git a/core/src/main/java/bisq/core/support/dispute/arbitration/arbitrator/ArbitratorManager.java b/core/src/main/java/bisq/core/support/dispute/arbitration/arbitrator/ArbitratorManager.java index f79a94a7e9..d487ec1712 100644 --- a/core/src/main/java/bisq/core/support/dispute/arbitration/arbitrator/ArbitratorManager.java +++ b/core/src/main/java/bisq/core/support/dispute/arbitration/arbitrator/ArbitratorManager.java @@ -27,7 +27,6 @@ import bisq.common.crypto.KeyRing; import javax.inject.Inject; import javax.inject.Singleton; -import javax.inject.Named; import java.util.ArrayList; import java.util.List; @@ -41,16 +40,18 @@ public class ArbitratorManager extends DisputeAgentManager { public ArbitratorManager(KeyRing keyRing, ArbitratorService arbitratorService, User user, - FilterManager filterManager, - @Named(Config.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) { - super(keyRing, arbitratorService, user, filterManager, useDevPrivilegeKeys); + FilterManager filterManager) { + super(keyRing, arbitratorService, user, filterManager); } @Override protected List getPubKeyList() { switch (Config.baseCurrencyNetwork()) { case XMR_LOCAL: - throw new RuntimeException("No arbitrator pub key list for local XMR testnet. Set useDevPrivilegeKeys=true"); + return List.of( + "027a381b5333a56e1cc3d90d3a7d07f26509adf7029ed06fc997c656621f8da1ee", + "024baabdba90e7cc0dc4626ef73ea9d722ea7085d1104491da8c76f28187513492", + "026eeec3c119dd6d537249d74e5752a642dd2c3cc5b6a9b44588eb58344f29b519"); case XMR_STAGENET: return List.of( "03bb559ce207a4deb51d4c705076c95b85ad8581d35936b2a422dcb504eaf7cdb0", diff --git a/core/src/main/java/bisq/core/support/dispute/mediation/mediator/MediatorManager.java b/core/src/main/java/bisq/core/support/dispute/mediation/mediator/MediatorManager.java index 6ff81da367..86be05aef5 100644 --- a/core/src/main/java/bisq/core/support/dispute/mediation/mediator/MediatorManager.java +++ b/core/src/main/java/bisq/core/support/dispute/mediation/mediator/MediatorManager.java @@ -23,11 +23,9 @@ import bisq.core.user.User; import bisq.network.p2p.storage.payload.ProtectedStorageEntry; -import bisq.common.config.Config; import bisq.common.crypto.KeyRing; import javax.inject.Singleton; -import javax.inject.Named; import javax.inject.Inject; @@ -40,9 +38,8 @@ public class MediatorManager extends DisputeAgentManager { public MediatorManager(KeyRing keyRing, MediatorService mediatorService, User user, - FilterManager filterManager, - @Named(Config.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) { - super(keyRing, mediatorService, user, filterManager, useDevPrivilegeKeys); + FilterManager filterManager) { + super(keyRing, mediatorService, user, filterManager); } @Override diff --git a/core/src/main/java/bisq/core/support/dispute/refund/refundagent/RefundAgentManager.java b/core/src/main/java/bisq/core/support/dispute/refund/refundagent/RefundAgentManager.java index 562e362eac..97b0022ed3 100644 --- a/core/src/main/java/bisq/core/support/dispute/refund/refundagent/RefundAgentManager.java +++ b/core/src/main/java/bisq/core/support/dispute/refund/refundagent/RefundAgentManager.java @@ -23,12 +23,10 @@ import bisq.core.user.User; import bisq.network.p2p.storage.payload.ProtectedStorageEntry; -import bisq.common.config.Config; import bisq.common.crypto.KeyRing; import javax.inject.Inject; import javax.inject.Singleton; -import javax.inject.Named; import java.util.List; @@ -42,9 +40,8 @@ public class RefundAgentManager extends DisputeAgentManager { public RefundAgentManager(KeyRing keyRing, RefundAgentService refundAgentService, User user, - FilterManager filterManager, - @Named(Config.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) { - super(keyRing, refundAgentService, user, filterManager, useDevPrivilegeKeys); + FilterManager filterManager) { + super(keyRing, refundAgentService, user, filterManager); } @Override diff --git a/core/src/main/java/bisq/core/trade/Trade.java b/core/src/main/java/bisq/core/trade/Trade.java index b717afc5d8..547ef028be 100644 --- a/core/src/main/java/bisq/core/trade/Trade.java +++ b/core/src/main/java/bisq/core/trade/Trade.java @@ -127,7 +127,7 @@ public abstract class Trade implements Tradable, Model { // deposit confirmed (TODO) // deposit unlocked - DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN(Phase.DEPOSIT_UNLOCKED), + DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN(Phase.DEPOSITS_UNLOCKED), // payment sent BUYER_CONFIRMED_IN_UI_PAYMENT_SENT(Phase.PAYMENT_SENT), @@ -191,7 +191,7 @@ public abstract class Trade implements Tradable, Model { INIT, DEPOSIT_REQUESTED, // TODO (woodser): remove unused phases DEPOSITS_PUBLISHED, - DEPOSIT_UNLOCKED, // TODO (woodser): rename to or add DEPOSIT_UNLOCKED + DEPOSITS_UNLOCKED, PAYMENT_SENT, PAYMENT_RECEIVED, PAYOUT_PUBLISHED, @@ -1291,7 +1291,7 @@ public abstract class Trade implements Tradable, Model { } public boolean isDepositUnlocked() { - return getState().getPhase().ordinal() >= Phase.DEPOSIT_UNLOCKED.ordinal(); + return getState().getPhase().ordinal() >= Phase.DEPOSITS_UNLOCKED.ordinal(); } public boolean isPaymentSent() { diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java index 132e81537f..41773777a0 100644 --- a/core/src/main/java/bisq/core/trade/TradeManager.java +++ b/core/src/main/java/bisq/core/trade/TradeManager.java @@ -500,9 +500,10 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi Offer offer = openOffer.getOffer(); - // verify request is from signer or backup arbitrator - if (!sender.equals(offer.getOfferPayload().getArbitratorSigner()) && !sender.equals(openOffer.getBackupArbitrator())) { // TODO (woodser): get backup arbitrator from maker-signed InitTradeRequest and remove from OpenOffer - log.warn("Ignoring InitTradeRequest from {} with tradeId {} because request must be from signer or backup arbitrator", sender, request.getTradeId()); + // verify request is from arbitrator + Arbitrator arbitrator = user.getAcceptedArbitratorByAddress(sender); + if (arbitrator == null) { + log.warn("Ignoring InitTradeRequest from {} with tradeId {} because request is not from accepted arbitrator", sender, request.getTradeId()); return; } @@ -762,7 +763,6 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi trade.getProcessModel().setTradeMessage(model.getTradeRequest()); trade.getProcessModel().setMakerSignature(model.getMakerSignature()); - trade.getProcessModel().setBackupArbitrator(model.getBackupArbitrator()); // backup arbitrator only used if signer offline trade.getProcessModel().setUseSavingsWallet(useSavingsWallet); trade.getProcessModel().setFundsNeededForTradeAsLong(fundsNeededForTrade.value); trade.setTakerPubKeyRing(model.getPubKeyRing()); diff --git a/core/src/main/java/bisq/core/trade/messages/SignContractResponse.java b/core/src/main/java/bisq/core/trade/messages/SignContractResponse.java index 0e844572c6..d0f6ab6ff9 100644 --- a/core/src/main/java/bisq/core/trade/messages/SignContractResponse.java +++ b/core/src/main/java/bisq/core/trade/messages/SignContractResponse.java @@ -30,14 +30,13 @@ import java.util.Optional; import lombok.EqualsAndHashCode; import lombok.Value; -import javax.annotation.Nullable; - @EqualsAndHashCode(callSuper = true) @Value public final class SignContractResponse extends TradeMessage implements DirectMessage { private final NodeAddress senderNodeAddress; private final PubKeyRing pubKeyRing; private final long currentDate; + private final String contractAsJson; private final String contractSignature; public SignContractResponse(String tradeId, @@ -46,11 +45,13 @@ public final class SignContractResponse extends TradeMessage implements DirectMe String uid, String messageVersion, long currentDate, + String contractAsJson, String contractSignature) { super(messageVersion, tradeId, uid); this.senderNodeAddress = senderNodeAddress; this.pubKeyRing = pubKeyRing; this.currentDate = currentDate; + this.contractAsJson = contractAsJson; this.contractSignature = contractSignature; } @@ -67,6 +68,7 @@ public final class SignContractResponse extends TradeMessage implements DirectMe .setPubKeyRing(pubKeyRing.toProtoMessage()) .setUid(uid); + Optional.ofNullable(contractAsJson).ifPresent(e -> builder.setContractAsJson(contractAsJson)); Optional.ofNullable(contractSignature).ifPresent(e -> builder.setContractSignature(contractSignature)); builder.setCurrentDate(currentDate); @@ -83,6 +85,7 @@ public final class SignContractResponse extends TradeMessage implements DirectMe proto.getUid(), messageVersion, proto.getCurrentDate(), + ProtoUtil.stringOrNullFromProto(proto.getContractAsJson()), ProtoUtil.stringOrNullFromProto(proto.getContractSignature())); } @@ -92,6 +95,7 @@ public final class SignContractResponse extends TradeMessage implements DirectMe "\n senderNodeAddress=" + senderNodeAddress + ",\n pubKeyRing=" + pubKeyRing + ",\n currentDate=" + currentDate + + ",\n contractAsJson='" + contractAsJson + ",\n contractSignature='" + contractSignature + "\n} " + super.toString(); } diff --git a/core/src/main/java/bisq/core/trade/protocol/ArbitratorProtocol.java b/core/src/main/java/bisq/core/trade/protocol/ArbitratorProtocol.java index 65bd3489ab..965e11a77b 100644 --- a/core/src/main/java/bisq/core/trade/protocol/ArbitratorProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/ArbitratorProtocol.java @@ -8,7 +8,7 @@ import bisq.core.trade.messages.InitTradeRequest; import bisq.core.trade.messages.PaymentAccountPayloadRequest; import bisq.core.trade.messages.SignContractResponse; import bisq.core.trade.protocol.tasks.ApplyFilter; -import bisq.core.trade.protocol.tasks.ArbitratorSendsInitTradeAndMultisigRequests; +import bisq.core.trade.protocol.tasks.ArbitratorSendsInitTradeOrMultisigRequests; import bisq.core.trade.protocol.tasks.ArbitratorProcessesDepositRequest; import bisq.core.trade.protocol.tasks.ArbitratorProcessesReserveTx; import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest; @@ -42,7 +42,7 @@ public class ArbitratorProtocol extends DisputeProtocol { ApplyFilter.class, ProcessInitTradeRequest.class, ArbitratorProcessesReserveTx.class, - ArbitratorSendsInitTradeAndMultisigRequests.class) + ArbitratorSendsInitTradeOrMultisigRequests.class) .using(new TradeTaskRunner(trade, () -> { startTimeout(TRADE_TIMEOUT); diff --git a/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java index 30cc5a7ea8..1b1769f88c 100644 --- a/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java @@ -26,9 +26,8 @@ import bisq.core.trade.messages.PaymentAccountPayloadRequest; import bisq.core.trade.messages.PaymentReceivedMessage; import bisq.core.trade.messages.SignContractRequest; import bisq.core.trade.messages.SignContractResponse; -import bisq.core.trade.protocol.tasks.MakerSendsInitTradeRequestIfUnreserved; +import bisq.core.trade.protocol.tasks.MakerSendsInitTradeRequest; import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest; -import bisq.core.trade.protocol.tasks.TradeTask; import bisq.network.p2p.NodeAddress; import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ResultHandler; @@ -65,7 +64,7 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol ProcessInitTradeRequest.class, //ApplyFilter.class, // TODO (woodser): these checks apply when maker signs availability request, but not here //VerifyPeersAccountAgeWitness.class, // TODO (woodser): these checks apply after in multisig, means if rejected need to reimburse other's fee - MakerSendsInitTradeRequestIfUnreserved.class) + MakerSendsInitTradeRequest.class) .using(new TradeTaskRunner(trade, () -> { startTimeout(TRADE_TIMEOUT); diff --git a/core/src/main/java/bisq/core/trade/protocol/BuyerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/BuyerProtocol.java index c7b19ed61b..4f9d4ea9b0 100644 --- a/core/src/main/java/bisq/core/trade/protocol/BuyerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/BuyerProtocol.java @@ -90,7 +90,7 @@ public abstract class BuyerProtocol extends DisputeProtocol { latchTrade(); this.errorMessageHandler = errorMessageHandler; BuyerEvent event = BuyerEvent.PAYMENT_SENT; - expect(phase(Trade.Phase.DEPOSIT_UNLOCKED) + expect(phase(Trade.Phase.DEPOSITS_UNLOCKED) .with(event) .preCondition(trade.confirmPermitted())) .setup(tasks(ApplyFilter.class, diff --git a/core/src/main/java/bisq/core/trade/protocol/DisputeProtocol.java b/core/src/main/java/bisq/core/trade/protocol/DisputeProtocol.java index 977edd49a7..3b77fb2baa 100644 --- a/core/src/main/java/bisq/core/trade/protocol/DisputeProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/DisputeProtocol.java @@ -58,7 +58,7 @@ public abstract class DisputeProtocol extends TradeProtocol { // Trader has not yet received the peer's signature but has clicked the accept button. public void onAcceptMediationResult(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { DisputeEvent event = DisputeEvent.MEDIATION_RESULT_ACCEPTED; - expect(anyPhase(Trade.Phase.DEPOSIT_UNLOCKED, + expect(anyPhase(Trade.Phase.DEPOSITS_UNLOCKED, Trade.Phase.PAYMENT_SENT, Trade.Phase.PAYMENT_RECEIVED) .with(event) @@ -85,7 +85,7 @@ public abstract class DisputeProtocol extends TradeProtocol { // Trader has already received the peer's signature and has clicked the accept button as well. public void onFinalizeMediationResultPayout(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { DisputeEvent event = DisputeEvent.MEDIATION_RESULT_ACCEPTED; - expect(anyPhase(Trade.Phase.DEPOSIT_UNLOCKED, + expect(anyPhase(Trade.Phase.DEPOSITS_UNLOCKED, Trade.Phase.PAYMENT_SENT, Trade.Phase.PAYMENT_RECEIVED) .with(event) @@ -113,7 +113,7 @@ public abstract class DisputeProtocol extends TradeProtocol { /////////////////////////////////////////////////////////////////////////////////////////// protected void handle(MediatedPayoutTxSignatureMessage message, NodeAddress peer) { - expect(anyPhase(Trade.Phase.DEPOSIT_UNLOCKED, + expect(anyPhase(Trade.Phase.DEPOSITS_UNLOCKED, Trade.Phase.PAYMENT_SENT, Trade.Phase.PAYMENT_RECEIVED) .with(message) @@ -123,7 +123,7 @@ public abstract class DisputeProtocol extends TradeProtocol { } protected void handle(MediatedPayoutTxPublishedMessage message, NodeAddress peer) { - expect(anyPhase(Trade.Phase.DEPOSIT_UNLOCKED, + expect(anyPhase(Trade.Phase.DEPOSITS_UNLOCKED, Trade.Phase.PAYMENT_SENT, Trade.Phase.PAYMENT_RECEIVED) .with(message) diff --git a/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java b/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java index 81f9814be9..b4d19f9660 100644 --- a/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java +++ b/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java @@ -160,9 +160,6 @@ public class ProcessModel implements Model, PersistablePayload { @Getter @Setter private String makerSignature; - @Getter - @Setter - private NodeAddress backupArbitrator; @Nullable @Getter @Setter @@ -231,7 +228,6 @@ public class ProcessModel implements Model, PersistablePayload { Optional.ofNullable(myMultiSigPubKey).ifPresent(e -> builder.setMyMultiSigPubKey(ByteString.copyFrom(myMultiSigPubKey))); Optional.ofNullable(tempTradingPeerNodeAddress).ifPresent(e -> builder.setTempTradingPeerNodeAddress(tempTradingPeerNodeAddress.toProtoMessage())); Optional.ofNullable(makerSignature).ifPresent(e -> builder.setMakerSignature(makerSignature)); - Optional.ofNullable(backupArbitrator).ifPresent(e -> builder.setBackupArbitrator(backupArbitrator.toProtoMessage())); Optional.ofNullable(multisigAddress).ifPresent(e -> builder.setMultisigAddress(multisigAddress)); return builder.build(); } @@ -260,7 +256,6 @@ public class ProcessModel implements Model, PersistablePayload { processModel.setTempTradingPeerNodeAddress(proto.hasTempTradingPeerNodeAddress() ? NodeAddress.fromProto(proto.getTempTradingPeerNodeAddress()) : null); processModel.setMediatedPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getMediatedPayoutTxSignature())); processModel.setMakerSignature(proto.getMakerSignature()); - processModel.setBackupArbitrator(proto.hasBackupArbitrator() ? NodeAddress.fromProto(proto.getBackupArbitrator()) : null); processModel.setMultisigAddress(ProtoUtil.stringOrNullFromProto(proto.getMultisigAddress())); String paymentStartedMessageStateString = ProtoUtil.stringOrNullFromProto(proto.getPaymentStartedMessageState()); diff --git a/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java index daf5f44d6e..7de59b5acd 100644 --- a/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java @@ -28,7 +28,7 @@ import bisq.core.trade.messages.InitMultisigRequest; import bisq.core.trade.messages.InitTradeRequest; import bisq.core.trade.messages.PaymentAccountPayloadRequest; import bisq.core.trade.messages.TradeMessage; -import bisq.core.trade.protocol.tasks.MakerSendsInitTradeRequestIfUnreserved; +import bisq.core.trade.protocol.tasks.MakerSendsInitTradeRequest; import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest; import bisq.network.p2p.NodeAddress; import bisq.common.handlers.ErrorMessageHandler; @@ -66,7 +66,7 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc ProcessInitTradeRequest.class, //ApplyFilter.class, // TODO (woodser): these checks apply when maker signs availability request, but not here //VerifyPeersAccountAgeWitness.class, // TODO (woodser): these checks apply after in multisig, means if rejected need to reimburse other's fee - MakerSendsInitTradeRequestIfUnreserved.class) + MakerSendsInitTradeRequest.class) .using(new TradeTaskRunner(trade, () -> { startTimeout(TRADE_TIMEOUT); @@ -123,7 +123,6 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc @Override protected void onTradeMessage(TradeMessage message, NodeAddress peer) { super.onTradeMessage(message, peer); - log.info("Received {} from {} with tradeId {} and uid {}", message.getClass().getSimpleName(), peer, message.getTradeId(), message.getUid()); } /////////////////////////////////////////////////////////////////////////////////////////// diff --git a/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java index b5ff7bef92..dac7882f77 100644 --- a/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java @@ -139,6 +139,5 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc @Override protected void onTradeMessage(TradeMessage message, NodeAddress peer) { super.onTradeMessage(message, peer); - log.info("Received {} from {} with tradeId {} and uid {}", message.getClass().getSimpleName(), peer, message.getTradeId(), message.getUid()); } } diff --git a/core/src/main/java/bisq/core/trade/protocol/SellerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/SellerProtocol.java index 3ff79eb8c3..6ac5401f88 100644 --- a/core/src/main/java/bisq/core/trade/protocol/SellerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/SellerProtocol.java @@ -86,7 +86,7 @@ public abstract class SellerProtocol extends DisputeProtocol { return; } latchTrade(); - expect(anyPhase(Trade.Phase.DEPOSIT_UNLOCKED, Trade.Phase.DEPOSITS_PUBLISHED) + expect(anyPhase(Trade.Phase.DEPOSITS_UNLOCKED, Trade.Phase.DEPOSITS_PUBLISHED) .with(message) .from(peer) .preCondition(trade.getPayoutTx() == null, diff --git a/core/src/main/java/bisq/core/trade/protocol/TakerSendsInitTradeRequestToArbitrator.java b/core/src/main/java/bisq/core/trade/protocol/TakerSendsInitTradeRequestToArbitrator.java index e3bfc14cb0..137a5822d5 100644 --- a/core/src/main/java/bisq/core/trade/protocol/TakerSendsInitTradeRequestToArbitrator.java +++ b/core/src/main/java/bisq/core/trade/protocol/TakerSendsInitTradeRequestToArbitrator.java @@ -17,16 +17,24 @@ package bisq.core.trade.protocol; +import bisq.core.offer.availability.DisputeAgentSelection; import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator; +import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; +import bisq.core.support.dispute.mediation.mediator.Mediator; import bisq.core.trade.Trade; import bisq.core.trade.messages.InitTradeRequest; import bisq.core.trade.protocol.tasks.TradeTask; +import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.network.p2p.NodeAddress; import bisq.network.p2p.SendDirectMessageListener; - +import java.util.HashSet; +import java.util.Set; import bisq.common.app.Version; +import bisq.common.handlers.ErrorMessageHandler; +import bisq.common.handlers.ResultHandler; import bisq.common.taskrunner.TaskRunner; + import lombok.extern.slf4j.Slf4j; @Slf4j @@ -41,53 +49,54 @@ public class TakerSendsInitTradeRequestToArbitrator extends TradeTask { protected void run() { try { runInterceptHook(); - - // send request to arbitrator - sendInitTradeRequest(trade.getOffer().getOfferPayload().getArbitratorSigner(), new SendDirectMessageListener() { - @Override - public void onArrived() { - log.info("{} arrived at arbitrator: offerId={}", InitTradeRequest.class.getSimpleName(), trade.getId()); - } - - // send request to backup arbitrator if signer unavailable - @Override - public void onFault(String errorMessage) { - log.info("Sending {} to signing arbitrator {} failed, using backup arbitrator {}. error={}", InitTradeRequest.class.getSimpleName(), trade.getOffer().getOfferPayload().getArbitratorSigner(), processModel.getBackupArbitrator(), errorMessage); - if (processModel.getBackupArbitrator() == null) { - log.warn("Cannot take offer because signing arbitrator is offline and backup arbitrator is null"); - failed(); - return; - } - sendInitTradeRequest(processModel.getBackupArbitrator(), new SendDirectMessageListener() { - @Override - public void onArrived() { - log.info("{} arrived at backup arbitrator: offerId={}", InitTradeRequest.class.getSimpleName(), trade.getId()); - } - @Override - public void onFault(String errorMessage) { // TODO (woodser): distinguish nack from offline - log.warn("Cannot take offer because arbitrators are unavailable: error={}.", InitTradeRequest.class.getSimpleName(), errorMessage); - failed(); - } - }); - } + + // send request to signing arbitrator then least used arbitrators until success + sendInitTradeRequests(trade.getOffer().getOfferPayload().getArbitratorSigner(), new HashSet(), () -> { + complete(); + }, (errorMessage) -> { + log.warn("Cannot initialize trade with arbitrators: " + errorMessage); + failed(errorMessage); }); - complete(); // TODO (woodser): onArrived() doesn't get called if arbitrator rejects concurrent requests. always complete before onArrived()? } catch (Throwable t) { failed(t); } } - + + private void sendInitTradeRequests(NodeAddress arbitratorNodeAddress, Set excludedArbitrators, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { + sendInitTradeRequest(arbitratorNodeAddress, new SendDirectMessageListener() { + @Override + public void onArrived() { + log.info("{} arrived at arbitrator: offerId={}", InitTradeRequest.class.getSimpleName(), trade.getId()); + resultHandler.handleResult(); + } + + // if unavailable, try alternative arbitrator + @Override + public void onFault(String errorMessage) { + log.warn("Arbitrator {} unavailable: {}", arbitratorNodeAddress, errorMessage); + excludedArbitrators.add(arbitratorNodeAddress); + Arbitrator altArbitrator = DisputeAgentSelection.getLeastUsedArbitrator(processModel.getTradeStatisticsManager(), processModel.getArbitratorManager(), excludedArbitrators); + if (altArbitrator == null) { + errorMessageHandler.handleErrorMessage("Cannot take offer because no arbitrators are available"); + return; + } + log.info("Using alternative arbitrator {}", altArbitrator.getNodeAddress()); + sendInitTradeRequests(altArbitrator.getNodeAddress(), excludedArbitrators, resultHandler, errorMessageHandler); + } + }); + } + private void sendInitTradeRequest(NodeAddress arbitratorNodeAddress, SendDirectMessageListener listener) { // get registered arbitrator Arbitrator arbitrator = processModel.getUser().getAcceptedArbitratorByAddress(arbitratorNodeAddress); if (arbitrator == null) throw new RuntimeException("Node address " + arbitratorNodeAddress + " is not a registered arbitrator"); - + // set pub keys processModel.getArbitrator().setPubKeyRing(arbitrator.getPubKeyRing()); trade.setArbitratorNodeAddress(arbitratorNodeAddress); trade.setArbitratorPubKeyRing(processModel.getArbitrator().getPubKeyRing()); - + // create request to arbitrator InitTradeRequest makerRequest = (InitTradeRequest) processModel.getTradeMessage(); // taker's InitTradeRequest to maker InitTradeRequest arbitratorRequest = new InitTradeRequest( diff --git a/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java b/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java index 065a9d7000..5e5280d25d 100644 --- a/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/TradeProtocol.java @@ -638,8 +638,9 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D } protected void unlatchTrade() { - if (tradeLatch != null) tradeLatch.countDown(); + CountDownLatch lastLatch = tradeLatch; tradeLatch = null; + if (lastLatch != null) lastLatch.countDown(); } protected void awaitTradeLatch() { diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorProcessesDepositRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorProcessesDepositRequest.java index 3b8f8687ae..f06b6d0e3d 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorProcessesDepositRequest.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorProcessesDepositRequest.java @@ -22,7 +22,6 @@ import bisq.common.app.Version; import bisq.common.crypto.PubKeyRing; import bisq.common.crypto.Sig; import bisq.common.taskrunner.TaskRunner; -import bisq.core.btc.wallet.XmrWalletService; import bisq.core.offer.Offer; import bisq.core.offer.OfferDirection; import bisq.core.trade.Trade; @@ -40,7 +39,7 @@ import monero.daemon.MoneroDaemon; @Slf4j public class ArbitratorProcessesDepositRequest extends TradeTask { - + @SuppressWarnings({"unused"}) public ArbitratorProcessesDepositRequest(TaskRunner taskHandler, Trade trade) { super(taskHandler, trade); @@ -50,7 +49,7 @@ public class ArbitratorProcessesDepositRequest extends TradeTask { protected void run() { try { runInterceptHook(); - + // get contract and signature String contractAsJson = trade.getContractAsJson(); DepositRequest request = (DepositRequest) processModel.getTradeMessage(); // TODO (woodser): verify response @@ -68,10 +67,10 @@ public class ArbitratorProcessesDepositRequest extends TradeTask { // verify signature if (!Sig.verify(peerPubKeyRing.getSignaturePubKey(), contractAsJson, signature)) throw new RuntimeException("Peer's contract signature is invalid"); - + // set peer's signature peer.setContractSignature(signature); - + // collect expected values of deposit tx Offer offer = trade.getOffer(); boolean isFromTaker = request.getSenderNodeAddress().equals(trade.getTakerNodeAddress()); @@ -83,14 +82,9 @@ public class ArbitratorProcessesDepositRequest extends TradeTask { if (trader == processModel.getMaker()) tradeFee = ParsingUtils.coinToAtomicUnits(trade.getOffer().getMakerFee()); else if (trader == processModel.getTaker()) tradeFee = ParsingUtils.coinToAtomicUnits(trade.getTakerFee()); else throw new RuntimeException("DepositRequest is not from maker or taker"); - - // flush reserve tx from pool - XmrWalletService xmrWalletService = trade.getXmrWalletService(); - MoneroDaemon daemon = xmrWalletService.getDaemon(); - daemon.flushTxPool(trader.getReserveTxHash()); - + // verify deposit tx - xmrWalletService.verifyTradeTx(depositAddress, + trade.getXmrWalletService().verifyTradeTx(depositAddress, depositAmount, tradeFee, trader.getDepositTxHash(), @@ -98,16 +92,17 @@ public class ArbitratorProcessesDepositRequest extends TradeTask { request.getDepositTxKey(), null, false); - + // set deposit info trader.setDepositTxHex(request.getDepositTxHex()); trader.setDepositTxKey(request.getDepositTxKey()); - + // relay deposit txs when both available // TODO (woodser): add small delay so tx has head start against double spend attempts? if (processModel.getMaker().getDepositTxHex() != null && processModel.getTaker().getDepositTxHex() != null) { - + // relay txs + MoneroDaemon daemon = trade.getXmrWalletService().getDaemon(); daemon.submitTxHex(processModel.getMaker().getDepositTxHex()); // TODO (woodser): check that result is good. will need to release funds if one is submitted daemon.submitTxHex(processModel.getTaker().getDepositTxHex()); @@ -130,14 +125,14 @@ public class ArbitratorProcessesDepositRequest extends TradeTask { } else { log.info("Arbitrator waiting for deposit request from maker and taker for trade " + trade.getId()); } - + // TODO (woodser): request persistence? complete(); } catch (Throwable t) { failed(t); } } - + private void sendDepositResponse(NodeAddress nodeAddress, PubKeyRing pubKeyRing, DepositResponse response) { log.info("Sending deposit response to trader={}; offerId={}", nodeAddress, trade.getId()); processModel.getP2PService().sendEncryptedDirectMessage(nodeAddress, pubKeyRing, response, new SendDirectMessageListener() { diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorProcessesReserveTx.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorProcessesReserveTx.java index 47bb93d611..45b81f0106 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorProcessesReserveTx.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorProcessesReserveTx.java @@ -20,9 +20,7 @@ package bisq.core.trade.protocol.tasks; import bisq.common.taskrunner.TaskRunner; import bisq.core.offer.Offer; import bisq.core.offer.OfferDirection; -import bisq.core.offer.OfferPayload; import bisq.core.trade.Trade; -import bisq.core.trade.TradeUtils; import bisq.core.trade.messages.InitTradeRequest; import bisq.core.trade.protocol.TradingPeer; import bisq.core.util.ParsingUtils; diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorSendsInitTradeAndMultisigRequests.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorSendsInitTradeOrMultisigRequests.java similarity index 52% rename from core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorSendsInitTradeAndMultisigRequests.java rename to core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorSendsInitTradeOrMultisigRequests.java index cd20e5e936..294f594684 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorSendsInitTradeAndMultisigRequests.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorSendsInitTradeOrMultisigRequests.java @@ -27,7 +27,6 @@ import bisq.core.trade.messages.InitTradeRequest; import bisq.core.trade.protocol.TradeListener; import bisq.network.p2p.AckMessage; import bisq.network.p2p.NodeAddress; -import bisq.network.p2p.P2PService; import bisq.network.p2p.SendDirectMessageListener; import com.google.common.base.Charsets; import java.util.Date; @@ -42,10 +41,10 @@ import monero.wallet.MoneroWallet; * Arbitrator sends InitMultisigRequests after the maker acks. */ @Slf4j -public class ArbitratorSendsInitTradeAndMultisigRequests extends TradeTask { +public class ArbitratorSendsInitTradeOrMultisigRequests extends TradeTask { @SuppressWarnings({"unused"}) - public ArbitratorSendsInitTradeAndMultisigRequests(TaskRunner taskHandler, Trade trade) { + public ArbitratorSendsInitTradeOrMultisigRequests(TaskRunner taskHandler, Trade trade) { super(taskHandler, trade); } @@ -53,82 +52,67 @@ public class ArbitratorSendsInitTradeAndMultisigRequests extends TradeTask { protected void run() { try { runInterceptHook(); - - // skip if request not from taker InitTradeRequest request = (InitTradeRequest) processModel.getTradeMessage(); - if (!request.getSenderNodeAddress().equals(trade.getTakerNodeAddress())) { - complete(); - return; - } - + // arbitrator signs offer id as nonce to avoid challenge protocol byte[] sig = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), processModel.getOfferId().getBytes(Charsets.UTF_8)); - // save pub keys - processModel.getArbitrator().setPubKeyRing(processModel.getPubKeyRing()); // TODO (woodser): why duplicating field in process model - trade.setArbitratorPubKeyRing(processModel.getPubKeyRing()); - trade.setMakerPubKeyRing(trade.getOffer().getPubKeyRing()); - trade.setTakerPubKeyRing(request.getPubKeyRing()); - - // create request to initialize trade with maker - InitTradeRequest makerRequest = new InitTradeRequest( - processModel.getOfferId(), - request.getSenderNodeAddress(), - request.getPubKeyRing(), - trade.getAmount().value, - trade.getPrice().getValue(), - trade.getTakerFee().getValue(), - request.getAccountId(), - request.getPaymentAccountId(), - request.getPaymentMethodId(), - UUID.randomUUID().toString(), - Version.getP2PMessageVersion(), - sig, - new Date().getTime(), - trade.getMakerNodeAddress(), - trade.getTakerNodeAddress(), - trade.getArbitratorNodeAddress(), - null, - null, // do not include taker's reserve tx - null, - null, - null); - - // send init multisig requests on ack // TODO (woodser): only send InitMultisigRequests if arbitrator has maker reserve tx, else wait for that - TradeListener listener = new TradeListener() { - @Override - public void onAckMessage(AckMessage ackMessage, NodeAddress sender) { - if (sender.equals(trade.getMakerNodeAddress()) && - ackMessage.getSourceMsgClassName().equals(InitTradeRequest.class.getSimpleName()) && - ackMessage.getSourceUid().equals(makerRequest.getUid())) { - trade.removeListener(this); - if (ackMessage.isSuccess()) sendInitMultisigRequests(); - } - } - }; - trade.addListener(listener); + // handle request from taker + if (request.getSenderNodeAddress().equals(trade.getTakerNodeAddress())) { - // send request to maker - log.info("Send {} with offerId {} and uid {} to maker {} with pub key ring", makerRequest.getClass().getSimpleName(), makerRequest.getTradeId(), makerRequest.getUid(), trade.getMakerNodeAddress(), trade.getMakerPubKeyRing()); - processModel.getP2PService().sendEncryptedDirectMessage( - trade.getMakerNodeAddress(), // TODO (woodser): maker's address might be different from original owner address if they disconnect and reconnect, need to validate and update address when requests received - trade.getMakerPubKeyRing(), - makerRequest, - new SendDirectMessageListener() { - @Override - public void onArrived() { - log.info("{} arrived at maker: offerId={}; uid={}", makerRequest.getClass().getSimpleName(), makerRequest.getTradeId(), makerRequest.getUid()); - complete(); + // create request to initialize trade with maker + InitTradeRequest makerRequest = new InitTradeRequest( + processModel.getOfferId(), + request.getSenderNodeAddress(), + request.getPubKeyRing(), + trade.getAmount().value, + trade.getPrice().getValue(), + trade.getTakerFee().getValue(), + request.getAccountId(), + request.getPaymentAccountId(), + request.getPaymentMethodId(), + UUID.randomUUID().toString(), + Version.getP2PMessageVersion(), + sig, + new Date().getTime(), + trade.getMakerNodeAddress(), + trade.getTakerNodeAddress(), + trade.getArbitratorNodeAddress(), + null, + null, // do not include taker's reserve tx + null, + null, + null); + + // send request to maker + log.info("Send {} with offerId {} and uid {} to maker {} with pub key ring", makerRequest.getClass().getSimpleName(), makerRequest.getTradeId(), makerRequest.getUid(), trade.getMakerNodeAddress(), trade.getMakerPubKeyRing()); + processModel.getP2PService().sendEncryptedDirectMessage( + trade.getMakerNodeAddress(), // TODO (woodser): maker's address might be different from original owner address if they disconnect and reconnect, need to validate and update address when requests received + trade.getMakerPubKeyRing(), + makerRequest, + new SendDirectMessageListener() { + @Override + public void onArrived() { + log.info("{} arrived at maker: offerId={}; uid={}", makerRequest.getClass().getSimpleName(), makerRequest.getTradeId(), makerRequest.getUid()); + complete(); + } + @Override + public void onFault(String errorMessage) { + log.error("Sending {} failed: uid={}; peer={}; error={}", makerRequest.getClass().getSimpleName(), makerRequest.getUid(), trade.getArbitratorNodeAddress(), errorMessage); + appendToErrorMessage("Sending message failed: message=" + makerRequest + "\nerrorMessage=" + errorMessage); + failed(); + } } - @Override - public void onFault(String errorMessage) { - log.error("Sending {} failed: uid={}; peer={}; error={}", makerRequest.getClass().getSimpleName(), makerRequest.getUid(), trade.getArbitratorNodeAddress(), errorMessage); - appendToErrorMessage("Sending message failed: message=" + makerRequest + "\nerrorMessage=" + errorMessage); - trade.removeListener(listener); - failed(); - } - } - ); + ); + } + + // handle request from maker + else if (request.getSenderNodeAddress().equals(trade.getMakerNodeAddress())) { + sendInitMultisigRequests(); + complete(); // TODO: wait for InitMultisigRequest arrivals? + } else { + throw new RuntimeException("Request is not from maker or taker"); + } } catch (Throwable t) { failed(t); } @@ -138,14 +122,12 @@ public class ArbitratorSendsInitTradeAndMultisigRequests extends TradeTask { // ensure arbitrator has maker's reserve tx if (processModel.getMaker().getReserveTxHash() == null) { - log.warn("Arbitrator {} does not have maker's reserve tx after initializing trade", P2PService.getMyNodeAddress()); - failed(); - return; + throw new RuntimeException("Arbitrator does not have maker's reserve tx after initializing trade"); } - + // create wallet for multisig MoneroWallet multisigWallet = processModel.getXmrWalletService().createMultisigWallet(trade.getId()); - + // prepare multisig String preparedHex = multisigWallet.prepareMultisig(); trade.getSelf().setPreparedMultisigHex(preparedHex); @@ -176,8 +158,6 @@ public class ArbitratorSendsInitTradeAndMultisigRequests extends TradeTask { @Override public void onFault(String errorMessage) { log.error("Sending {} failed: uid={}; peer={}; error={}", initMultisigRequest.getClass().getSimpleName(), initMultisigRequest.getUid(), trade.getMakerNodeAddress(), errorMessage); - appendToErrorMessage("Sending message failed: message=" + initMultisigRequest + "\nerrorMessage=" + errorMessage); - failed(); } } ); @@ -196,8 +176,6 @@ public class ArbitratorSendsInitTradeAndMultisigRequests extends TradeTask { @Override public void onFault(String errorMessage) { log.error("Sending {} failed: uid={}; peer={}; error={}", initMultisigRequest.getClass().getSimpleName(), initMultisigRequest.getUid(), trade.getTakerNodeAddress(), errorMessage); - appendToErrorMessage("Sending message failed: message=" + initMultisigRequest + "\nerrorMessage=" + errorMessage); - failed(); } } ); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/MakerSendsInitTradeRequestIfUnreserved.java b/core/src/main/java/bisq/core/trade/protocol/tasks/MakerSendsInitTradeRequest.java similarity index 89% rename from core/src/main/java/bisq/core/trade/protocol/tasks/MakerSendsInitTradeRequestIfUnreserved.java rename to core/src/main/java/bisq/core/trade/protocol/tasks/MakerSendsInitTradeRequest.java index b0c654661e..080b28b0fb 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/MakerSendsInitTradeRequestIfUnreserved.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/MakerSendsInitTradeRequest.java @@ -37,9 +37,9 @@ import static bisq.core.util.Validator.checkTradeId; import static com.google.common.base.Preconditions.checkNotNull; @Slf4j -public class MakerSendsInitTradeRequestIfUnreserved extends TradeTask { +public class MakerSendsInitTradeRequest extends TradeTask { @SuppressWarnings({"unused"}) - public MakerSendsInitTradeRequestIfUnreserved(TaskRunner taskHandler, Trade trade) { + public MakerSendsInitTradeRequest(TaskRunner taskHandler, Trade trade) { super(taskHandler, trade); } @@ -48,28 +48,22 @@ public class MakerSendsInitTradeRequestIfUnreserved extends TradeTask { try { runInterceptHook(); - // skip if arbitrator is signer and therefore already has reserve tx - Offer offer = processModel.getOffer(); - if (offer.getOfferPayload().getArbitratorSigner().equals(trade.getArbitratorNodeAddress())) { - complete(); - return; - } - // verify trade InitTradeRequest makerRequest = (InitTradeRequest) processModel.getTradeMessage(); // arbitrator's InitTradeRequest to maker checkNotNull(makerRequest); checkTradeId(processModel.getOfferId(), makerRequest); - + // maker signs offer id as nonce to avoid challenge protocol // TODO (woodser): is this necessary? + Offer offer = processModel.getOffer(); byte[] sig = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), offer.getId().getBytes(Charsets.UTF_8)); - + // create request to arbitrator InitTradeRequest arbitratorRequest = new InitTradeRequest( offer.getId(), processModel.getMyNodeAddress(), processModel.getPubKeyRing(), offer.getAmount().value, - offer.getPrice().getValue(), + trade.getPrice().getValue(), offer.getMakerFee().value, trade.getProcessModel().getAccountId(), offer.getMakerPaymentAccountId(), @@ -86,7 +80,7 @@ public class MakerSendsInitTradeRequestIfUnreserved extends TradeTask { trade.getSelf().getReserveTxKey(), model.getXmrWalletService().getOrCreateAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString(), null); - + // send request to arbitrator log.info("Sending {} with offerId {} and uid {} to arbitrator {} with pub key ring {}", arbitratorRequest.getClass().getSimpleName(), arbitratorRequest.getTradeId(), arbitratorRequest.getUid(), trade.getArbitratorNodeAddress(), trade.getArbitratorPubKeyRing()); processModel.getP2PService().sendEncryptedDirectMessage( diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessDepositResponse.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessDepositResponse.java index d246b108f6..5d39362315 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessDepositResponse.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessDepositResponse.java @@ -60,8 +60,8 @@ public class ProcessDepositResponse extends TradeTask { processModel.getP2PService().sendEncryptedDirectMessage(trade.getTradingPeerNodeAddress(), trade.getTradingPeerPubKeyRing(), request, new SendDirectMessageListener() { @Override public void onArrived() { - complete(); log.info("{} arrived: trading peer={}; offerId={}; uid={}", request.getClass().getSimpleName(), trade.getTradingPeerNodeAddress(), trade.getId()); + complete(); } @Override public void onFault(String errorMessage) { diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessInitTradeRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessInitTradeRequest.java index eea02e38ec..35b7c8ed4c 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessInitTradeRequest.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessInitTradeRequest.java @@ -25,7 +25,6 @@ import bisq.core.trade.Trade; import bisq.core.trade.TradeUtils; import bisq.core.trade.messages.InitTradeRequest; import bisq.core.trade.protocol.TradingPeer; -import bisq.core.user.User; import bisq.common.taskrunner.TaskRunner; import org.bitcoinj.core.Coin; @@ -62,6 +61,9 @@ public class ProcessInitTradeRequest extends TradeTask { // handle request as arbitrator TradingPeer multisigParticipant; if (trade instanceof ArbitratorTrade) { + trade.setMakerPubKeyRing((trade.getOffer().getPubKeyRing())); + trade.setArbitratorPubKeyRing(processModel.getPubKeyRing()); + processModel.getArbitrator().setPubKeyRing(processModel.getPubKeyRing()); // TODO (woodser): why duplicating field in process model // handle request from taker if (request.getSenderNodeAddress().equals(request.getTakerNodeAddress())) { @@ -70,6 +72,17 @@ public class ProcessInitTradeRequest extends TradeTask { if (trade.getTakerPubKeyRing() != null) throw new RuntimeException("Pub key ring should not be initialized before processing InitTradeRequest"); trade.setTakerPubKeyRing(request.getPubKeyRing()); if (!TradeUtils.isMakerSignatureValid(request, request.getMakerSignature(), offer.getPubKeyRing())) throw new RuntimeException("Maker signature is invalid for the trade request"); // verify maker signature + + // check trade price + try { + long tradePrice = request.getTradePrice(); + offer.verifyTakersTradePrice(tradePrice); + trade.setPrice(tradePrice); + } catch (TradePriceOutOfToleranceException e) { + failed(e.getMessage()); + } catch (Throwable e2) { + failed(e2); + } } // handle request from maker @@ -79,6 +92,7 @@ public class ProcessInitTradeRequest extends TradeTask { if (trade.getMakerPubKeyRing() == null) trade.setMakerPubKeyRing(request.getPubKeyRing()); else if (!trade.getMakerPubKeyRing().equals(request.getPubKeyRing())) throw new RuntimeException("Init trade requests from maker and taker do not agree"); // TODO (woodser): proper handling trade.setMakerPubKeyRing(request.getPubKeyRing()); + if (trade.getPrice().getValue() != request.getTradePrice()) throw new RuntimeException("Maker and taker price do not agree"); } else { throw new RuntimeException("Sender is not trade's maker or taker"); } @@ -89,6 +103,17 @@ public class ProcessInitTradeRequest extends TradeTask { multisigParticipant = processModel.getTaker(); trade.setTakerNodeAddress(request.getSenderNodeAddress()); // arbitrator sends maker InitTradeRequest with taker's node address and pub key ring trade.setTakerPubKeyRing(request.getPubKeyRing()); + + // check trade price + try { + long tradePrice = request.getTradePrice(); + offer.verifyTakersTradePrice(tradePrice); + trade.setPrice(tradePrice); + } catch (TradePriceOutOfToleranceException e) { + failed(e.getMessage()); + } catch (Throwable e2) { + failed(e2); + } } // handle invalid trade type @@ -106,17 +131,6 @@ public class ProcessInitTradeRequest extends TradeTask { multisigParticipant.setAccountAgeWitnessSignature(request.getAccountAgeWitnessSignatureOfOfferId()); multisigParticipant.setCurrentDate(request.getCurrentDate()); - // check trade price - try { - long tradePrice = request.getTradePrice(); - offer.verifyTakersTradePrice(tradePrice); - trade.setPrice(tradePrice); - } catch (TradePriceOutOfToleranceException e) { - failed(e.getMessage()); - } catch (Throwable e2) { - failed(e2); - } - // check trade amount checkArgument(request.getTradeAmount() > 0); trade.setAmount(Coin.valueOf(request.getTradeAmount())); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessSignContractRequest.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessSignContractRequest.java index 6e10528039..951225f501 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessSignContractRequest.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessSignContractRequest.java @@ -18,7 +18,10 @@ package bisq.core.trade.protocol.tasks; +import static com.google.common.base.Preconditions.checkNotNull; + import bisq.common.app.Version; +import bisq.common.crypto.Hash; import bisq.common.crypto.PubKeyRing; import bisq.common.crypto.Sig; import bisq.common.taskrunner.TaskRunner; @@ -77,6 +80,7 @@ public class ProcessSignContractRequest extends TradeTask { // save contract and signature trade.setContract(contract); trade.setContractAsJson(contractAsJson); + trade.setContractHash(Hash.getSha256Hash(checkNotNull(contractAsJson))); trade.getSelf().setContractSignature(signature); // create response with contract signature @@ -87,6 +91,7 @@ public class ProcessSignContractRequest extends TradeTask { UUID.randomUUID().toString(), Version.getP2PMessageVersion(), new Date().getTime(), + contractAsJson, signature); // get response recipients. only arbitrator sends response to both peers diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessSignContractResponse.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessSignContractResponse.java index 78d52f0718..4593901f0e 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessSignContractResponse.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ProcessSignContractResponse.java @@ -44,10 +44,13 @@ public class ProcessSignContractResponse extends TradeTask { try { runInterceptHook(); - // get contract and signature + // compare contracts String contractAsJson = trade.getContractAsJson(); - SignContractResponse response = (SignContractResponse) processModel.getTradeMessage(); // TODO (woodser): verify response - String signature = response.getContractSignature(); + SignContractResponse response = (SignContractResponse) processModel.getTradeMessage(); + if (!contractAsJson.equals(response.getContractAsJson())) { + trade.getContract().printDiff(response.getContractAsJson()); + failed("Contracts are not matching"); + } // get peer info // TODO (woodser): make these utilities / refactor model @@ -60,6 +63,7 @@ public class ProcessSignContractResponse extends TradeTask { // verify signature // TODO (woodser): transfer contract for convenient comparison? + String signature = response.getContractSignature(); if (!Sig.verify(peerPubKeyRing.getSignaturePubKey(), contractAsJson, signature)) throw new RuntimeException("Peer's contract signature is invalid"); // set peer's signature diff --git a/core/src/test/java/bisq/core/arbitration/ArbitratorManagerTest.java b/core/src/test/java/bisq/core/arbitration/ArbitratorManagerTest.java index c4b45fee6f..7cf1c43464 100644 --- a/core/src/test/java/bisq/core/arbitration/ArbitratorManagerTest.java +++ b/core/src/test/java/bisq/core/arbitration/ArbitratorManagerTest.java @@ -44,7 +44,7 @@ public class ArbitratorManagerTest { User user = mock(User.class); ArbitratorService arbitratorService = mock(ArbitratorService.class); - ArbitratorManager manager = new ArbitratorManager(null, arbitratorService, user, null, false); + ArbitratorManager manager = new ArbitratorManager(null, arbitratorService, user, null); ArrayList languagesOne = new ArrayList() {{ add("en"); @@ -80,7 +80,7 @@ public class ArbitratorManagerTest { User user = mock(User.class); ArbitratorService arbitratorService = mock(ArbitratorService.class); - ArbitratorManager manager = new ArbitratorManager(null, arbitratorService, user, null, false); + ArbitratorManager manager = new ArbitratorManager(null, arbitratorService, user, null); ArrayList languagesOne = new ArrayList() {{ add("en"); diff --git a/daemon/src/main/java/bisq/daemon/grpc/GrpcDisputeAgentsService.java b/daemon/src/main/java/bisq/daemon/grpc/GrpcDisputeAgentsService.java index 3fae432993..d996abddfa 100644 --- a/daemon/src/main/java/bisq/daemon/grpc/GrpcDisputeAgentsService.java +++ b/daemon/src/main/java/bisq/daemon/grpc/GrpcDisputeAgentsService.java @@ -1,9 +1,10 @@ package bisq.daemon.grpc; import bisq.core.api.CoreApi; - import bisq.proto.grpc.RegisterDisputeAgentReply; import bisq.proto.grpc.RegisterDisputeAgentRequest; +import bisq.proto.grpc.UnregisterDisputeAgentReply; +import bisq.proto.grpc.UnregisterDisputeAgentRequest; import io.grpc.ServerInterceptor; import io.grpc.stub.StreamObserver; @@ -18,6 +19,7 @@ import lombok.extern.slf4j.Slf4j; import static bisq.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig.getCustomRateMeteringInterceptor; import static bisq.proto.grpc.DisputeAgentsGrpc.DisputeAgentsImplBase; import static bisq.proto.grpc.DisputeAgentsGrpc.getRegisterDisputeAgentMethod; +import static bisq.proto.grpc.DisputeAgentsGrpc.getUnregisterDisputeAgentMethod; import static java.util.concurrent.TimeUnit.SECONDS; @@ -41,10 +43,39 @@ class GrpcDisputeAgentsService extends DisputeAgentsImplBase { public void registerDisputeAgent(RegisterDisputeAgentRequest req, StreamObserver responseObserver) { try { - coreApi.registerDisputeAgent(req.getDisputeAgentType(), req.getRegistrationKey()); - var reply = RegisterDisputeAgentReply.newBuilder().build(); - responseObserver.onNext(reply); - responseObserver.onCompleted(); + GrpcErrorMessageHandler errorMessageHandler = new GrpcErrorMessageHandler(getRegisterDisputeAgentMethod().getFullMethodName(), responseObserver, exceptionHandler, log); + coreApi.registerDisputeAgent( + req.getDisputeAgentType(), + req.getRegistrationKey(), + () -> { + var reply = RegisterDisputeAgentReply.newBuilder().build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + }, + errorMessage -> { + if (!errorMessageHandler.isErrorHandled()) errorMessageHandler.handleErrorMessage(errorMessage); + }); + + } catch (Throwable cause) { + exceptionHandler.handleException(log, cause, responseObserver); + } + } + + @Override + public void unregisterDisputeAgent(UnregisterDisputeAgentRequest req, + StreamObserver responseObserver) { + try { + GrpcErrorMessageHandler errorMessageHandler = new GrpcErrorMessageHandler(getUnregisterDisputeAgentMethod().getFullMethodName(), responseObserver, exceptionHandler, log); + coreApi.unregisterDisputeAgent( + req.getDisputeAgentType(), + () -> { + var reply = UnregisterDisputeAgentReply.newBuilder().build(); + responseObserver.onNext(reply); + responseObserver.onCompleted(); + }, + errorMessage -> { + if (!errorMessageHandler.isErrorHandled()) errorMessageHandler.handleErrorMessage(errorMessage); + }); } catch (Throwable cause) { exceptionHandler.handleException(log, cause, responseObserver); } diff --git a/daemon/src/main/java/bisq/daemon/grpc/GrpcWalletsService.java b/daemon/src/main/java/bisq/daemon/grpc/GrpcWalletsService.java index 045ba6a3e4..8c64a738ff 100644 --- a/daemon/src/main/java/bisq/daemon/grpc/GrpcWalletsService.java +++ b/daemon/src/main/java/bisq/daemon/grpc/GrpcWalletsService.java @@ -415,7 +415,7 @@ class GrpcWalletsService extends WalletsImplBase { return getCustomRateMeteringInterceptor(coreApi.getConfig().appDataDir, this.getClass()) .or(() -> Optional.of(CallRateMeteringInterceptor.valueOf( new HashMap<>() {{ - put(getGetBalancesMethod().getFullMethodName(), new GrpcCallRateMeter(20, SECONDS)); + put(getGetBalancesMethod().getFullMethodName(), new GrpcCallRateMeter(50, SECONDS)); put(getGetAddressBalanceMethod().getFullMethodName(), new GrpcCallRateMeter(1, SECONDS)); put(getGetFundingAddressesMethod().getFullMethodName(), new GrpcCallRateMeter(1, SECONDS)); put(getSendBtcMethod().getFullMethodName(), new GrpcCallRateMeter(1, MINUTES)); diff --git a/desktop/src/main/java/bisq/desktop/main/overlays/notifications/NotificationCenter.java b/desktop/src/main/java/bisq/desktop/main/overlays/notifications/NotificationCenter.java index 243185843a..b05dadd65c 100644 --- a/desktop/src/main/java/bisq/desktop/main/overlays/notifications/NotificationCenter.java +++ b/desktop/src/main/java/bisq/desktop/main/overlays/notifications/NotificationCenter.java @@ -192,7 +192,7 @@ public class NotificationCenter { message = Res.get("notification.trade.accepted", role); } - if (trade instanceof BuyerTrade && phase.ordinal() == Trade.Phase.DEPOSIT_UNLOCKED.ordinal()) + if (trade instanceof BuyerTrade && phase.ordinal() == Trade.Phase.DEPOSITS_UNLOCKED.ordinal()) message = Res.get("notification.trade.confirmed"); else if (trade instanceof SellerTrade && phase.ordinal() == Trade.Phase.PAYMENT_SENT.ordinal()) message = Res.get("notification.trade.paymentStarted"); diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java index aef7a5790a..e4fcc2310e 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/buyer/BuyerStep2View.java @@ -162,7 +162,7 @@ public class BuyerStep2View extends TradeStepView { switch (state) { case BUYER_CONFIRMED_IN_UI_PAYMENT_SENT: busyAnimation.play(); - statusLabel.setText("Confirming payment sent. This can take up to a few minutes depending on connection speed. Please wait..."); + statusLabel.setText("Confirming payment sent. This can take up to a few minutes. Please wait..."); break; case BUYER_SENT_PAYMENT_SENT_MSG: busyAnimation.play(); diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java index 35f1c4f18e..088cbaae4e 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/pendingtrades/steps/seller/SellerStep3View.java @@ -121,7 +121,7 @@ public class SellerStep3View extends TradeStepView { switch (state) { case SELLER_CONFIRMED_IN_UI_PAYMENT_RECEIPT: busyAnimation.play(); - statusLabel.setText(Res.get("Confirming payment received. This can take up to a few minutes depending on connection speed. Please wait...")); + statusLabel.setText(Res.get("Confirming payment received. This can take up to a few minutes. Please wait...")); break; case SELLER_PUBLISHED_PAYOUT_TX: case SELLER_SENT_PAYOUT_TX_PUBLISHED_MSG: diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index c4e21805d7..ea1dbb8e7b 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -845,14 +845,6 @@ - - - - - - - - diff --git a/proto/src/main/proto/grpc.proto b/proto/src/main/proto/grpc.proto index 268c67235f..69a56427cb 100644 --- a/proto/src/main/proto/grpc.proto +++ b/proto/src/main/proto/grpc.proto @@ -220,6 +220,8 @@ message SendDisputeChatMessageReply { service DisputeAgents { rpc RegisterDisputeAgent (RegisterDisputeAgentRequest) returns (RegisterDisputeAgentReply) { } + rpc UnregisterDisputeAgent (UnregisterDisputeAgentRequest) returns (UnregisterDisputeAgentReply) { + } } message RegisterDisputeAgentRequest { @@ -230,6 +232,13 @@ message RegisterDisputeAgentRequest { message RegisterDisputeAgentReply { } +message UnregisterDisputeAgentRequest { + string dispute_agent_type = 1; +} + +message UnregisterDisputeAgentReply { +} + /////////////////////////////////////////////////////////////////////////////////////////// // Notifications /////////////////////////////////////////////////////////////////////////////////////////// @@ -530,6 +539,7 @@ message OfferInfo { string pub_key_ring = 26; string version_nr = 27; int32 protocol_version = 28; + string arbitrator_signer = 29; } message AvailabilityResultWithDescription { @@ -822,19 +832,20 @@ message TradeInfo { string payout_tx_id = 11; uint64 amount_as_long = 12; string price = 13; - string trading_peer_node_address = 14; - string state = 15; - string phase = 16; - string period_state = 17; - bool is_deposit_published = 18; - bool is_deposit_unlocked = 19; - bool is_payment_sent = 20; - bool is_payment_received = 21; - bool is_payout_published = 22; - bool is_completed = 23; - string contract_as_json = 24; - ContractInfo contract = 25; - string trade_volume = 26; + string arbitrator_node_address = 14; + string trading_peer_node_address = 15; + string state = 16; + string phase = 17; + string period_state = 18; + bool is_deposit_published = 19; + bool is_deposit_unlocked = 20; + bool is_payment_sent = 21; + bool is_payment_received = 22; + bool is_payout_published = 23; + bool is_completed = 24; + string contract_as_json = 25; + ContractInfo contract = 26; + string trade_volume = 27; string maker_deposit_tx_id = 100; string taker_deposit_tx_id = 101; diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 12980b87be..1d0acc3e75 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -184,7 +184,6 @@ message OfferAvailabilityResponse { repeated int32 supported_capabilities = 3; string uid = 4; string maker_signature = 5; - NodeAddress backup_arbitrator = 6; } message RefreshOfferMessage { @@ -328,7 +327,8 @@ message SignContractResponse { PubKeyRing pub_key_ring = 3; string uid = 4; int64 current_date = 5; - string contract_signature = 6; + string contract_as_json = 6; + string contract_signature = 7; } message DepositRequest { @@ -1598,14 +1598,13 @@ message OpenOffer { Offer offer = 1; State state = 2; - NodeAddress backup_arbitrator = 3; - int64 trigger_price = 4; - bool auto_split = 5; - repeated string scheduled_tx_hashes = 6; - string scheduled_amount = 7; // BigInteger - string reserve_tx_hash = 8; - string reserve_tx_hex = 9; - string reserve_tx_key = 10; + int64 trigger_price = 3; + bool auto_split = 4; + repeated string scheduled_tx_hashes = 5; + string scheduled_amount = 6; // BigInteger + string reserve_tx_hash = 7; + string reserve_tx_hex = 8; + string reserve_tx_key = 9; } message Tradable { @@ -1665,7 +1664,7 @@ message Trade { INIT = 1; DEPOSIT_REQUESTED = 2; DEPOSITS_PUBLISHED = 3; - DEPOSITS_CONFIRMED = 4; + DEPOSITS_UNLOCKED = 4; PAYMENT_SENT = 5; PAYMENT_RECEIVED = 6; PAYOUT_PUBLISHED = 7; @@ -1778,12 +1777,11 @@ message ProcessModel { int64 seller_payout_amount_from_mediation = 20; string maker_signature = 1001; - NodeAddress backup_arbitrator = 1002; - TradingPeer maker = 1003; - TradingPeer taker = 1004; - TradingPeer arbitrator = 1005; - NodeAddress temp_trading_peer_node_address = 1006; - string multisig_address = 1007; + TradingPeer maker = 1002; + TradingPeer taker = 1003; + TradingPeer arbitrator = 1004; + NodeAddress temp_trading_peer_node_address = 1005; + string multisig_address = 1006; } message TradingPeer {