use backup arbitrator if signing arbitrator not available
This commit is contained in:
parent
7d21bdf9f3
commit
4dafd57026
11
Makefile
11
Makefile
@ -54,6 +54,17 @@ arbitrator-desktop:
|
||||
--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 \
|
||||
--baseCurrencyNetwork=XMR_STAGENET \
|
||||
|
@ -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);
|
||||
|
@ -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<String>(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}";
|
||||
}
|
||||
|
@ -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<String> 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<String> 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}";
|
||||
}
|
||||
|
@ -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<String> frozenKeyImages = new ArrayList<String>();
|
||||
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<OpenOffer> 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);
|
||||
@ -934,6 +931,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
|
||||
ArrayList<OpenOffer> openOffersClone = new ArrayList<>(openOffers.getList());
|
||||
@ -1019,7 +1018,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||
originalOfferPayload.getHashOfChallenge(),
|
||||
updatedExtraDataMap,
|
||||
protocolVersion,
|
||||
originalOfferPayload.getArbitratorNodeAddress(),
|
||||
originalOfferPayload.getArbitratorSigner(),
|
||||
originalOfferPayload.getArbitratorSignature(),
|
||||
originalOfferPayload.getReserveTxKeyImages());
|
||||
|
||||
|
@ -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;
|
||||
@ -47,11 +47,21 @@ public class DisputeAgentSelection {
|
||||
public static <T extends DisputeAgent> T getLeastUsedArbitrator(TradeStatisticsManager tradeStatisticsManager,
|
||||
DisputeAgentManager<T> disputeAgentManager) {
|
||||
return getLeastUsedDisputeAgent(tradeStatisticsManager,
|
||||
disputeAgentManager);
|
||||
disputeAgentManager,
|
||||
null);
|
||||
}
|
||||
|
||||
public static <T extends DisputeAgent> T getLeastUsedArbitrator(TradeStatisticsManager tradeStatisticsManager,
|
||||
DisputeAgentManager<T> disputeAgentManager,
|
||||
NodeAddress excludedArbitrator) {
|
||||
return getLeastUsedDisputeAgent(tradeStatisticsManager,
|
||||
disputeAgentManager,
|
||||
excludedArbitrator);
|
||||
}
|
||||
|
||||
private static <T extends DisputeAgent> T getLeastUsedDisputeAgent(TradeStatisticsManager tradeStatisticsManager,
|
||||
DisputeAgentManager<T> disputeAgentManager) {
|
||||
DisputeAgentManager<T> disputeAgentManager,
|
||||
NodeAddress excludedDisputeAgent) {
|
||||
// We take last 100 entries from trade statistics
|
||||
List<TradeStatistics3> 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<T> optionalDisputeAgent = disputeAgentManager.getObservableMap().values().stream()
|
||||
|
@ -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
|
||||
|
@ -53,7 +53,7 @@ public class ProcessOfferAvailabilityResponse extends Task<OfferAvailabilityMode
|
||||
return;
|
||||
}
|
||||
|
||||
// check maker signature for trade request
|
||||
// verify maker signature for trade request
|
||||
if (!TradeUtils.isMakerSignatureValid(model.getTradeRequest(), offerAvailabilityResponse.getMakerSignature(), offer.getPubKeyRing())) {
|
||||
offer.setState(Offer.State.NOT_AVAILABLE);
|
||||
failed("Take offer attempt failed because maker signature is invalid");
|
||||
@ -61,11 +61,9 @@ public class ProcessOfferAvailabilityResponse extends Task<OfferAvailabilityMode
|
||||
}
|
||||
|
||||
offer.setState(Offer.State.AVAILABLE);
|
||||
|
||||
model.setMakerSignature(offerAvailabilityResponse.getMakerSignature());
|
||||
model.setArbitratorNodeAddress(offerAvailabilityResponse.getArbitratorNodeAddress());
|
||||
model.setBackupArbitrator(offerAvailabilityResponse.getBackupArbitrator());
|
||||
checkNotNull(model.getMakerSignature());
|
||||
checkNotNull(model.getArbitratorNodeAddress());
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
|
@ -37,6 +37,7 @@ import bisq.common.taskrunner.TaskRunner;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
// TODO (woodser): rename to TakerSendOfferAvailabilityRequest and group with other taker tasks
|
||||
@Slf4j
|
||||
public class SendOfferAvailabilityRequest extends Task<OfferAvailabilityModel> {
|
||||
public SendOfferAvailabilityRequest(TaskRunner<OfferAvailabilityModel> taskHandler, OfferAvailabilityModel model) {
|
||||
@ -78,7 +79,7 @@ public class SendOfferAvailabilityRequest extends Task<OfferAvailabilityModel> {
|
||||
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,
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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<PlaceOfferModel> 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;
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ public class MakerProcessesSignOfferResponse extends Task<PlaceOfferModel> {
|
||||
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");
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ public class MakerReservesTradeFunds extends Task<PlaceOfferModel> {
|
||||
// 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) {
|
||||
|
@ -68,7 +68,7 @@ public class MakerSendsSignOfferRequest extends Task<PlaceOfferModel> {
|
||||
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() {
|
||||
|
@ -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<SignedOffer> signedOfferOptional = openOfferManager.getSignedOfferById(request.getTradeId());
|
||||
if (!signedOfferOptional.isPresent()) return;
|
||||
if (signedOfferOptional.isPresent()) {
|
||||
SignedOffer signedOffer = signedOfferOptional.get();
|
||||
trade.getMaker().setReserveTxHash(signedOffer.getReserveTxHash());
|
||||
}
|
||||
|
||||
initTradeAndProtocol(trade, getTradeProtocol(trade));
|
||||
tradableList.add(trade);
|
||||
}
|
||||
@ -464,19 +466,14 @@ 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());
|
||||
// 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())
|
||||
@ -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());
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
() -> {
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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<String> 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<RawTransactionInput> 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());
|
||||
|
@ -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,
|
||||
() -> {
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
|
||||
|
@ -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<String> 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()));
|
||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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());
|
||||
|
@ -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);
|
||||
InitTradeRequest makerRequest = (InitTradeRequest) processModel.getTradeMessage(); // arbitrator's InitTradeRequest to maker
|
||||
checkNotNull(makerRequest);
|
||||
checkTradeId(processModel.getOfferId(), makerRequest);
|
||||
|
||||
// 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<NodeAddress> acceptedMediatorAddresses = user.getAcceptedMediatorAddresses();
|
||||
checkNotNull(acceptedMediatorAddresses, "acceptedMediatorAddresses must not be null");
|
||||
// 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));
|
||||
|
||||
// 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());
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -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<String> frozenKeyImages = new ArrayList<String>();
|
||||
List<String> reserveTxKeyImages = new ArrayList<String>();
|
||||
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) {
|
||||
|
@ -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,17 +42,55 @@ 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?
|
||||
// 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();
|
||||
}
|
||||
|
||||
// save pub keys
|
||||
// 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());
|
||||
complete();
|
||||
}
|
||||
@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());
|
||||
trade.setMakerPubKeyRing(trade.getTradingPeer().getPubKeyRing());
|
||||
|
||||
// send trade request to arbitrator
|
||||
InitTradeRequest makerRequest = (InitTradeRequest) processModel.getTradeMessage();
|
||||
// create request to arbitrator
|
||||
InitTradeRequest makerRequest = (InitTradeRequest) processModel.getTradeMessage(); // taker's InitTradeRequest to maker
|
||||
InitTradeRequest arbitratorRequest = new InitTradeRequest(
|
||||
makerRequest.getTradeId(),
|
||||
makerRequest.getSenderNodeAddress(),
|
||||
@ -77,27 +115,12 @@ public class TakerSendsInitTradeRequestToArbitrator extends TradeTask {
|
||||
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());
|
||||
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(),
|
||||
arbitratorNodeAddress,
|
||||
arbitrator.getPubKeyRing(),
|
||||
arbitratorRequest,
|
||||
new SendDirectMessageListener() {
|
||||
@Override
|
||||
public void onArrived() {
|
||||
log.info("{} arrived at arbitrator: offerId={}; uid={}", arbitratorRequest.getClass().getSimpleName(), arbitratorRequest.getTradeId(), arbitratorRequest.getUid());
|
||||
}
|
||||
@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);
|
||||
failed();
|
||||
}
|
||||
}
|
||||
listener
|
||||
);
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -219,7 +219,7 @@ class EditOfferDataModel extends MutableOfferDataModel {
|
||||
offerPayload.getHashOfChallenge(),
|
||||
offerPayload.getExtraDataMap(),
|
||||
offerPayload.getProtocolVersion(),
|
||||
offerPayload.getArbitratorNodeAddress(),
|
||||
offerPayload.getArbitratorSigner(),
|
||||
offerPayload.getArbitratorSignature(),
|
||||
offerPayload.getReserveTxKeyImages());
|
||||
|
||||
|
@ -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<string, string> 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;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
Loading…
Reference in New Issue
Block a user