From 4dafd57026b1059d855377a895a688679c038a3b Mon Sep 17 00:00:00 2001 From: woodser Date: Mon, 15 Nov 2021 17:20:28 -0500 Subject: [PATCH] use backup arbitrator if signing arbitrator not available --- Makefile | 11 ++ .../java/bisq/core/offer/OfferFilter.java | 4 +- .../java/bisq/core/offer/OfferPayload.java | 10 +- .../main/java/bisq/core/offer/OpenOffer.java | 51 +++++-- .../bisq/core/offer/OpenOfferManager.java | 27 ++-- .../availability/DisputeAgentSelection.java | 21 ++- .../availability/OfferAvailabilityModel.java | 2 +- .../ProcessOfferAvailabilityResponse.java | 8 +- .../tasks/SendOfferAvailabilityRequest.java | 3 +- .../messages/OfferAvailabilityResponse.java | 14 +- .../offer/placeoffer/PlaceOfferProtocol.java | 3 +- .../MakerProcessesSignOfferResponse.java | 2 +- .../tasks/MakerReservesTradeFunds.java | 2 +- .../tasks/MakerSendsSignOfferRequest.java | 2 +- .../java/bisq/core/trade/TradeManager.java | 41 +++--- .../trade/protocol/ArbitratorProtocol.java | 6 +- .../trade/protocol/BuyerAsMakerProtocol.java | 3 +- .../trade/protocol/BuyerAsTakerProtocol.java | 4 +- .../core/trade/protocol/ProcessModel.java | 24 +--- .../trade/protocol/SellerAsMakerProtocol.java | 3 +- .../trade/protocol/SellerAsTakerProtocol.java | 1 + .../bisq/core/trade/protocol/TradingPeer.java | 8 +- ...dsInitMultisigRequestsIfFundsReserved.java | 131 ------------------ ...torSendsInitTradeAndMultisigRequests.java} | 102 +++++++++++++- .../SendSignContractRequestAfterMultisig.java | 5 +- .../tasks/UpdateMultisigWithTradingPeer.java | 3 +- ...akerSendsInitTradeRequestIfUnreserved.java | 94 +++++-------- .../tasks/taker/TakerReservesTradeFunds.java | 9 +- ...akerSendsInitTradeRequestToArbitrator.java | 123 +++++++++------- .../bisq/core/trade/TradableListTest.java | 2 +- .../editoffer/EditOfferDataModel.java | 2 +- proto/src/main/proto/pb.proto | 37 ++--- 32 files changed, 370 insertions(+), 388 deletions(-) delete mode 100644 core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorSendsInitMultisigRequestsIfFundsReserved.java rename core/src/main/java/bisq/core/trade/protocol/tasks/{ArbitratorSendsInitTradeRequestToMakerIfFromTaker.java => ArbitratorSendsInitTradeAndMultisigRequests.java} (53%) diff --git a/Makefile b/Makefile index daa5247fdb..c255c33822 100644 --- a/Makefile +++ b/Makefile @@ -53,6 +53,17 @@ arbitrator-desktop: --appName=haveno-XMR_STAGENET_arbitrator \ --apiPassword=apitest \ --apiPort=9998 + +arbitrator-desktop2: + # Arbitrator and mediator need to be registerd in the UI after launching it. + ./haveno-desktop \ + --baseCurrencyNetwork=XMR_STAGENET \ + --useLocalhostForP2P=true \ + --useDevPrivilegeKeys=true \ + --nodePort=7777 \ + --appName=haveno-XMR_STAGENET_arbitrator2 \ + --apiPassword=apitest \ + --apiPort=10001 alice-desktop: ./haveno-desktop \ diff --git a/core/src/main/java/bisq/core/offer/OfferFilter.java b/core/src/main/java/bisq/core/offer/OfferFilter.java index 9d42736e41..466f0216cb 100644 --- a/core/src/main/java/bisq/core/offer/OfferFilter.java +++ b/core/src/main/java/bisq/core/offer/OfferFilter.java @@ -216,8 +216,8 @@ public class OfferFilter { public boolean hasValidSignature(Offer offer) { // get arbitrator - Mediator arbitrator = user.getAcceptedMediatorByAddress(offer.getOfferPayload().getArbitratorNodeAddress()); // TODO (woodser): does this return null if arbitrator goes offline? - if (arbitrator == null) return false; // TODO (woodser): if arbitrator is null, get arbirator's pub key ring from store, otherwise cannot validate and offer is not seen by takers when arbitrator goes offline + Mediator arbitrator = user.getAcceptedMediatorByAddress(offer.getOfferPayload().getArbitratorSigner()); + if (arbitrator == null) return false; // invalid arbitrator // validate arbitrator signature return TradeUtils.isArbitratorSignatureValid(offer.getOfferPayload(), arbitrator); diff --git a/core/src/main/java/bisq/core/offer/OfferPayload.java b/core/src/main/java/bisq/core/offer/OfferPayload.java index 9c98429c7e..207172c06f 100644 --- a/core/src/main/java/bisq/core/offer/OfferPayload.java +++ b/core/src/main/java/bisq/core/offer/OfferPayload.java @@ -169,7 +169,7 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay // address and signature of signing arbitrator @Setter - private NodeAddress arbitratorNodeAddress; + private NodeAddress arbitratorSigner; @Setter @Nullable private String arbitratorSignature; @@ -255,7 +255,7 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay this.hashOfChallenge = hashOfChallenge; this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap); this.protocolVersion = protocolVersion; - this.arbitratorNodeAddress = arbitratorSigner; + this.arbitratorSigner = arbitratorSigner; this.arbitratorSignature = arbitratorSignature; this.reserveTxKeyImages = reserveTxKeyImages; } @@ -295,7 +295,7 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay .setUpperClosePrice(upperClosePrice) .setIsPrivateOffer(isPrivateOffer) .setProtocolVersion(protocolVersion) - .setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage()); + .setArbitratorSigner(arbitratorSigner.toProtoMessage()); builder.setOfferFeePaymentTxId(checkNotNull(offerFeePaymentTxId, "OfferPayload is in invalid state: offerFeePaymentTxID is not set when adding to P2P network.")); @@ -357,7 +357,7 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay hashOfChallenge, extraDataMapMap, proto.getProtocolVersion(), - NodeAddress.fromProto(proto.getArbitratorNodeAddress()), + NodeAddress.fromProto(proto.getArbitratorSigner()), ProtoUtil.stringOrNullFromProto(proto.getArbitratorSignature()), proto.getReserveTxKeyImagesList() == null ? null : new ArrayList(proto.getReserveTxKeyImagesList())); } @@ -424,7 +424,7 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay ",\n hashOfChallenge='" + hashOfChallenge + '\'' + ",\n extraDataMap=" + extraDataMap + ",\n protocolVersion=" + protocolVersion + - ",\n arbitratorSigner=" + arbitratorNodeAddress + + ",\n arbitratorSigner=" + arbitratorSigner + ",\n arbitratorSignature=" + arbitratorSignature + "\n}"; } diff --git a/core/src/main/java/bisq/core/offer/OpenOffer.java b/core/src/main/java/bisq/core/offer/OpenOffer.java index 5d5c63596f..cbd99baac2 100644 --- a/core/src/main/java/bisq/core/offer/OpenOffer.java +++ b/core/src/main/java/bisq/core/offer/OpenOffer.java @@ -24,9 +24,7 @@ import bisq.network.p2p.NodeAddress; import bisq.common.Timer; import bisq.common.UserThread; import bisq.common.proto.ProtoUtil; -import java.util.ArrayList; import java.util.Date; -import java.util.List; import java.util.Optional; import lombok.EqualsAndHashCode; @@ -58,10 +56,17 @@ public final class OpenOffer implements Tradable { @Getter @Setter @Nullable - private NodeAddress arbitratorNodeAddress; + private NodeAddress backupArbitrator; @Setter @Getter - private List frozenKeyImages = new ArrayList<>(); + private String reserveTxHash; + @Setter + @Getter + private String reserveTxHex; + @Setter + @Getter + private String reserveTxKey; + // Added in v1.5.3. // If market price reaches that trigger price the offer gets deactivated @@ -81,11 +86,17 @@ public final class OpenOffer implements Tradable { state = State.AVAILABLE; } - public OpenOffer(Offer offer, long triggerPrice, List frozenKeyImages) { + public OpenOffer(Offer offer, + long triggerPrice, + String reserveTxHash, + String reserveTxHex, + String reserveTxKey) { this.offer = offer; this.triggerPrice = triggerPrice; state = State.AVAILABLE; - this.frozenKeyImages = frozenKeyImages; + this.reserveTxHash = reserveTxHash; + this.reserveTxHex = reserveTxHex; + this.reserveTxKey = reserveTxKey; } /////////////////////////////////////////////////////////////////////////////////////////// @@ -94,12 +105,18 @@ public final class OpenOffer implements Tradable { private OpenOffer(Offer offer, State state, - @Nullable NodeAddress arbitratorNodeAddress, - long triggerPrice) { + @Nullable NodeAddress backupArbitrator, + long triggerPrice, + String reserveTxHash, + String reserveTxHex, + String reserveTxKey) { this.offer = offer; this.state = state; - this.arbitratorNodeAddress = arbitratorNodeAddress; + this.backupArbitrator = backupArbitrator; this.triggerPrice = triggerPrice; + this.reserveTxHash = reserveTxHash; + this.reserveTxHex = reserveTxHex; + this.reserveTxKey = reserveTxKey; if (this.state == State.RESERVED) setState(State.AVAILABLE); @@ -111,9 +128,11 @@ public final class OpenOffer implements Tradable { .setOffer(offer.toProtoMessage()) .setTriggerPrice(triggerPrice) .setState(protobuf.OpenOffer.State.valueOf(state.name())) - .addAllFrozenKeyImages(frozenKeyImages); + .setReserveTxHash(reserveTxHash) + .setReserveTxHex(reserveTxHex) + .setReserveTxKey(reserveTxKey); - Optional.ofNullable(arbitratorNodeAddress).ifPresent(nodeAddress -> builder.setArbitratorNodeAddress(nodeAddress.toProtoMessage())); + Optional.ofNullable(backupArbitrator).ifPresent(nodeAddress -> builder.setBackupArbitrator(nodeAddress.toProtoMessage())); return protobuf.Tradable.newBuilder().setOpenOffer(builder).build(); } @@ -121,9 +140,11 @@ 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.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null, - proto.getTriggerPrice()); - openOffer.setFrozenKeyImages(proto.getFrozenKeyImagesList()); + proto.hasBackupArbitrator() ? NodeAddress.fromProto(proto.getBackupArbitrator()) : null, + proto.getTriggerPrice(), + proto.getReserveTxHash(), + proto.getReserveTxHex(), + proto.getReserveTxKey()); return openOffer; } @@ -187,7 +208,7 @@ public final class OpenOffer implements Tradable { return "OpenOffer{" + ",\n offer=" + offer + ",\n state=" + state + - ",\n arbitratorNodeAddress=" + arbitratorNodeAddress + + ",\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 d39d432f31..26e8d2f5cd 100644 --- a/core/src/main/java/bisq/core/offer/OpenOfferManager.java +++ b/core/src/main/java/bisq/core/offer/OpenOfferManager.java @@ -91,7 +91,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import lombok.Getter; -import monero.daemon.model.MoneroOutput; import org.jetbrains.annotations.NotNull; import javax.annotation.Nullable; @@ -416,10 +415,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe model, transaction -> { - // save frozen key images with open offer - List frozenKeyImages = new ArrayList(); - for (MoneroOutput output : model.getReserveTx().getInputs()) frozenKeyImages.add(output.getKeyImage().getHex()); - OpenOffer openOffer = new OpenOffer(offer, triggerPrice, frozenKeyImages); + // save reserve tx with open offer + OpenOffer openOffer = new OpenOffer(offer, triggerPrice, model.getReserveTx().getHash(), model.getReserveTx().getFullHex(), model.getReserveTx().getKey()); openOffers.add(openOffer); requestPersistence(); resultHandler.handleResult(transaction); @@ -577,7 +574,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe closedTradableManager.add(openOffer); log.info("onRemoved offerId={}", offer.getId()); btcWalletService.resetAddressEntriesForOpenOffer(offer.getId()); - for (String frozenKeyImage : openOffer.getFrozenKeyImages()) xmrWalletService.getWallet().thawOutput(frozenKeyImage); + for (String frozenKeyImage : offer.getOfferPayload().getReserveTxKeyImages()) xmrWalletService.getWallet().thawOutput(frozenKeyImage); requestPersistence(); resultHandler.handleResult(); } @@ -642,7 +639,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe } // verify arbitrator is signer of offer payload - if (!request.getOfferPayload().getArbitratorNodeAddress().equals(thisAddress)) { + if (!thisAddress.equals(request.getOfferPayload().getArbitratorSigner())) { errorMessage = "Cannot sign offer because offer payload is for a different arbitrator"; log.info(errorMessage); sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, errorMessage); @@ -784,7 +781,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe Optional openOfferOptional = getOpenOfferById(request.offerId); AvailabilityResult availabilityResult; String makerSignature = null; - NodeAddress arbitratorNodeAddress = null; + NodeAddress backupArbitratorNodeAddress = null; if (openOfferOptional.isPresent()) { OpenOffer openOffer = openOfferOptional.get(); if (!apiUserDeniedByOffer(request)) { @@ -793,10 +790,10 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe Offer offer = openOffer.getOffer(); if (preferences.getIgnoreTradersList().stream().noneMatch(fullAddress -> fullAddress.equals(peer.getFullAddress()))) { - // use signing arbitrator if available, otherwise use least used arbitrator - boolean isSignerOnline = true; - arbitratorNodeAddress = isSignerOnline ? offer.getOfferPayload().getArbitratorNodeAddress() : DisputeAgentSelection.getLeastUsedArbitrator(tradeStatisticsManager, mediatorManager).getNodeAddress(); - openOffer.setArbitratorNodeAddress(arbitratorNodeAddress); + // 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? String tradeRequestAsJson = Utilities.objectToJson(request.getTradeRequest()); @@ -848,7 +845,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe OfferAvailabilityResponse offerAvailabilityResponse = new OfferAvailabilityResponse(request.offerId, availabilityResult, makerSignature, - arbitratorNodeAddress); + backupArbitratorNodeAddress); log.info("Send {} with offerId {} and uid {} to peer {}", offerAvailabilityResponse.getClass().getSimpleName(), offerAvailabilityResponse.getOfferId(), offerAvailabilityResponse.getUid(), peer); @@ -933,6 +930,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe /////////////////////////////////////////////////////////////////////////////////////////// // Update persisted offer if a new capability is required after a software update /////////////////////////////////////////////////////////////////////////////////////////// + + // TODO (woodser): arbitrator signature will be invalid if offer updated (exclude updateable fields from signature? re-sign?) private void maybeUpdatePersistedOffers() { // We need to clone to avoid ConcurrentModificationException @@ -1019,7 +1018,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe originalOfferPayload.getHashOfChallenge(), updatedExtraDataMap, protocolVersion, - originalOfferPayload.getArbitratorNodeAddress(), + originalOfferPayload.getArbitratorSigner(), originalOfferPayload.getArbitratorSignature(), originalOfferPayload.getReserveTxKeyImages()); 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 7b3bee5fc5..cc3d72600b 100644 --- a/core/src/main/java/bisq/core/offer/availability/DisputeAgentSelection.java +++ b/core/src/main/java/bisq/core/offer/availability/DisputeAgentSelection.java @@ -21,7 +21,7 @@ import bisq.core.support.dispute.agent.DisputeAgent; import bisq.core.support.dispute.agent.DisputeAgentManager; import bisq.core.trade.statistics.TradeStatistics3; import bisq.core.trade.statistics.TradeStatisticsManager; - +import bisq.network.p2p.NodeAddress; import bisq.common.util.Tuple2; import com.google.common.annotations.VisibleForTesting; @@ -43,15 +43,25 @@ import static com.google.common.base.Preconditions.checkArgument; @Slf4j public class DisputeAgentSelection { public static final int LOOK_BACK_RANGE = 100; - + public static T getLeastUsedArbitrator(TradeStatisticsManager tradeStatisticsManager, DisputeAgentManager disputeAgentManager) { return getLeastUsedDisputeAgent(tradeStatisticsManager, - disputeAgentManager); + disputeAgentManager, + null); +} + + public static T getLeastUsedArbitrator(TradeStatisticsManager tradeStatisticsManager, + DisputeAgentManager disputeAgentManager, + NodeAddress excludedArbitrator) { + return getLeastUsedDisputeAgent(tradeStatisticsManager, + disputeAgentManager, + excludedArbitrator); } private static T getLeastUsedDisputeAgent(TradeStatisticsManager tradeStatisticsManager, - DisputeAgentManager disputeAgentManager) { + DisputeAgentManager disputeAgentManager, + NodeAddress excludedDisputeAgent) { // We take last 100 entries from trade statistics List list = new ArrayList<>(tradeStatisticsManager.getObservableTradeStatisticsSet()); list.sort(Comparator.comparing(TradeStatistics3::getDateAsLong)); @@ -71,6 +81,9 @@ public class DisputeAgentSelection { .map(disputeAgent -> disputeAgent.getNodeAddress().getFullAddress()) .collect(Collectors.toSet()); + if (excludedDisputeAgent != null) disputeAgents.remove(excludedDisputeAgent.getFullAddress()); + if (disputeAgents.isEmpty()) return null; + String result = getLeastUsedDisputeAgent(lastAddressesUsedInTrades, disputeAgents); Optional optionalDisputeAgent = disputeAgentManager.getObservableMap().values().stream() 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 ffed475112..7573b72da7 100644 --- a/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java +++ b/core/src/main/java/bisq/core/offer/availability/OfferAvailabilityModel.java @@ -67,7 +67,7 @@ public class OfferAvailabilityModel implements Model { private String makerSignature; @Setter @Getter - private NodeAddress arbitratorNodeAddress; + 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 cf39489752..fc01b63ba4 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 @@ -53,19 +53,17 @@ public class ProcessOfferAvailabilityResponse extends Task { public SendOfferAvailabilityRequest(TaskRunner taskHandler, OfferAvailabilityModel model) { @@ -78,7 +79,7 @@ public class SendOfferAvailabilityRequest extends Task { new Date().getTime(), offer.getMakerNodeAddress(), P2PService.getMyNodeAddress(), - null, // maker provides node address of arbitrator on response + null, // maker provides node address of backup arbitrator on response null, // reserve tx not sent from taker to maker null, null, diff --git a/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityResponse.java b/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityResponse.java index 0d49401f1b..faa96c7adf 100644 --- a/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityResponse.java +++ b/core/src/main/java/bisq/core/offer/messages/OfferAvailabilityResponse.java @@ -46,19 +46,19 @@ public final class OfferAvailabilityResponse extends OfferMessage implements Sup @Nullable private final String makerSignature; - private final NodeAddress arbitratorNodeAddress; + private final NodeAddress backupArbitrator; public OfferAvailabilityResponse(String offerId, AvailabilityResult availabilityResult, String makerSignature, - NodeAddress arbitratorNodeAddress) { + NodeAddress backupArbitrator) { this(offerId, availabilityResult, Capabilities.app, Version.getP2PMessageVersion(), UUID.randomUUID().toString(), makerSignature, - arbitratorNodeAddress); + backupArbitrator); } @@ -77,19 +77,19 @@ public final class OfferAvailabilityResponse extends OfferMessage implements Sup this.availabilityResult = availabilityResult; this.supportedCapabilities = supportedCapabilities; this.makerSignature = makerSignature; - this.arbitratorNodeAddress = arbitratorNodeAddress; + this.backupArbitrator = arbitratorNodeAddress; } @Override public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { final protobuf.OfferAvailabilityResponse.Builder builder = protobuf.OfferAvailabilityResponse.newBuilder() .setOfferId(offerId) - .setAvailabilityResult(protobuf.AvailabilityResult.valueOf(availabilityResult.name())) - .setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage()); + .setAvailabilityResult(protobuf.AvailabilityResult.valueOf(availabilityResult.name())); Optional.ofNullable(supportedCapabilities).ifPresent(e -> 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) @@ -103,6 +103,6 @@ public final class OfferAvailabilityResponse extends OfferMessage implements Sup messageVersion, proto.getUid().isEmpty() ? null : proto.getUid(), proto.getMakerSignature().isEmpty() ? null : proto.getMakerSignature(), - NodeAddress.fromProto(proto.getArbitratorNodeAddress())); + proto.hasBackupArbitrator() ? NodeAddress.fromProto(proto.getBackupArbitrator()) : null); } } diff --git a/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferProtocol.java b/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferProtocol.java index 4fadf2a9f9..abb519af06 100644 --- a/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferProtocol.java +++ b/core/src/main/java/bisq/core/offer/placeoffer/PlaceOfferProtocol.java @@ -56,6 +56,7 @@ public class PlaceOfferProtocol { // Called from UI /////////////////////////////////////////////////////////////////////////////////////////// + // TODO (woodser): this returns before offer is placed public void placeOffer() { log.debug("placeOffer() " + model.getOffer().getId()); TaskRunner taskRunner = new TaskRunner<>(model, @@ -82,7 +83,7 @@ public class PlaceOfferProtocol { log.debug("handleSignOfferResponse() " + model.getOffer().getId()); model.setSignOfferResponse(response); - if (!model.getOffer().getOfferPayload().getArbitratorNodeAddress().equals(sender)) { + if (!model.getOffer().getOfferPayload().getArbitratorSigner().equals(sender)) { log.warn("Ignoring sign offer response from different sender"); return; } diff --git a/core/src/main/java/bisq/core/offer/placeoffer/tasks/MakerProcessesSignOfferResponse.java b/core/src/main/java/bisq/core/offer/placeoffer/tasks/MakerProcessesSignOfferResponse.java index 4c985647cc..ed74e1f92d 100644 --- a/core/src/main/java/bisq/core/offer/placeoffer/tasks/MakerProcessesSignOfferResponse.java +++ b/core/src/main/java/bisq/core/offer/placeoffer/tasks/MakerProcessesSignOfferResponse.java @@ -39,7 +39,7 @@ public class MakerProcessesSignOfferResponse extends Task { runInterceptHook(); // validate arbitrator signature - Mediator arbitrator = checkNotNull(model.getUser().getAcceptedMediatorByAddress(offer.getOfferPayload().getArbitratorNodeAddress()), "user.getAcceptedMediatorByAddress(arbitratorNodeAddress) must not be null"); + Mediator arbitrator = checkNotNull(model.getUser().getAcceptedMediatorByAddress(offer.getOfferPayload().getArbitratorSigner()), "user.getAcceptedMediatorByAddress(arbitratorSigner) must not be null"); if (!TradeUtils.isArbitratorSignatureValid(model.getSignOfferResponse().getSignedOfferPayload(), arbitrator)) { throw new RuntimeException("Offer payload has invalid arbitrator signature"); } diff --git a/core/src/main/java/bisq/core/offer/placeoffer/tasks/MakerReservesTradeFunds.java b/core/src/main/java/bisq/core/offer/placeoffer/tasks/MakerReservesTradeFunds.java index b16e322c60..74a7dea8ea 100644 --- a/core/src/main/java/bisq/core/offer/placeoffer/tasks/MakerReservesTradeFunds.java +++ b/core/src/main/java/bisq/core/offer/placeoffer/tasks/MakerReservesTradeFunds.java @@ -64,7 +64,7 @@ public class MakerReservesTradeFunds extends Task { // TODO (woodser): persist model.setReserveTx(reserveTx); offer.getOfferPayload().setReserveTxKeyImages(reservedKeyImages); - offer.setOfferFeePaymentTxId(reserveTx.getHash()); // TODO (woodser): rename this to reserve tx id + offer.setOfferFeePaymentTxId(reserveTx.getHash()); // TODO (woodser): don't use this field offer.setState(Offer.State.OFFER_FEE_RESERVED); complete(); } catch (Throwable t) { 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 474c163a32..a9038170c3 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 @@ -68,7 +68,7 @@ public class MakerSendsSignOfferRequest extends Task { returnAddress); // get signing arbitrator - Mediator arbitrator = checkNotNull(model.getUser().getAcceptedMediatorByAddress(offer.getOfferPayload().getArbitratorNodeAddress()), "user.getAcceptedMediatorByAddress(mediatorNodeAddress) must not be null"); + Mediator arbitrator = checkNotNull(model.getUser().getAcceptedMediatorByAddress(offer.getOfferPayload().getArbitratorSigner()), "user.getAcceptedMediatorByAddress(mediatorNodeAddress) must not be null"); // send request model.getP2PService().sendEncryptedDirectMessage(arbitrator.getNodeAddress(), arbitrator.getPubKeyRing(), request, new SendDirectMessageListener() { diff --git a/core/src/main/java/bisq/core/trade/TradeManager.java b/core/src/main/java/bisq/core/trade/TradeManager.java index 2c4c1a5446..997a371eab 100644 --- a/core/src/main/java/bisq/core/trade/TradeManager.java +++ b/core/src/main/java/bisq/core/trade/TradeManager.java @@ -433,11 +433,13 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi request.getTakerNodeAddress(), request.getArbitratorNodeAddress()); - // set reserve tx hash + // set reserve tx hash if available Optional signedOfferOptional = openOfferManager.getSignedOfferById(request.getTradeId()); - if (!signedOfferOptional.isPresent()) return; - SignedOffer signedOffer = signedOfferOptional.get(); - trade.getMaker().setReserveTxHash(signedOffer.getReserveTxHash()); + if (signedOfferOptional.isPresent()) { + SignedOffer signedOffer = signedOfferOptional.get(); + trade.getMaker().setReserveTxHash(signedOffer.getReserveTxHash()); + } + initTradeAndProtocol(trade, getTradeProtocol(trade)); tradableList.add(trade); } @@ -464,20 +466,15 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi } Offer offer = openOffer.getOffer(); - openOfferManager.reserveOpenOffer(openOffer); // TODO (woodser): reserve offer if arbitrator? - // verify request is from signing arbitrator when they're online, else from selected arbitrator - if (!sender.equals(offer.getOfferPayload().getArbitratorNodeAddress())) { - boolean isSignerOnline = true; // TODO (woodser): determine if signer is online and test - if (isSignerOnline) { - log.warn("Ignoring InitTradeRequest from {} with tradeId {} because request must be from signing arbitrator when online", sender, request.getTradeId()); - return; - } else if (!sender.equals(openOffer.getArbitratorNodeAddress())) { - log.warn("Ignoring InitTradeRequest from {} with tradeId {} because request must be from selected arbitrator when signing arbitrator is offline", sender, request.getTradeId()); - return; - } + // 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()); + return; } + openOfferManager.reserveOpenOffer(openOffer); // TODO (woodser): reserve offer if arbitrator? + Trade trade; if (offer.isBuyOffer()) trade = new BuyerAsMakerTrade(offer, @@ -504,12 +501,14 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi //System.out.println("TradeManager trade.setTradingPeerNodeAddress(): " + sender); //trade.setTradingPeerNodeAddress(sender); - // TODO (woodser): what if maker's address changes while offer open, or taker's address changes after multisig deposit available? need to verify and update + // TODO (woodser): what if maker's address changes while offer open, or taker's address changes after multisig deposit available? need to verify and update. see OpenOfferManager.maybeUpdatePersistedOffers() trade.setArbitratorPubKeyRing(user.getAcceptedMediatorByAddress(sender).getPubKeyRing()); trade.setMakerPubKeyRing(trade.getOffer().getPubKeyRing()); initTradeAndProtocol(trade, getTradeProtocol(trade)); - trade.getProcessModel().setReserveTxHash(offer.getOfferFeePaymentTxId()); // TODO (woodser): initialize in initTradeAndProtocol ? - trade.getProcessModel().setFrozenKeyImages(openOffer.getFrozenKeyImages()); + trade.getSelf().setReserveTxHash(openOffer.getReserveTxHash()); // TODO (woodser): initialize in initTradeAndProtocol? + trade.getSelf().setReserveTxHex(openOffer.getReserveTxHex()); + trade.getSelf().setReserveTxKey(openOffer.getReserveTxKey()); + trade.getSelf().setReserveTxKeyImages(offer.getOfferPayload().getReserveTxKeyImages()); tradableList.add(trade); ((MakerProtocol) getTradeProtocol(trade)).handleInitTradeRequest(request, sender, errorMessage -> { @@ -702,7 +701,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi UUID.randomUUID().toString(), model.getPeerNodeAddress(), P2PService.getMyNodeAddress(), - offer.getOfferPayload().getArbitratorNodeAddress()); + offer.getOfferPayload().getArbitratorSigner()); } else { trade = new BuyerAsTakerTrade(offer, amount, @@ -713,12 +712,12 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi UUID.randomUUID().toString(), model.getPeerNodeAddress(), P2PService.getMyNodeAddress(), - offer.getOfferPayload().getArbitratorNodeAddress()); + offer.getOfferPayload().getArbitratorSigner()); } trade.getProcessModel().setTradeMessage(model.getTradeRequest()); trade.getProcessModel().setMakerSignature(model.getMakerSignature()); - trade.getProcessModel().setArbitratorNodeAddress(model.getArbitratorNodeAddress()); + 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/protocol/ArbitratorProtocol.java b/core/src/main/java/bisq/core/trade/protocol/ArbitratorProtocol.java index 934b2c3d65..879126faee 100644 --- a/core/src/main/java/bisq/core/trade/protocol/ArbitratorProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/ArbitratorProtocol.java @@ -7,11 +7,10 @@ import bisq.core.trade.messages.InitMultisigRequest; import bisq.core.trade.messages.InitTradeRequest; import bisq.core.trade.messages.SignContractRequest; import bisq.core.trade.protocol.tasks.ApplyFilter; -import bisq.core.trade.protocol.tasks.ArbitratorSendsInitTradeRequestToMakerIfFromTaker; +import bisq.core.trade.protocol.tasks.ArbitratorSendsInitTradeAndMultisigRequests; import bisq.core.trade.protocol.tasks.ProcessDepositRequest; import bisq.core.trade.protocol.tasks.ProcessInitMultisigRequest; import bisq.core.trade.protocol.tasks.ArbitratorProcessesReserveTx; -import bisq.core.trade.protocol.tasks.ArbitratorSendsInitMultisigRequestsIfFundsReserved; import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest; import bisq.core.trade.protocol.tasks.ProcessSignContractRequest; import bisq.core.util.Validator; @@ -42,8 +41,7 @@ public class ArbitratorProtocol extends DisputeProtocol { ApplyFilter.class, ProcessInitTradeRequest.class, ArbitratorProcessesReserveTx.class, - ArbitratorSendsInitTradeRequestToMakerIfFromTaker.class, - ArbitratorSendsInitMultisigRequestsIfFundsReserved.class)) + ArbitratorSendsInitTradeAndMultisigRequests.class)) .executeTasks(); } 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 c73036fb6e..2623f1adcf 100644 --- a/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/BuyerAsMakerProtocol.java @@ -42,6 +42,7 @@ import bisq.core.trade.protocol.tasks.buyer.BuyerSendsDelayedPayoutTxSignatureRe import bisq.core.trade.protocol.tasks.buyer.BuyerSignsDelayedPayoutTx; import bisq.core.trade.protocol.tasks.buyer.BuyerVerifiesPreparedDelayedPayoutTx; import bisq.core.trade.protocol.tasks.maker.MakerRemovesOpenOffer; +import bisq.core.trade.protocol.tasks.maker.MakerSendsInitTradeRequestIfUnreserved; import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment; import bisq.core.util.Validator; @@ -142,7 +143,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, // TODO (woodser): implement this + MakerSendsInitTradeRequestIfUnreserved.class, MakerRemovesOpenOffer.class). using(new TradeTaskRunner(trade, () -> { diff --git a/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java b/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java index 3cc42a555c..3d949277d3 100644 --- a/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/BuyerAsTakerProtocol.java @@ -76,9 +76,9 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol public BuyerAsTakerProtocol(BuyerAsTakerTrade trade) { super(trade); - Offer offer = checkNotNull(trade.getOffer()); trade.getTradingPeer().setPubKeyRing(offer.getPubKeyRing()); + trade.setMakerPubKeyRing(offer.getPubKeyRing()); // TODO (woodser): setup deposit and payout listeners on construction for startup like before rebase? } @@ -100,7 +100,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol .setup(tasks( ApplyFilter.class, TakerReservesTradeFunds.class, - TakerSendsInitTradeRequestToArbitrator.class) + TakerSendsInitTradeRequestToArbitrator.class) // TODO (woodser): app hangs if this pipeline fails. use .using() like below .withTimeout(30)) .executeTasks(); } 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 d7eae6b031..41109d110c 100644 --- a/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java +++ b/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java @@ -25,7 +25,6 @@ import bisq.core.btc.wallet.XmrWalletService; import bisq.core.filter.FilterManager; import bisq.core.network.MessageState; import bisq.core.offer.Offer; -import bisq.core.offer.OfferPayload.Direction; import bisq.core.offer.OpenOfferManager; import bisq.core.payment.PaymentAccount; import bisq.core.payment.payload.PaymentAccountPayload; @@ -33,9 +32,7 @@ import bisq.core.proto.CoreProtoResolver; import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; import bisq.core.support.dispute.mediation.mediator.MediatorManager; import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; -import bisq.core.trade.ArbitratorTrade; import bisq.core.trade.MakerTrade; -import bisq.core.trade.TakerTrade; import bisq.core.trade.Trade; import bisq.core.trade.TradeManager; import bisq.core.trade.messages.TradeMessage; @@ -60,7 +57,6 @@ import org.bitcoinj.core.Transaction; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; -import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -145,7 +141,7 @@ public class ProcessModel implements Model, PersistablePayload { // After successful verified we copy that over to the trade.tradingPeerAddress @Nullable @Setter - private NodeAddress tempTradingPeerNodeAddress; // TODO (woodser): remove entirely + private NodeAddress tempTradingPeerNodeAddress; // TODO (woodser): remove entirely? // Added in v.1.1.6 @Nullable @@ -166,17 +162,11 @@ public class ProcessModel implements Model, PersistablePayload { private String makerSignature; @Getter @Setter - private NodeAddress arbitratorNodeAddress; + private NodeAddress backupArbitrator; @Nullable @Getter @Setter transient private MoneroTxWallet reserveTx; - @Setter - @Getter - private String reserveTxHash; - @Setter - @Getter - private List frozenKeyImages = new ArrayList<>(); @Getter @Setter transient private MoneroTxWallet depositTxXmr; @@ -247,20 +237,18 @@ public class ProcessModel implements Model, PersistablePayload { .setFundsNeededForTradeAsLong(fundsNeededForTradeAsLong) .setPaymentStartedMessageState(paymentStartedMessageStateProperty.get().name()) .setBuyerPayoutAmountFromMediation(buyerPayoutAmountFromMediation) - .setSellerPayoutAmountFromMediation(sellerPayoutAmountFromMediation) - .addAllFrozenKeyImages(frozenKeyImages); + .setSellerPayoutAmountFromMediation(sellerPayoutAmountFromMediation); Optional.ofNullable(maker).ifPresent(e -> builder.setMaker((protobuf.TradingPeer) maker.toProtoMessage())); Optional.ofNullable(taker).ifPresent(e -> builder.setTaker((protobuf.TradingPeer) taker.toProtoMessage())); Optional.ofNullable(arbitrator).ifPresent(e -> builder.setArbitrator((protobuf.TradingPeer) arbitrator.toProtoMessage())); Optional.ofNullable(takeOfferFeeTxId).ifPresent(builder::setTakeOfferFeeTxId); - Optional.ofNullable(reserveTxHash).ifPresent(e -> builder.setReserveTxHash(reserveTxHash)); Optional.ofNullable(payoutTxSignature).ifPresent(e -> builder.setPayoutTxSignature(ByteString.copyFrom(payoutTxSignature))); Optional.ofNullable(rawTransactionInputs).ifPresent(e -> builder.addAllRawTransactionInputs(ProtoUtil.collectionToProto(rawTransactionInputs, protobuf.RawTransactionInput.class))); Optional.ofNullable(changeOutputAddress).ifPresent(builder::setChangeOutputAddress); 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(arbitratorNodeAddress).ifPresent(e -> builder.setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage())); + Optional.ofNullable(backupArbitrator).ifPresent(e -> builder.setBackupArbitrator(backupArbitrator.toProtoMessage())); Optional.ofNullable(preparedMultisigHex).ifPresent(e -> builder.setPreparedMultisigHex(preparedMultisigHex)); Optional.ofNullable(madeMultisigHex).ifPresent(e -> builder.setMadeMultisigHex(madeMultisigHex)); Optional.ofNullable(multisigSetupComplete).ifPresent(e -> builder.setMultisigSetupComplete(multisigSetupComplete)); @@ -282,8 +270,6 @@ public class ProcessModel implements Model, PersistablePayload { processModel.setSellerPayoutAmountFromMediation(proto.getSellerPayoutAmountFromMediation()); // nullable - processModel.setReserveTxHash(proto.getReserveTxHash()); - processModel.setFrozenKeyImages(proto.getFrozenKeyImagesList()); processModel.setTakeOfferFeeTxId(ProtoUtil.stringOrNullFromProto(proto.getTakeOfferFeeTxId())); processModel.setPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getPayoutTxSignature())); List rawTransactionInputs = proto.getRawTransactionInputsList().isEmpty() ? @@ -295,7 +281,7 @@ 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.setArbitratorNodeAddress(proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null); + processModel.setBackupArbitrator(proto.hasBackupArbitrator() ? NodeAddress.fromProto(proto.getBackupArbitrator()) : null); processModel.setPreparedMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getPreparedMultisigHex())); processModel.setMadeMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getMadeMultisigHex())); processModel.setMultisigSetupComplete(proto.getMultisigSetupComplete()); 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 d9fbc8f57e..4f2a622192 100644 --- a/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/SellerAsMakerProtocol.java @@ -38,6 +38,7 @@ import bisq.core.trade.protocol.tasks.ProcessSignContractResponse; import bisq.core.trade.protocol.tasks.SendSignContractRequestAfterMultisig; import bisq.core.trade.protocol.tasks.TradeTask; import bisq.core.trade.protocol.tasks.maker.MakerRemovesOpenOffer; +import bisq.core.trade.protocol.tasks.maker.MakerSendsInitTradeRequestIfUnreserved; import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment; import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx; import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest; @@ -144,7 +145,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, // TODO (woodser): implement this + MakerSendsInitTradeRequestIfUnreserved.class, MakerRemovesOpenOffer.class). using(new TradeTaskRunner(trade, () -> { 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 65d009b444..65cd88d9fc 100644 --- a/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java +++ b/core/src/main/java/bisq/core/trade/protocol/SellerAsTakerProtocol.java @@ -73,6 +73,7 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc super(trade); Offer offer = checkNotNull(trade.getOffer()); trade.getTradingPeer().setPubKeyRing(offer.getPubKeyRing()); + trade.setMakerPubKeyRing(offer.getPubKeyRing()); } diff --git a/core/src/main/java/bisq/core/trade/protocol/TradingPeer.java b/core/src/main/java/bisq/core/trade/protocol/TradingPeer.java index a470b163c0..3714d237cb 100644 --- a/core/src/main/java/bisq/core/trade/protocol/TradingPeer.java +++ b/core/src/main/java/bisq/core/trade/protocol/TradingPeer.java @@ -27,7 +27,7 @@ import bisq.common.proto.persistable.PersistablePayload; import com.google.protobuf.ByteString; import com.google.protobuf.Message; - +import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @@ -102,6 +102,8 @@ public final class TradingPeer implements PersistablePayload { @Nullable private String reserveTxKey; @Nullable + private List reserveTxKeyImages = new ArrayList<>(); + @Nullable private String preparedMultisigHex; @Nullable private String madeMultisigHex; @@ -120,7 +122,8 @@ public final class TradingPeer implements PersistablePayload { @Override public Message toProtoMessage() { final protobuf.TradingPeer.Builder builder = protobuf.TradingPeer.newBuilder() - .setChangeOutputValue(changeOutputValue); + .setChangeOutputValue(changeOutputValue) + .addAllReserveTxKeyImages(reserveTxKeyImages); Optional.ofNullable(accountId).ifPresent(builder::setAccountId); Optional.ofNullable(paymentAccountId).ifPresent(builder::setPaymentAccountId); Optional.ofNullable(paymentMethodId).ifPresent(builder::setPaymentMethodId); @@ -183,6 +186,7 @@ public final class TradingPeer implements PersistablePayload { tradingPeer.setReserveTxHash(ProtoUtil.stringOrNullFromProto(proto.getReserveTxHash())); tradingPeer.setReserveTxHex(ProtoUtil.stringOrNullFromProto(proto.getReserveTxHex())); tradingPeer.setReserveTxKey(ProtoUtil.stringOrNullFromProto(proto.getReserveTxKey())); + tradingPeer.setReserveTxKeyImages(proto.getReserveTxKeyImagesList()); tradingPeer.setPreparedMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getPreparedMultisigHex())); tradingPeer.setMadeMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getMadeMultisigHex())); tradingPeer.setSignedPayoutTxHex(ProtoUtil.stringOrNullFromProto(proto.getSignedPayoutTxHex())); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorSendsInitMultisigRequestsIfFundsReserved.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorSendsInitMultisigRequestsIfFundsReserved.java deleted file mode 100644 index 76b0c74d4a..0000000000 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorSendsInitMultisigRequestsIfFundsReserved.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * This file is part of Bisq. - * - * Bisq is free software: you can redistribute it and/or modify it - * under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or (at - * your option) any later version. - * - * Bisq is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public - * License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Bisq. If not, see . - */ - -package bisq.core.trade.protocol.tasks; - -import static com.google.common.base.Preconditions.checkNotNull; - -import bisq.common.app.Version; -import bisq.common.crypto.Sig; -import bisq.common.taskrunner.TaskRunner; -import bisq.core.payment.payload.PaymentAccountPayload; -import bisq.core.trade.Trade; -import bisq.core.trade.messages.InitMultisigRequest; -import bisq.core.trade.messages.InitTradeRequest; -import bisq.network.p2p.SendDirectMessageListener; -import com.google.common.base.Charsets; -import java.util.Date; -import java.util.UUID; -import lombok.extern.slf4j.Slf4j; -import monero.wallet.MoneroWallet; - -/** - * Arbitrator sends InitMultisigRequest to maker and taker if both reserve txs received. - */ -@Slf4j -public class ArbitratorSendsInitMultisigRequestsIfFundsReserved extends TradeTask { - - private boolean takerAck; - private boolean makerAck; - - @SuppressWarnings({"unused"}) - public ArbitratorSendsInitMultisigRequestsIfFundsReserved(TaskRunner taskHandler, Trade trade) { - super(taskHandler, trade); - } - - @Override - protected void run() { - try { - runInterceptHook(); - - // skip if arbitrator does not have maker reserve tx - if (processModel.getMaker().getReserveTxHash() == null) { - log.info("Arbitrator does not have maker reserve tx for offerId {}, waiting to receive before initializing multisig wallet", processModel.getOffer().getId()); - complete(); - return; - } - - // create wallet for multisig - MoneroWallet multisigWallet = processModel.getXmrWalletService().createMultisigWallet(trade.getId()); - - // prepare multisig - String preparedHex = multisigWallet.prepareMultisig(); - processModel.setPreparedMultisigHex(preparedHex); - - // create message to initialize multisig - InitMultisigRequest request = new InitMultisigRequest( - processModel.getOffer().getId(), - processModel.getMyNodeAddress(), - processModel.getPubKeyRing(), - UUID.randomUUID().toString(), - Version.getP2PMessageVersion(), - new Date().getTime(), - preparedHex, - null); - - // send request to maker - log.info("Send {} with offerId {} and uid {} to maker {}", request.getClass().getSimpleName(), request.getTradeId(), request.getUid(), trade.getMakerNodeAddress()); - processModel.getP2PService().sendEncryptedDirectMessage( - trade.getMakerNodeAddress(), - trade.getMakerPubKeyRing(), - request, - new SendDirectMessageListener() { - @Override - public void onArrived() { - log.info("{} arrived at arbitrator: offerId={}; uid={}", request.getClass().getSimpleName(), request.getTradeId(), request.getUid()); - makerAck = true; - checkComplete(); - } - @Override - public void onFault(String errorMessage) { - log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), request.getUid(), trade.getMakerNodeAddress(), errorMessage); - appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage); - failed(); - } - } - ); - - // send request to taker - log.info("Send {} with offerId {} and uid {} to taker {}", request.getClass().getSimpleName(), request.getTradeId(), request.getUid(), trade.getTakerNodeAddress()); - processModel.getP2PService().sendEncryptedDirectMessage( - trade.getTakerNodeAddress(), - trade.getTakerPubKeyRing(), - request, - new SendDirectMessageListener() { - @Override - public void onArrived() { - log.info("{} arrived at peer: offerId={}; uid={}", request.getClass().getSimpleName(), request.getTradeId(), request.getUid()); - takerAck = true; - checkComplete(); - } - @Override - public void onFault(String errorMessage) { - log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), request.getUid(), trade.getTakerNodeAddress(), errorMessage); - appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage); - failed(); - } - } - ); - } catch (Throwable t) { - failed(t); - } - } - - private void checkComplete() { - if (makerAck && takerAck) complete(); - } -} diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorSendsInitTradeRequestToMakerIfFromTaker.java b/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorSendsInitTradeAndMultisigRequests.java similarity index 53% rename from core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorSendsInitTradeRequestToMakerIfFromTaker.java rename to core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorSendsInitTradeAndMultisigRequests.java index 2703dec66e..756392c589 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorSendsInitTradeRequestToMakerIfFromTaker.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/ArbitratorSendsInitTradeAndMultisigRequests.java @@ -22,24 +22,33 @@ import bisq.common.app.Version; import bisq.common.crypto.Sig; import bisq.common.taskrunner.TaskRunner; import bisq.core.trade.Trade; +import bisq.core.trade.messages.InitMultisigRequest; 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; import java.util.UUID; import lombok.extern.slf4j.Slf4j; +import monero.wallet.MoneroWallet; /** * Arbitrator sends InitTradeRequest to maker after receiving InitTradeRequest * from taker and verifying taker reserve tx. + * + * Arbitrator sends InitMultisigRequests after the maker acks. */ @Slf4j -public class ArbitratorSendsInitTradeRequestToMakerIfFromTaker extends TradeTask { +public class ArbitratorSendsInitTradeAndMultisigRequests extends TradeTask { + + private boolean takerAck; + private boolean makerAck; + @SuppressWarnings({"unused"}) - public ArbitratorSendsInitTradeRequestToMakerIfFromTaker(TaskRunner taskHandler, Trade trade) { + public ArbitratorSendsInitTradeAndMultisigRequests(TaskRunner taskHandler, Trade trade) { super(taskHandler, trade); } @@ -48,12 +57,15 @@ public class ArbitratorSendsInitTradeRequestToMakerIfFromTaker extends TradeTask try { runInterceptHook(); - // collect fields for request - String offerId = processModel.getOffer().getId(); + // 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(), offerId.getBytes(Charsets.UTF_8)); + 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 @@ -63,7 +75,7 @@ public class ArbitratorSendsInitTradeRequestToMakerIfFromTaker extends TradeTask // create request to initialize trade with maker InitTradeRequest makerRequest = new InitTradeRequest( - offerId, + processModel.getOfferId(), request.getSenderNodeAddress(), request.getPubKeyRing(), trade.getTradeAmount().value, @@ -91,7 +103,7 @@ public class ArbitratorSendsInitTradeRequestToMakerIfFromTaker extends TradeTask public void onAckMessage(AckMessage ackMessage, NodeAddress sender) { if (sender.equals(trade.getMakerNodeAddress()) && ackMessage.getSourceMsgClassName().equals(InitTradeRequest.class.getSimpleName())) { trade.removeListener(this); - if (ackMessage.isSuccess()) complete(); + if (ackMessage.isSuccess()) sendInitMultisigRequests(); else failed("Received unsuccessful ack for InitTradeRequest from maker"); // TODO (woodser): maker should not do this, penalize them by broadcasting reserve tx? } } @@ -122,4 +134,80 @@ public class ArbitratorSendsInitTradeRequestToMakerIfFromTaker extends TradeTask failed(t); } } + + private void sendInitMultisigRequests() { + + // 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; + } + + // create wallet for multisig + MoneroWallet multisigWallet = processModel.getXmrWalletService().createMultisigWallet(trade.getId()); + + // prepare multisig + String preparedHex = multisigWallet.prepareMultisig(); + processModel.setPreparedMultisigHex(preparedHex); + + // create message to initialize multisig + InitMultisigRequest initMultisigRequest = new InitMultisigRequest( + processModel.getOffer().getId(), + processModel.getMyNodeAddress(), + processModel.getPubKeyRing(), + UUID.randomUUID().toString(), + Version.getP2PMessageVersion(), + new Date().getTime(), + preparedHex, + null); + + // send request to maker + log.info("Send {} with offerId {} and uid {} to maker {}", initMultisigRequest.getClass().getSimpleName(), initMultisigRequest.getTradeId(), initMultisigRequest.getUid(), trade.getMakerNodeAddress()); + processModel.getP2PService().sendEncryptedDirectMessage( + trade.getMakerNodeAddress(), + trade.getMakerPubKeyRing(), + initMultisigRequest, + new SendDirectMessageListener() { + @Override + public void onArrived() { + log.info("{} arrived at arbitrator: offerId={}; uid={}", initMultisigRequest.getClass().getSimpleName(), initMultisigRequest.getTradeId(), initMultisigRequest.getUid()); + makerAck = true; + checkComplete(); + } + @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(); + } + } + ); + + // send request to taker + log.info("Send {} with offerId {} and uid {} to taker {}", initMultisigRequest.getClass().getSimpleName(), initMultisigRequest.getTradeId(), initMultisigRequest.getUid(), trade.getTakerNodeAddress()); + processModel.getP2PService().sendEncryptedDirectMessage( + trade.getTakerNodeAddress(), + trade.getTakerPubKeyRing(), + initMultisigRequest, + new SendDirectMessageListener() { + @Override + public void onArrived() { + log.info("{} arrived at peer: offerId={}; uid={}", initMultisigRequest.getClass().getSimpleName(), initMultisigRequest.getTradeId(), initMultisigRequest.getUid()); + takerAck = true; + checkComplete(); + } + @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(); + } + } + ); + } + + private void checkComplete() { + if (makerAck && takerAck) complete(); + } } diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/SendSignContractRequestAfterMultisig.java b/core/src/main/java/bisq/core/trade/protocol/tasks/SendSignContractRequestAfterMultisig.java index 59d791d1ba..9b80fe3a9d 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/SendSignContractRequestAfterMultisig.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/SendSignContractRequestAfterMultisig.java @@ -23,7 +23,6 @@ import bisq.core.btc.model.XmrAddressEntry; import bisq.core.offer.Offer; import bisq.core.trade.MakerTrade; import bisq.core.trade.SellerTrade; -import bisq.core.trade.TakerTrade; import bisq.core.trade.Trade; import bisq.core.trade.TradeUtils; import bisq.core.trade.messages.SignContractRequest; @@ -62,8 +61,8 @@ public class SendSignContractRequestAfterMultisig extends TradeTask { // thaw reserved outputs MoneroWallet wallet = trade.getXmrWalletService().getWallet(); - for (String frozenKeyImage : processModel.getFrozenKeyImages()) { - wallet.thawOutput(frozenKeyImage); + for (String reserveTxKeyImage : trade.getSelf().getReserveTxKeyImages()) { + wallet.thawOutput(reserveTxKeyImage); } // create deposit tx diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/UpdateMultisigWithTradingPeer.java b/core/src/main/java/bisq/core/trade/protocol/tasks/UpdateMultisigWithTradingPeer.java index fd18c53cb2..b93addaa1d 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/UpdateMultisigWithTradingPeer.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/UpdateMultisigWithTradingPeer.java @@ -101,8 +101,7 @@ public class UpdateMultisigWithTradingPeer extends TradeTask { new Date().getTime(), updatedMultisigHex); - System.out.println("SENDING MESSAGE!!!!!!!"); - System.out.println(message); + System.out.println("Sending message: " + message); // TODO (woodser): trade.getTradingPeerNodeAddress() and/or trade.getTradingPeerPubKeyRing() are null on restart of application, so cannot send payment to complete trade log.info("Send {} with offerId {} and uid {} to peer {}", message.getClass().getSimpleName(), message.getTradeId(), message.getUid(), trade.getTradingPeerNodeAddress()); diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendsInitTradeRequestIfUnreserved.java b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendsInitTradeRequestIfUnreserved.java index 7825dc67af..644f5c9c0f 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendsInitTradeRequestIfUnreserved.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/maker/MakerSendsInitTradeRequestIfUnreserved.java @@ -18,14 +18,11 @@ package bisq.core.trade.protocol.tasks.maker; import bisq.core.btc.model.XmrAddressEntry; -import bisq.core.btc.wallet.XmrWalletService; -import bisq.core.payment.payload.PaymentAccountPayload; +import bisq.core.offer.Offer; import bisq.core.trade.Trade; import bisq.core.trade.messages.InitTradeRequest; import bisq.core.trade.protocol.tasks.TradeTask; -import bisq.core.user.User; -import bisq.network.p2p.NodeAddress; import bisq.network.p2p.SendDirectMessageListener; import bisq.common.app.Version; @@ -34,8 +31,6 @@ import bisq.common.taskrunner.TaskRunner; import com.google.common.base.Charsets; -import java.util.Date; -import java.util.List; import java.util.UUID; import lombok.extern.slf4j.Slf4j; @@ -55,88 +50,63 @@ public class MakerSendsInitTradeRequestIfUnreserved extends TradeTask { try { runInterceptHook(); - System.out.println("MAKER SENDING INIT TRADE REQ TO ARBITRATOR"); + // 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 request = (InitTradeRequest) processModel.getTradeMessage(); - checkNotNull(request); - checkTradeId(processModel.getOfferId(), request); - - // collect fields to send taker prepared multisig response // TODO (woodser): this should happen on response from arbitrator - XmrWalletService walletService = processModel.getProvider().getXmrWalletService(); - String offerId = processModel.getOffer().getId(); - String payoutAddress = walletService.getWallet().createSubaddress(0).getAddress(); // TODO (woodser): register TRADE_PAYOUT? - walletService.getWallet().save(); - checkNotNull(trade.getTradeAmount(), "TradeAmount must not be null"); -// checkNotNull(trade.getTakerFeeTxId(), "TakeOfferFeeTxId must not be null"); // TODO (woodser): no taker fee tx yet if creating multisig first - final User user = processModel.getUser(); - checkNotNull(user, "User must not be null"); - final List acceptedMediatorAddresses = user.getAcceptedMediatorAddresses(); - checkNotNull(acceptedMediatorAddresses, "acceptedMediatorAddresses must not be null"); + 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 - final PaymentAccountPayload paymentAccountPayload = checkNotNull(processModel.getPaymentAccountPayload(trade), "processModel.getPaymentAccountPayload(trade) must not be null"); - byte[] sig = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), offerId.getBytes(Charsets.UTF_8)); - - System.out.println("MAKER SENDING ARBITRTATOR SENDER NODE ADDRESS"); - System.out.println(processModel.getMyNodeAddress()); + // maker signs offer id as nonce to avoid challenge protocol // TODO (woodser): is this necessary? + byte[] sig = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), offer.getId().getBytes(Charsets.UTF_8)); - if (true) throw new RuntimeException("Not yet implemented"); - - // create message to initialize trade - InitTradeRequest message = new InitTradeRequest( - offerId, + // create request to arbitrator + InitTradeRequest arbitratorRequest = new InitTradeRequest( + offer.getId(), processModel.getMyNodeAddress(), processModel.getPubKeyRing(), - trade.getTradeAmount().value, - trade.getTradePrice().getValue(), - trade.getTakerFee().getValue(), - processModel.getAccountId(), - paymentAccountPayload.getId(), - paymentAccountPayload.getPaymentMethodId(), + offer.getAmount().value, + offer.getPrice().getValue(), + offer.getMakerFee().value, + trade.getProcessModel().getAccountId(), + offer.getMakerPaymentAccountId(), + offer.getOfferPayload().getPaymentMethodId(), UUID.randomUUID().toString(), Version.getP2PMessageVersion(), sig, - new Date().getTime(), + makerRequest.getCurrentDate(), trade.getMakerNodeAddress(), trade.getTakerNodeAddress(), trade.getArbitratorNodeAddress(), - processModel.getReserveTx().getHash(), // TODO (woodser): need to first create and save reserve tx - processModel.getReserveTx().getFullHex(), - processModel.getReserveTx().getKey(), - processModel.getXmrWalletService().getAddressEntry(offerId, XmrAddressEntry.Context.TRADE_PAYOUT).get().getAddressString(), + trade.getSelf().getReserveTxHash(), + trade.getSelf().getReserveTxHex(), + trade.getSelf().getReserveTxKey(), + model.getXmrWalletService().getOrCreateAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString(), null); - - log.info("Send {} with offerId {} and uid {} to peer {}", - message.getClass().getSimpleName(), message.getTradeId(), - message.getUid(), trade.getArbitratorNodeAddress()); - - - System.out.println("MAKER TRADE INFO"); - System.out.println("Trading peer node address: " + trade.getTradingPeerNodeAddress()); - System.out.println("Maker node address: " + trade.getMakerNodeAddress()); - System.out.println("Taker node adddress: " + trade.getTakerNodeAddress()); - System.out.println("Arbitrator node address: " + trade.getArbitratorNodeAddress()); - + // 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( trade.getArbitratorNodeAddress(), trade.getArbitratorPubKeyRing(), - message, + arbitratorRequest, new SendDirectMessageListener() { @Override public void onArrived() { - log.info("{} arrived at arbitrator: offerId={}; uid={}", message.getClass().getSimpleName(), message.getTradeId(), message.getUid()); + log.info("{} arrived at arbitrator: offerId={}", InitTradeRequest.class.getSimpleName(), trade.getId()); complete(); } @Override public void onFault(String errorMessage) { - log.error("Sending {} failed: uid={}; peer={}; error={}", message.getClass().getSimpleName(), message.getUid(), trade.getTradingPeerNodeAddress(), errorMessage); - appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage); + log.warn("Failed to send {} to arbitrator, error={}.", InitTradeRequest.class.getSimpleName(), errorMessage); failed(); } - } - ); + }); } catch (Throwable t) { failed(t); } diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerReservesTradeFunds.java b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerReservesTradeFunds.java index 8388694dfa..0e37f67171 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerReservesTradeFunds.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerReservesTradeFunds.java @@ -49,19 +49,18 @@ public class TakerReservesTradeFunds extends TradeTask { // freeze trade funds // TODO (woodser): synchronize to handle potential race condition where concurrent trades freeze each other's outputs - List frozenKeyImages = new ArrayList(); + List reserveTxKeyImages = new ArrayList(); MoneroWallet wallet = model.getXmrWalletService().getWallet(); for (MoneroOutput input : reserveTx.getInputs()) { - frozenKeyImages.add(input.getKeyImage().getHex()); + reserveTxKeyImages.add(input.getKeyImage().getHex()); wallet.freezeOutput(input.getKeyImage().getHex()); } // save process state // TODO (woodser): persist processModel.setReserveTx(reserveTx); - processModel.setReserveTxHash(reserveTx.getHash()); - processModel.setFrozenKeyImages(frozenKeyImages); - trade.setTakerFeeTxId(reserveTx.getHash()); + processModel.getTaker().setReserveTxKeyImages(reserveTxKeyImages); + trade.setTakerFeeTxId(reserveTx.getHash()); // TODO (woodser): this should be multisig deposit tx id? how is it used? //trade.setState(Trade.State.TAKER_PUBLISHED_TAKER_FEE_TX); // TODO (woodser): fee tx is not broadcast separate, update states complete(); } catch (Throwable t) { diff --git a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendsInitTradeRequestToArbitrator.java b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendsInitTradeRequestToArbitrator.java index 8519a82235..8c2164663d 100644 --- a/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendsInitTradeRequestToArbitrator.java +++ b/core/src/main/java/bisq/core/trade/protocol/tasks/taker/TakerSendsInitTradeRequestToArbitrator.java @@ -21,7 +21,7 @@ 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.network.p2p.NodeAddress; import bisq.network.p2p.SendDirectMessageListener; import bisq.common.app.Version; @@ -42,62 +42,85 @@ public class TakerSendsInitTradeRequestToArbitrator extends TradeTask { try { runInterceptHook(); - // get primary arbitrator - Mediator arbitrator = processModel.getUser().getAcceptedMediatorByAddress(trade.getArbitratorNodeAddress()); - if (arbitrator == null) throw new RuntimeException("Cannot get arbitrator instance from node address"); // TODO (woodser): null if arbitrator goes offline or never seen? - - // save pub keys - processModel.getArbitrator().setPubKeyRing(arbitrator.getPubKeyRing()); - trade.setArbitratorPubKeyRing(processModel.getArbitrator().getPubKeyRing()); - trade.setMakerPubKeyRing(trade.getTradingPeer().getPubKeyRing()); - - // send trade request to arbitrator - InitTradeRequest makerRequest = (InitTradeRequest) processModel.getTradeMessage(); - InitTradeRequest arbitratorRequest = new InitTradeRequest( - makerRequest.getTradeId(), - makerRequest.getSenderNodeAddress(), - makerRequest.getPubKeyRing(), - makerRequest.getTradeAmount(), - makerRequest.getTradePrice(), - makerRequest.getTradeFee(), - makerRequest.getAccountId(), - makerRequest.getPaymentAccountId(), - makerRequest.getPaymentMethodId(), - makerRequest.getUid(), - Version.getP2PMessageVersion(), - makerRequest.getAccountAgeWitnessSignatureOfOfferId(), - makerRequest.getCurrentDate(), - makerRequest.getMakerNodeAddress(), - makerRequest.getTakerNodeAddress(), - trade.getArbitratorNodeAddress(), - processModel.getReserveTx().getHash(), - processModel.getReserveTx().getFullHex(), - processModel.getReserveTx().getKey(), - makerRequest.getPayoutAddress(), - processModel.getMakerSignature()); - - // send request to arbitrator - System.out.println("SENDING INIT TRADE REQUEST TO ARBITRATOR!"); - log.info("Send {} with offerId {} and uid {} to arbitrator {} with pub key ring", arbitratorRequest.getClass().getSimpleName(), arbitratorRequest.getTradeId(), arbitratorRequest.getUid(), trade.getArbitratorNodeAddress(), trade.getArbitratorPubKeyRing()); - processModel.getP2PService().sendEncryptedDirectMessage( - trade.getArbitratorNodeAddress(), - trade.getArbitratorPubKeyRing(), - arbitratorRequest, - new SendDirectMessageListener() { + // send request to offer signer + sendInitTradeRequest(trade.getOffer().getOfferPayload().getArbitratorSigner(), new SendDirectMessageListener() { + @Override + public void onArrived() { + log.info("{} arrived at arbitrator: offerId={}", InitTradeRequest.class.getSimpleName(), trade.getId()); + complete(); + } + + // 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 arbitrator: offerId={}; uid={}", arbitratorRequest.getClass().getSimpleName(), arbitratorRequest.getTradeId(), arbitratorRequest.getUid()); + log.info("{} arrived at backup arbitrator: offerId={}", InitTradeRequest.class.getSimpleName(), trade.getId()); + complete(); } - @Override // TODO (woodser): handle case where primary arbitrator is unavailable so use backup arbitrator, distinguish offline from bad ack - public void onFault(String errorMessage) { - log.error("Sending {} failed: uid={}; peer={}; error={}", arbitratorRequest.getClass().getSimpleName(), arbitratorRequest.getUid(), trade.getArbitratorNodeAddress(), errorMessage); - appendToErrorMessage("Sending message failed: message=" + arbitratorRequest + "\nerrorMessage=" + errorMessage); + @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(); } - } - ); + }); + } + }); } catch (Throwable t) { failed(t); } } + + private void sendInitTradeRequest(NodeAddress arbitratorNodeAddress, SendDirectMessageListener listener) { + + // get registered arbitrator + Mediator arbitrator = processModel.getUser().getAcceptedMediatorByAddress(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( + makerRequest.getTradeId(), + makerRequest.getSenderNodeAddress(), + makerRequest.getPubKeyRing(), + makerRequest.getTradeAmount(), + makerRequest.getTradePrice(), + makerRequest.getTradeFee(), + makerRequest.getAccountId(), + makerRequest.getPaymentAccountId(), + makerRequest.getPaymentMethodId(), + makerRequest.getUid(), + Version.getP2PMessageVersion(), + makerRequest.getAccountAgeWitnessSignatureOfOfferId(), + makerRequest.getCurrentDate(), + makerRequest.getMakerNodeAddress(), + makerRequest.getTakerNodeAddress(), + trade.getArbitratorNodeAddress(), + processModel.getReserveTx().getHash(), + processModel.getReserveTx().getFullHex(), + processModel.getReserveTx().getKey(), + makerRequest.getPayoutAddress(), + processModel.getMakerSignature()); + + // 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( + arbitratorNodeAddress, + arbitrator.getPubKeyRing(), + arbitratorRequest, + listener + ); + } } diff --git a/core/src/test/java/bisq/core/trade/TradableListTest.java b/core/src/test/java/bisq/core/trade/TradableListTest.java index e6a41bca6a..ea4504c570 100644 --- a/core/src/test/java/bisq/core/trade/TradableListTest.java +++ b/core/src/test/java/bisq/core/trade/TradableListTest.java @@ -39,7 +39,7 @@ public class TradableListTest { // test adding an OpenOffer and convert toProto Offer offer = new Offer(offerPayload); - OpenOffer openOffer = new OpenOffer(offer); + OpenOffer openOffer = new OpenOffer(offer, 0, "", "", ""); openOfferTradableList.add(openOffer); message = (protobuf.PersistableEnvelope) openOfferTradableList.toProtoMessage(); assertEquals(message.getMessageCase(), TRADABLE_LIST); diff --git a/desktop/src/main/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModel.java b/desktop/src/main/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModel.java index e52f2b7bc4..79760ba4e2 100644 --- a/desktop/src/main/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModel.java +++ b/desktop/src/main/java/bisq/desktop/main/portfolio/editoffer/EditOfferDataModel.java @@ -219,7 +219,7 @@ class EditOfferDataModel extends MutableOfferDataModel { offerPayload.getHashOfChallenge(), offerPayload.getExtraDataMap(), offerPayload.getProtocolVersion(), - offerPayload.getArbitratorNodeAddress(), + offerPayload.getArbitratorSigner(), offerPayload.getArbitratorSignature(), offerPayload.getReserveTxKeyImages()); diff --git a/proto/src/main/proto/pb.proto b/proto/src/main/proto/pb.proto index 5fdc7b3bc8..563e3e0bc0 100644 --- a/proto/src/main/proto/pb.proto +++ b/proto/src/main/proto/pb.proto @@ -189,7 +189,7 @@ message OfferAvailabilityResponse { repeated int32 supported_capabilities = 3; string uid = 4; string maker_signature = 5; - NodeAddress arbitrator_node_address = 6; + NodeAddress backup_arbitrator = 6; } message RefreshOfferMessage { @@ -853,7 +853,7 @@ message OfferPayload { map extra_data = 34; int32 protocol_version = 35; - NodeAddress arbitrator_node_address = 1001; + NodeAddress arbitrator_signer = 1001; string arbitrator_signature = 1002; repeated string reserve_tx_key_images = 1003; } @@ -1472,9 +1472,11 @@ message OpenOffer { Offer offer = 1; State state = 2; - NodeAddress arbitrator_node_address = 3; + NodeAddress backup_arbitrator = 3; int64 trigger_price = 4; - repeated string frozen_key_images = 5; + string reserve_tx_hash = 5; + string reserve_tx_hex = 6; + string reserve_tx_key = 7; } message Tradable { @@ -1644,18 +1646,16 @@ message ProcessModel { int64 seller_payout_amount_from_mediation = 20; string maker_signature = 1001; - NodeAddress arbitrator_node_address = 1002; + NodeAddress backup_arbitrator = 1002; TradingPeer maker = 1003; TradingPeer taker = 1004; TradingPeer arbitrator = 1005; NodeAddress temp_trading_peer_node_address = 1006; - string reserve_tx_hash = 1007; - repeated string frozen_key_images = 1008; - string prepared_multisig_hex = 1009; - string made_multisig_hex = 1010; - bool multisig_setup_complete = 1011; - bool maker_ready_to_fund_multisig = 1012; - bool multisig_deposit_initiated = 1013; + string prepared_multisig_hex = 1007; + string made_multisig_hex = 1008; + bool multisig_setup_complete = 1009; + bool maker_ready_to_fund_multisig = 1010; + bool multisig_deposit_initiated = 1011; } message TradingPeer { @@ -1681,12 +1681,13 @@ message TradingPeer { string reserve_tx_hash = 1001; string reserve_tx_hex = 1002; string reserve_tx_key = 1003; - string prepared_multisig_hex = 1004; - string made_multisig_hex = 1005; - string signed_payout_tx_hex = 1006; - string deposit_tx_hash = 1007; - string deposit_tx_hex = 1008; - string deposit_tx_key = 1009; + repeated string reserve_tx_key_images = 1004; + string prepared_multisig_hex = 1005; + string made_multisig_hex = 1006; + string signed_payout_tx_hex = 1007; + string deposit_tx_hash = 1008; + string deposit_tx_hex = 1009; + string deposit_tx_key = 1010; } ///////////////////////////////////////////////////////////////////////////////////////////