use backup arbitrator if signing arbitrator not available

This commit is contained in:
woodser 2021-11-15 17:20:28 -05:00
parent 7d21bdf9f3
commit 4dafd57026
32 changed files with 370 additions and 388 deletions

View File

@ -53,6 +53,17 @@ arbitrator-desktop:
--appName=haveno-XMR_STAGENET_arbitrator \ --appName=haveno-XMR_STAGENET_arbitrator \
--apiPassword=apitest \ --apiPassword=apitest \
--apiPort=9998 --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: alice-desktop:
./haveno-desktop \ ./haveno-desktop \

View File

@ -216,8 +216,8 @@ public class OfferFilter {
public boolean hasValidSignature(Offer offer) { public boolean hasValidSignature(Offer offer) {
// get arbitrator // get arbitrator
Mediator arbitrator = user.getAcceptedMediatorByAddress(offer.getOfferPayload().getArbitratorNodeAddress()); // TODO (woodser): does this return null if arbitrator goes offline? Mediator arbitrator = user.getAcceptedMediatorByAddress(offer.getOfferPayload().getArbitratorSigner());
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 if (arbitrator == null) return false; // invalid arbitrator
// validate arbitrator signature // validate arbitrator signature
return TradeUtils.isArbitratorSignatureValid(offer.getOfferPayload(), arbitrator); return TradeUtils.isArbitratorSignatureValid(offer.getOfferPayload(), arbitrator);

View File

@ -169,7 +169,7 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
// address and signature of signing arbitrator // address and signature of signing arbitrator
@Setter @Setter
private NodeAddress arbitratorNodeAddress; private NodeAddress arbitratorSigner;
@Setter @Setter
@Nullable @Nullable
private String arbitratorSignature; private String arbitratorSignature;
@ -255,7 +255,7 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
this.hashOfChallenge = hashOfChallenge; this.hashOfChallenge = hashOfChallenge;
this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap); this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap);
this.protocolVersion = protocolVersion; this.protocolVersion = protocolVersion;
this.arbitratorNodeAddress = arbitratorSigner; this.arbitratorSigner = arbitratorSigner;
this.arbitratorSignature = arbitratorSignature; this.arbitratorSignature = arbitratorSignature;
this.reserveTxKeyImages = reserveTxKeyImages; this.reserveTxKeyImages = reserveTxKeyImages;
} }
@ -295,7 +295,7 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
.setUpperClosePrice(upperClosePrice) .setUpperClosePrice(upperClosePrice)
.setIsPrivateOffer(isPrivateOffer) .setIsPrivateOffer(isPrivateOffer)
.setProtocolVersion(protocolVersion) .setProtocolVersion(protocolVersion)
.setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage()); .setArbitratorSigner(arbitratorSigner.toProtoMessage());
builder.setOfferFeePaymentTxId(checkNotNull(offerFeePaymentTxId, builder.setOfferFeePaymentTxId(checkNotNull(offerFeePaymentTxId,
"OfferPayload is in invalid state: offerFeePaymentTxID is not set when adding to P2P network.")); "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, hashOfChallenge,
extraDataMapMap, extraDataMapMap,
proto.getProtocolVersion(), proto.getProtocolVersion(),
NodeAddress.fromProto(proto.getArbitratorNodeAddress()), NodeAddress.fromProto(proto.getArbitratorSigner()),
ProtoUtil.stringOrNullFromProto(proto.getArbitratorSignature()), ProtoUtil.stringOrNullFromProto(proto.getArbitratorSignature()),
proto.getReserveTxKeyImagesList() == null ? null : new ArrayList<String>(proto.getReserveTxKeyImagesList())); proto.getReserveTxKeyImagesList() == null ? null : new ArrayList<String>(proto.getReserveTxKeyImagesList()));
} }
@ -424,7 +424,7 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
",\n hashOfChallenge='" + hashOfChallenge + '\'' + ",\n hashOfChallenge='" + hashOfChallenge + '\'' +
",\n extraDataMap=" + extraDataMap + ",\n extraDataMap=" + extraDataMap +
",\n protocolVersion=" + protocolVersion + ",\n protocolVersion=" + protocolVersion +
",\n arbitratorSigner=" + arbitratorNodeAddress + ",\n arbitratorSigner=" + arbitratorSigner +
",\n arbitratorSignature=" + arbitratorSignature + ",\n arbitratorSignature=" + arbitratorSignature +
"\n}"; "\n}";
} }

View File

@ -24,9 +24,7 @@ import bisq.network.p2p.NodeAddress;
import bisq.common.Timer; import bisq.common.Timer;
import bisq.common.UserThread; import bisq.common.UserThread;
import bisq.common.proto.ProtoUtil; import bisq.common.proto.ProtoUtil;
import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
@ -58,10 +56,17 @@ public final class OpenOffer implements Tradable {
@Getter @Getter
@Setter @Setter
@Nullable @Nullable
private NodeAddress arbitratorNodeAddress; private NodeAddress backupArbitrator;
@Setter @Setter
@Getter @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. // Added in v1.5.3.
// If market price reaches that trigger price the offer gets deactivated // If market price reaches that trigger price the offer gets deactivated
@ -81,11 +86,17 @@ public final class OpenOffer implements Tradable {
state = State.AVAILABLE; 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.offer = offer;
this.triggerPrice = triggerPrice; this.triggerPrice = triggerPrice;
state = State.AVAILABLE; 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, private OpenOffer(Offer offer,
State state, State state,
@Nullable NodeAddress arbitratorNodeAddress, @Nullable NodeAddress backupArbitrator,
long triggerPrice) { long triggerPrice,
String reserveTxHash,
String reserveTxHex,
String reserveTxKey) {
this.offer = offer; this.offer = offer;
this.state = state; this.state = state;
this.arbitratorNodeAddress = arbitratorNodeAddress; this.backupArbitrator = backupArbitrator;
this.triggerPrice = triggerPrice; this.triggerPrice = triggerPrice;
this.reserveTxHash = reserveTxHash;
this.reserveTxHex = reserveTxHex;
this.reserveTxKey = reserveTxKey;
if (this.state == State.RESERVED) if (this.state == State.RESERVED)
setState(State.AVAILABLE); setState(State.AVAILABLE);
@ -111,9 +128,11 @@ public final class OpenOffer implements Tradable {
.setOffer(offer.toProtoMessage()) .setOffer(offer.toProtoMessage())
.setTriggerPrice(triggerPrice) .setTriggerPrice(triggerPrice)
.setState(protobuf.OpenOffer.State.valueOf(state.name())) .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(); return protobuf.Tradable.newBuilder().setOpenOffer(builder).build();
} }
@ -121,9 +140,11 @@ public final class OpenOffer implements Tradable {
public static Tradable fromProto(protobuf.OpenOffer proto) { public static Tradable fromProto(protobuf.OpenOffer proto) {
OpenOffer openOffer = new OpenOffer(Offer.fromProto(proto.getOffer()), OpenOffer openOffer = new OpenOffer(Offer.fromProto(proto.getOffer()),
ProtoUtil.enumFromProto(OpenOffer.State.class, proto.getState().name()), ProtoUtil.enumFromProto(OpenOffer.State.class, proto.getState().name()),
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null, proto.hasBackupArbitrator() ? NodeAddress.fromProto(proto.getBackupArbitrator()) : null,
proto.getTriggerPrice()); proto.getTriggerPrice(),
openOffer.setFrozenKeyImages(proto.getFrozenKeyImagesList()); proto.getReserveTxHash(),
proto.getReserveTxHex(),
proto.getReserveTxKey());
return openOffer; return openOffer;
} }
@ -187,7 +208,7 @@ public final class OpenOffer implements Tradable {
return "OpenOffer{" + return "OpenOffer{" +
",\n offer=" + offer + ",\n offer=" + offer +
",\n state=" + state + ",\n state=" + state +
",\n arbitratorNodeAddress=" + arbitratorNodeAddress + ",\n arbitratorNodeAddress=" + backupArbitrator +
",\n triggerPrice=" + triggerPrice + ",\n triggerPrice=" + triggerPrice +
"\n}"; "\n}";
} }

View File

@ -91,7 +91,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import lombok.Getter; import lombok.Getter;
import monero.daemon.model.MoneroOutput;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -416,10 +415,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
model, model,
transaction -> { transaction -> {
// save frozen key images with open offer // save reserve tx with open offer
List<String> frozenKeyImages = new ArrayList<String>(); OpenOffer openOffer = new OpenOffer(offer, triggerPrice, model.getReserveTx().getHash(), model.getReserveTx().getFullHex(), model.getReserveTx().getKey());
for (MoneroOutput output : model.getReserveTx().getInputs()) frozenKeyImages.add(output.getKeyImage().getHex());
OpenOffer openOffer = new OpenOffer(offer, triggerPrice, frozenKeyImages);
openOffers.add(openOffer); openOffers.add(openOffer);
requestPersistence(); requestPersistence();
resultHandler.handleResult(transaction); resultHandler.handleResult(transaction);
@ -577,7 +574,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
closedTradableManager.add(openOffer); closedTradableManager.add(openOffer);
log.info("onRemoved offerId={}", offer.getId()); log.info("onRemoved offerId={}", offer.getId());
btcWalletService.resetAddressEntriesForOpenOffer(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(); requestPersistence();
resultHandler.handleResult(); resultHandler.handleResult();
} }
@ -642,7 +639,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
} }
// verify arbitrator is signer of offer payload // 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"; errorMessage = "Cannot sign offer because offer payload is for a different arbitrator";
log.info(errorMessage); log.info(errorMessage);
sendAckMessage(request.getClass(), peer, request.getPubKeyRing(), request.getOfferId(), request.getUid(), false, 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); Optional<OpenOffer> openOfferOptional = getOpenOfferById(request.offerId);
AvailabilityResult availabilityResult; AvailabilityResult availabilityResult;
String makerSignature = null; String makerSignature = null;
NodeAddress arbitratorNodeAddress = null; NodeAddress backupArbitratorNodeAddress = null;
if (openOfferOptional.isPresent()) { if (openOfferOptional.isPresent()) {
OpenOffer openOffer = openOfferOptional.get(); OpenOffer openOffer = openOfferOptional.get();
if (!apiUserDeniedByOffer(request)) { if (!apiUserDeniedByOffer(request)) {
@ -793,10 +790,10 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
Offer offer = openOffer.getOffer(); Offer offer = openOffer.getOffer();
if (preferences.getIgnoreTradersList().stream().noneMatch(fullAddress -> fullAddress.equals(peer.getFullAddress()))) { if (preferences.getIgnoreTradersList().stream().noneMatch(fullAddress -> fullAddress.equals(peer.getFullAddress()))) {
// use signing arbitrator if available, otherwise use least used arbitrator // set backup arbitrator if signer is not available
boolean isSignerOnline = true; Mediator backupMediator = DisputeAgentSelection.getLeastUsedArbitrator(tradeStatisticsManager, mediatorManager, offer.getOfferPayload().getArbitratorSigner());
arbitratorNodeAddress = isSignerOnline ? offer.getOfferPayload().getArbitratorNodeAddress() : DisputeAgentSelection.getLeastUsedArbitrator(tradeStatisticsManager, mediatorManager).getNodeAddress(); backupArbitratorNodeAddress = backupMediator == null ? null : backupMediator.getNodeAddress();
openOffer.setArbitratorNodeAddress(arbitratorNodeAddress); openOffer.setBackupArbitrator(backupArbitratorNodeAddress);
// maker signs taker's request // TODO (woodser): should maker signature include selected arbitrator? // maker signs taker's request // TODO (woodser): should maker signature include selected arbitrator?
String tradeRequestAsJson = Utilities.objectToJson(request.getTradeRequest()); String tradeRequestAsJson = Utilities.objectToJson(request.getTradeRequest());
@ -848,7 +845,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
OfferAvailabilityResponse offerAvailabilityResponse = new OfferAvailabilityResponse(request.offerId, OfferAvailabilityResponse offerAvailabilityResponse = new OfferAvailabilityResponse(request.offerId,
availabilityResult, availabilityResult,
makerSignature, makerSignature,
arbitratorNodeAddress); backupArbitratorNodeAddress);
log.info("Send {} with offerId {} and uid {} to peer {}", log.info("Send {} with offerId {} and uid {} to peer {}",
offerAvailabilityResponse.getClass().getSimpleName(), offerAvailabilityResponse.getOfferId(), offerAvailabilityResponse.getClass().getSimpleName(), offerAvailabilityResponse.getOfferId(),
offerAvailabilityResponse.getUid(), peer); 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 // 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() { private void maybeUpdatePersistedOffers() {
// We need to clone to avoid ConcurrentModificationException // We need to clone to avoid ConcurrentModificationException
@ -1019,7 +1018,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
originalOfferPayload.getHashOfChallenge(), originalOfferPayload.getHashOfChallenge(),
updatedExtraDataMap, updatedExtraDataMap,
protocolVersion, protocolVersion,
originalOfferPayload.getArbitratorNodeAddress(), originalOfferPayload.getArbitratorSigner(),
originalOfferPayload.getArbitratorSignature(), originalOfferPayload.getArbitratorSignature(),
originalOfferPayload.getReserveTxKeyImages()); originalOfferPayload.getReserveTxKeyImages());

View File

@ -21,7 +21,7 @@ import bisq.core.support.dispute.agent.DisputeAgent;
import bisq.core.support.dispute.agent.DisputeAgentManager; import bisq.core.support.dispute.agent.DisputeAgentManager;
import bisq.core.trade.statistics.TradeStatistics3; import bisq.core.trade.statistics.TradeStatistics3;
import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.network.p2p.NodeAddress;
import bisq.common.util.Tuple2; import bisq.common.util.Tuple2;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
@ -43,15 +43,25 @@ import static com.google.common.base.Preconditions.checkArgument;
@Slf4j @Slf4j
public class DisputeAgentSelection { public class DisputeAgentSelection {
public static final int LOOK_BACK_RANGE = 100; public static final int LOOK_BACK_RANGE = 100;
public static <T extends DisputeAgent> T getLeastUsedArbitrator(TradeStatisticsManager tradeStatisticsManager, public static <T extends DisputeAgent> T getLeastUsedArbitrator(TradeStatisticsManager tradeStatisticsManager,
DisputeAgentManager<T> disputeAgentManager) { DisputeAgentManager<T> disputeAgentManager) {
return getLeastUsedDisputeAgent(tradeStatisticsManager, 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, 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 // We take last 100 entries from trade statistics
List<TradeStatistics3> list = new ArrayList<>(tradeStatisticsManager.getObservableTradeStatisticsSet()); List<TradeStatistics3> list = new ArrayList<>(tradeStatisticsManager.getObservableTradeStatisticsSet());
list.sort(Comparator.comparing(TradeStatistics3::getDateAsLong)); list.sort(Comparator.comparing(TradeStatistics3::getDateAsLong));
@ -71,6 +81,9 @@ public class DisputeAgentSelection {
.map(disputeAgent -> disputeAgent.getNodeAddress().getFullAddress()) .map(disputeAgent -> disputeAgent.getNodeAddress().getFullAddress())
.collect(Collectors.toSet()); .collect(Collectors.toSet());
if (excludedDisputeAgent != null) disputeAgents.remove(excludedDisputeAgent.getFullAddress());
if (disputeAgents.isEmpty()) return null;
String result = getLeastUsedDisputeAgent(lastAddressesUsedInTrades, disputeAgents); String result = getLeastUsedDisputeAgent(lastAddressesUsedInTrades, disputeAgents);
Optional<T> optionalDisputeAgent = disputeAgentManager.getObservableMap().values().stream() Optional<T> optionalDisputeAgent = disputeAgentManager.getObservableMap().values().stream()

View File

@ -67,7 +67,7 @@ public class OfferAvailabilityModel implements Model {
private String makerSignature; private String makerSignature;
@Setter @Setter
@Getter @Getter
private NodeAddress arbitratorNodeAddress; private NodeAddress backupArbitrator;
// Added in v1.5.5 // Added in v1.5.5
@Getter @Getter

View File

@ -53,19 +53,17 @@ public class ProcessOfferAvailabilityResponse extends Task<OfferAvailabilityMode
return; return;
} }
// check maker signature for trade request // verify maker signature for trade request
if (!TradeUtils.isMakerSignatureValid(model.getTradeRequest(), offerAvailabilityResponse.getMakerSignature(), offer.getPubKeyRing())) { if (!TradeUtils.isMakerSignatureValid(model.getTradeRequest(), offerAvailabilityResponse.getMakerSignature(), offer.getPubKeyRing())) {
offer.setState(Offer.State.NOT_AVAILABLE); offer.setState(Offer.State.NOT_AVAILABLE);
failed("Take offer attempt failed because maker signature is invalid"); failed("Take offer attempt failed because maker signature is invalid");
return; return;
} }
offer.setState(Offer.State.AVAILABLE);
offer.setState(Offer.State.AVAILABLE);
model.setMakerSignature(offerAvailabilityResponse.getMakerSignature()); model.setMakerSignature(offerAvailabilityResponse.getMakerSignature());
model.setArbitratorNodeAddress(offerAvailabilityResponse.getArbitratorNodeAddress()); model.setBackupArbitrator(offerAvailabilityResponse.getBackupArbitrator());
checkNotNull(model.getMakerSignature()); checkNotNull(model.getMakerSignature());
checkNotNull(model.getArbitratorNodeAddress());
complete(); complete();
} catch (Throwable t) { } catch (Throwable t) {

View File

@ -37,6 +37,7 @@ import bisq.common.taskrunner.TaskRunner;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
// TODO (woodser): rename to TakerSendOfferAvailabilityRequest and group with other taker tasks
@Slf4j @Slf4j
public class SendOfferAvailabilityRequest extends Task<OfferAvailabilityModel> { public class SendOfferAvailabilityRequest extends Task<OfferAvailabilityModel> {
public SendOfferAvailabilityRequest(TaskRunner<OfferAvailabilityModel> taskHandler, OfferAvailabilityModel model) { public SendOfferAvailabilityRequest(TaskRunner<OfferAvailabilityModel> taskHandler, OfferAvailabilityModel model) {
@ -78,7 +79,7 @@ public class SendOfferAvailabilityRequest extends Task<OfferAvailabilityModel> {
new Date().getTime(), new Date().getTime(),
offer.getMakerNodeAddress(), offer.getMakerNodeAddress(),
P2PService.getMyNodeAddress(), 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, // reserve tx not sent from taker to maker
null, null,
null, null,

View File

@ -46,19 +46,19 @@ public final class OfferAvailabilityResponse extends OfferMessage implements Sup
@Nullable @Nullable
private final String makerSignature; private final String makerSignature;
private final NodeAddress arbitratorNodeAddress; private final NodeAddress backupArbitrator;
public OfferAvailabilityResponse(String offerId, public OfferAvailabilityResponse(String offerId,
AvailabilityResult availabilityResult, AvailabilityResult availabilityResult,
String makerSignature, String makerSignature,
NodeAddress arbitratorNodeAddress) { NodeAddress backupArbitrator) {
this(offerId, this(offerId,
availabilityResult, availabilityResult,
Capabilities.app, Capabilities.app,
Version.getP2PMessageVersion(), Version.getP2PMessageVersion(),
UUID.randomUUID().toString(), UUID.randomUUID().toString(),
makerSignature, makerSignature,
arbitratorNodeAddress); backupArbitrator);
} }
@ -77,19 +77,19 @@ public final class OfferAvailabilityResponse extends OfferMessage implements Sup
this.availabilityResult = availabilityResult; this.availabilityResult = availabilityResult;
this.supportedCapabilities = supportedCapabilities; this.supportedCapabilities = supportedCapabilities;
this.makerSignature = makerSignature; this.makerSignature = makerSignature;
this.arbitratorNodeAddress = arbitratorNodeAddress; this.backupArbitrator = arbitratorNodeAddress;
} }
@Override @Override
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
final protobuf.OfferAvailabilityResponse.Builder builder = protobuf.OfferAvailabilityResponse.newBuilder() final protobuf.OfferAvailabilityResponse.Builder builder = protobuf.OfferAvailabilityResponse.newBuilder()
.setOfferId(offerId) .setOfferId(offerId)
.setAvailabilityResult(protobuf.AvailabilityResult.valueOf(availabilityResult.name())) .setAvailabilityResult(protobuf.AvailabilityResult.valueOf(availabilityResult.name()));
.setArbitratorNodeAddress(arbitratorNodeAddress.toProtoMessage());
Optional.ofNullable(supportedCapabilities).ifPresent(e -> builder.addAllSupportedCapabilities(Capabilities.toIntList(supportedCapabilities))); Optional.ofNullable(supportedCapabilities).ifPresent(e -> builder.addAllSupportedCapabilities(Capabilities.toIntList(supportedCapabilities)));
Optional.ofNullable(uid).ifPresent(e -> builder.setUid(uid)); Optional.ofNullable(uid).ifPresent(e -> builder.setUid(uid));
Optional.ofNullable(makerSignature).ifPresent(e -> builder.setMakerSignature(makerSignature)); Optional.ofNullable(makerSignature).ifPresent(e -> builder.setMakerSignature(makerSignature));
Optional.ofNullable(backupArbitrator).ifPresent(nodeAddress -> builder.setBackupArbitrator(nodeAddress.toProtoMessage()));
return getNetworkEnvelopeBuilder() return getNetworkEnvelopeBuilder()
.setOfferAvailabilityResponse(builder) .setOfferAvailabilityResponse(builder)
@ -103,6 +103,6 @@ public final class OfferAvailabilityResponse extends OfferMessage implements Sup
messageVersion, messageVersion,
proto.getUid().isEmpty() ? null : proto.getUid(), proto.getUid().isEmpty() ? null : proto.getUid(),
proto.getMakerSignature().isEmpty() ? null : proto.getMakerSignature(), proto.getMakerSignature().isEmpty() ? null : proto.getMakerSignature(),
NodeAddress.fromProto(proto.getArbitratorNodeAddress())); proto.hasBackupArbitrator() ? NodeAddress.fromProto(proto.getBackupArbitrator()) : null);
} }
} }

View File

@ -56,6 +56,7 @@ public class PlaceOfferProtocol {
// Called from UI // Called from UI
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// TODO (woodser): this returns before offer is placed
public void placeOffer() { public void placeOffer() {
log.debug("placeOffer() " + model.getOffer().getId()); log.debug("placeOffer() " + model.getOffer().getId());
TaskRunner<PlaceOfferModel> taskRunner = new TaskRunner<>(model, TaskRunner<PlaceOfferModel> taskRunner = new TaskRunner<>(model,
@ -82,7 +83,7 @@ public class PlaceOfferProtocol {
log.debug("handleSignOfferResponse() " + model.getOffer().getId()); log.debug("handleSignOfferResponse() " + model.getOffer().getId());
model.setSignOfferResponse(response); 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"); log.warn("Ignoring sign offer response from different sender");
return; return;
} }

View File

@ -39,7 +39,7 @@ public class MakerProcessesSignOfferResponse extends Task<PlaceOfferModel> {
runInterceptHook(); runInterceptHook();
// validate arbitrator signature // 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)) { if (!TradeUtils.isArbitratorSignatureValid(model.getSignOfferResponse().getSignedOfferPayload(), arbitrator)) {
throw new RuntimeException("Offer payload has invalid arbitrator signature"); throw new RuntimeException("Offer payload has invalid arbitrator signature");
} }

View File

@ -64,7 +64,7 @@ public class MakerReservesTradeFunds extends Task<PlaceOfferModel> {
// TODO (woodser): persist // TODO (woodser): persist
model.setReserveTx(reserveTx); model.setReserveTx(reserveTx);
offer.getOfferPayload().setReserveTxKeyImages(reservedKeyImages); 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); offer.setState(Offer.State.OFFER_FEE_RESERVED);
complete(); complete();
} catch (Throwable t) { } catch (Throwable t) {

View File

@ -68,7 +68,7 @@ public class MakerSendsSignOfferRequest extends Task<PlaceOfferModel> {
returnAddress); returnAddress);
// get signing arbitrator // 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 // send request
model.getP2PService().sendEncryptedDirectMessage(arbitrator.getNodeAddress(), arbitrator.getPubKeyRing(), request, new SendDirectMessageListener() { model.getP2PService().sendEncryptedDirectMessage(arbitrator.getNodeAddress(), arbitrator.getPubKeyRing(), request, new SendDirectMessageListener() {

View File

@ -433,11 +433,13 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
request.getTakerNodeAddress(), request.getTakerNodeAddress(),
request.getArbitratorNodeAddress()); request.getArbitratorNodeAddress());
// set reserve tx hash // set reserve tx hash if available
Optional<SignedOffer> signedOfferOptional = openOfferManager.getSignedOfferById(request.getTradeId()); Optional<SignedOffer> signedOfferOptional = openOfferManager.getSignedOfferById(request.getTradeId());
if (!signedOfferOptional.isPresent()) return; if (signedOfferOptional.isPresent()) {
SignedOffer signedOffer = signedOfferOptional.get(); SignedOffer signedOffer = signedOfferOptional.get();
trade.getMaker().setReserveTxHash(signedOffer.getReserveTxHash()); trade.getMaker().setReserveTxHash(signedOffer.getReserveTxHash());
}
initTradeAndProtocol(trade, getTradeProtocol(trade)); initTradeAndProtocol(trade, getTradeProtocol(trade));
tradableList.add(trade); tradableList.add(trade);
} }
@ -464,20 +466,15 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
} }
Offer offer = openOffer.getOffer(); 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 // verify request is from signer or backup arbitrator
if (!sender.equals(offer.getOfferPayload().getArbitratorNodeAddress())) { if (!sender.equals(offer.getOfferPayload().getArbitratorSigner()) && !sender.equals(openOffer.getBackupArbitrator())) { // TODO (woodser): get backup arbitrator from maker-signed InitTradeRequest and remove from OpenOffer
boolean isSignerOnline = true; // TODO (woodser): determine if signer is online and test log.warn("Ignoring InitTradeRequest from {} with tradeId {} because request must be from signer or backup arbitrator", sender, request.getTradeId());
if (isSignerOnline) { return;
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;
}
} }
openOfferManager.reserveOpenOffer(openOffer); // TODO (woodser): reserve offer if arbitrator?
Trade trade; Trade trade;
if (offer.isBuyOffer()) if (offer.isBuyOffer())
trade = new BuyerAsMakerTrade(offer, trade = new BuyerAsMakerTrade(offer,
@ -504,12 +501,14 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
//System.out.println("TradeManager trade.setTradingPeerNodeAddress(): " + sender); //System.out.println("TradeManager trade.setTradingPeerNodeAddress(): " + sender);
//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.setArbitratorPubKeyRing(user.getAcceptedMediatorByAddress(sender).getPubKeyRing());
trade.setMakerPubKeyRing(trade.getOffer().getPubKeyRing()); trade.setMakerPubKeyRing(trade.getOffer().getPubKeyRing());
initTradeAndProtocol(trade, getTradeProtocol(trade)); initTradeAndProtocol(trade, getTradeProtocol(trade));
trade.getProcessModel().setReserveTxHash(offer.getOfferFeePaymentTxId()); // TODO (woodser): initialize in initTradeAndProtocol ? trade.getSelf().setReserveTxHash(openOffer.getReserveTxHash()); // TODO (woodser): initialize in initTradeAndProtocol?
trade.getProcessModel().setFrozenKeyImages(openOffer.getFrozenKeyImages()); trade.getSelf().setReserveTxHex(openOffer.getReserveTxHex());
trade.getSelf().setReserveTxKey(openOffer.getReserveTxKey());
trade.getSelf().setReserveTxKeyImages(offer.getOfferPayload().getReserveTxKeyImages());
tradableList.add(trade); tradableList.add(trade);
((MakerProtocol) getTradeProtocol(trade)).handleInitTradeRequest(request, sender, errorMessage -> { ((MakerProtocol) getTradeProtocol(trade)).handleInitTradeRequest(request, sender, errorMessage -> {
@ -702,7 +701,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
UUID.randomUUID().toString(), UUID.randomUUID().toString(),
model.getPeerNodeAddress(), model.getPeerNodeAddress(),
P2PService.getMyNodeAddress(), P2PService.getMyNodeAddress(),
offer.getOfferPayload().getArbitratorNodeAddress()); offer.getOfferPayload().getArbitratorSigner());
} else { } else {
trade = new BuyerAsTakerTrade(offer, trade = new BuyerAsTakerTrade(offer,
amount, amount,
@ -713,12 +712,12 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
UUID.randomUUID().toString(), UUID.randomUUID().toString(),
model.getPeerNodeAddress(), model.getPeerNodeAddress(),
P2PService.getMyNodeAddress(), P2PService.getMyNodeAddress(),
offer.getOfferPayload().getArbitratorNodeAddress()); offer.getOfferPayload().getArbitratorSigner());
} }
trade.getProcessModel().setTradeMessage(model.getTradeRequest()); trade.getProcessModel().setTradeMessage(model.getTradeRequest());
trade.getProcessModel().setMakerSignature(model.getMakerSignature()); 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().setUseSavingsWallet(useSavingsWallet);
trade.getProcessModel().setFundsNeededForTradeAsLong(fundsNeededForTrade.value); trade.getProcessModel().setFundsNeededForTradeAsLong(fundsNeededForTrade.value);
trade.setTakerPubKeyRing(model.getPubKeyRing()); trade.setTakerPubKeyRing(model.getPubKeyRing());

View File

@ -7,11 +7,10 @@ import bisq.core.trade.messages.InitMultisigRequest;
import bisq.core.trade.messages.InitTradeRequest; import bisq.core.trade.messages.InitTradeRequest;
import bisq.core.trade.messages.SignContractRequest; import bisq.core.trade.messages.SignContractRequest;
import bisq.core.trade.protocol.tasks.ApplyFilter; 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.ProcessDepositRequest;
import bisq.core.trade.protocol.tasks.ProcessInitMultisigRequest; import bisq.core.trade.protocol.tasks.ProcessInitMultisigRequest;
import bisq.core.trade.protocol.tasks.ArbitratorProcessesReserveTx; 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.ProcessInitTradeRequest;
import bisq.core.trade.protocol.tasks.ProcessSignContractRequest; import bisq.core.trade.protocol.tasks.ProcessSignContractRequest;
import bisq.core.util.Validator; import bisq.core.util.Validator;
@ -42,8 +41,7 @@ public class ArbitratorProtocol extends DisputeProtocol {
ApplyFilter.class, ApplyFilter.class,
ProcessInitTradeRequest.class, ProcessInitTradeRequest.class,
ArbitratorProcessesReserveTx.class, ArbitratorProcessesReserveTx.class,
ArbitratorSendsInitTradeRequestToMakerIfFromTaker.class, ArbitratorSendsInitTradeAndMultisigRequests.class))
ArbitratorSendsInitMultisigRequestsIfFundsReserved.class))
.executeTasks(); .executeTasks();
} }

View File

@ -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.BuyerSignsDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.buyer.BuyerVerifiesPreparedDelayedPayoutTx; import bisq.core.trade.protocol.tasks.buyer.BuyerVerifiesPreparedDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.maker.MakerRemovesOpenOffer; 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.maker.MakerVerifyTakerFeePayment;
import bisq.core.util.Validator; import bisq.core.util.Validator;
@ -142,7 +143,7 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
ProcessInitTradeRequest.class, ProcessInitTradeRequest.class,
//ApplyFilter.class, // TODO (woodser): these checks apply when maker signs availability request, but not here //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 //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). MakerRemovesOpenOffer.class).
using(new TradeTaskRunner(trade, using(new TradeTaskRunner(trade,
() -> { () -> {

View File

@ -76,9 +76,9 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
public BuyerAsTakerProtocol(BuyerAsTakerTrade trade) { public BuyerAsTakerProtocol(BuyerAsTakerTrade trade) {
super(trade); super(trade);
Offer offer = checkNotNull(trade.getOffer()); Offer offer = checkNotNull(trade.getOffer());
trade.getTradingPeer().setPubKeyRing(offer.getPubKeyRing()); trade.getTradingPeer().setPubKeyRing(offer.getPubKeyRing());
trade.setMakerPubKeyRing(offer.getPubKeyRing());
// TODO (woodser): setup deposit and payout listeners on construction for startup like before rebase? // 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( .setup(tasks(
ApplyFilter.class, ApplyFilter.class,
TakerReservesTradeFunds.class, TakerReservesTradeFunds.class,
TakerSendsInitTradeRequestToArbitrator.class) TakerSendsInitTradeRequestToArbitrator.class) // TODO (woodser): app hangs if this pipeline fails. use .using() like below
.withTimeout(30)) .withTimeout(30))
.executeTasks(); .executeTasks();
} }

View File

@ -25,7 +25,6 @@ import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.filter.FilterManager; import bisq.core.filter.FilterManager;
import bisq.core.network.MessageState; import bisq.core.network.MessageState;
import bisq.core.offer.Offer; import bisq.core.offer.Offer;
import bisq.core.offer.OfferPayload.Direction;
import bisq.core.offer.OpenOfferManager; import bisq.core.offer.OpenOfferManager;
import bisq.core.payment.PaymentAccount; import bisq.core.payment.PaymentAccount;
import bisq.core.payment.payload.PaymentAccountPayload; 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.arbitration.arbitrator.ArbitratorManager;
import bisq.core.support.dispute.mediation.mediator.MediatorManager; import bisq.core.support.dispute.mediation.mediator.MediatorManager;
import bisq.core.support.dispute.refund.refundagent.RefundAgentManager; import bisq.core.support.dispute.refund.refundagent.RefundAgentManager;
import bisq.core.trade.ArbitratorTrade;
import bisq.core.trade.MakerTrade; import bisq.core.trade.MakerTrade;
import bisq.core.trade.TakerTrade;
import bisq.core.trade.Trade; import bisq.core.trade.Trade;
import bisq.core.trade.TradeManager; import bisq.core.trade.TradeManager;
import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.messages.TradeMessage;
@ -60,7 +57,6 @@ import org.bitcoinj.core.Transaction;
import javafx.beans.property.ObjectProperty; import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; 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 // After successful verified we copy that over to the trade.tradingPeerAddress
@Nullable @Nullable
@Setter @Setter
private NodeAddress tempTradingPeerNodeAddress; // TODO (woodser): remove entirely private NodeAddress tempTradingPeerNodeAddress; // TODO (woodser): remove entirely?
// Added in v.1.1.6 // Added in v.1.1.6
@Nullable @Nullable
@ -166,17 +162,11 @@ public class ProcessModel implements Model, PersistablePayload {
private String makerSignature; private String makerSignature;
@Getter @Getter
@Setter @Setter
private NodeAddress arbitratorNodeAddress; private NodeAddress backupArbitrator;
@Nullable @Nullable
@Getter @Getter
@Setter @Setter
transient private MoneroTxWallet reserveTx; transient private MoneroTxWallet reserveTx;
@Setter
@Getter
private String reserveTxHash;
@Setter
@Getter
private List<String> frozenKeyImages = new ArrayList<>();
@Getter @Getter
@Setter @Setter
transient private MoneroTxWallet depositTxXmr; transient private MoneroTxWallet depositTxXmr;
@ -247,20 +237,18 @@ public class ProcessModel implements Model, PersistablePayload {
.setFundsNeededForTradeAsLong(fundsNeededForTradeAsLong) .setFundsNeededForTradeAsLong(fundsNeededForTradeAsLong)
.setPaymentStartedMessageState(paymentStartedMessageStateProperty.get().name()) .setPaymentStartedMessageState(paymentStartedMessageStateProperty.get().name())
.setBuyerPayoutAmountFromMediation(buyerPayoutAmountFromMediation) .setBuyerPayoutAmountFromMediation(buyerPayoutAmountFromMediation)
.setSellerPayoutAmountFromMediation(sellerPayoutAmountFromMediation) .setSellerPayoutAmountFromMediation(sellerPayoutAmountFromMediation);
.addAllFrozenKeyImages(frozenKeyImages);
Optional.ofNullable(maker).ifPresent(e -> builder.setMaker((protobuf.TradingPeer) maker.toProtoMessage())); Optional.ofNullable(maker).ifPresent(e -> builder.setMaker((protobuf.TradingPeer) maker.toProtoMessage()));
Optional.ofNullable(taker).ifPresent(e -> builder.setTaker((protobuf.TradingPeer) taker.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(arbitrator).ifPresent(e -> builder.setArbitrator((protobuf.TradingPeer) arbitrator.toProtoMessage()));
Optional.ofNullable(takeOfferFeeTxId).ifPresent(builder::setTakeOfferFeeTxId); 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(payoutTxSignature).ifPresent(e -> builder.setPayoutTxSignature(ByteString.copyFrom(payoutTxSignature)));
Optional.ofNullable(rawTransactionInputs).ifPresent(e -> builder.addAllRawTransactionInputs(ProtoUtil.collectionToProto(rawTransactionInputs, protobuf.RawTransactionInput.class))); Optional.ofNullable(rawTransactionInputs).ifPresent(e -> builder.addAllRawTransactionInputs(ProtoUtil.collectionToProto(rawTransactionInputs, protobuf.RawTransactionInput.class)));
Optional.ofNullable(changeOutputAddress).ifPresent(builder::setChangeOutputAddress); Optional.ofNullable(changeOutputAddress).ifPresent(builder::setChangeOutputAddress);
Optional.ofNullable(myMultiSigPubKey).ifPresent(e -> builder.setMyMultiSigPubKey(ByteString.copyFrom(myMultiSigPubKey))); Optional.ofNullable(myMultiSigPubKey).ifPresent(e -> builder.setMyMultiSigPubKey(ByteString.copyFrom(myMultiSigPubKey)));
Optional.ofNullable(tempTradingPeerNodeAddress).ifPresent(e -> builder.setTempTradingPeerNodeAddress(tempTradingPeerNodeAddress.toProtoMessage())); Optional.ofNullable(tempTradingPeerNodeAddress).ifPresent(e -> builder.setTempTradingPeerNodeAddress(tempTradingPeerNodeAddress.toProtoMessage()));
Optional.ofNullable(makerSignature).ifPresent(e -> builder.setMakerSignature(makerSignature)); 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(preparedMultisigHex).ifPresent(e -> builder.setPreparedMultisigHex(preparedMultisigHex));
Optional.ofNullable(madeMultisigHex).ifPresent(e -> builder.setMadeMultisigHex(madeMultisigHex)); Optional.ofNullable(madeMultisigHex).ifPresent(e -> builder.setMadeMultisigHex(madeMultisigHex));
Optional.ofNullable(multisigSetupComplete).ifPresent(e -> builder.setMultisigSetupComplete(multisigSetupComplete)); Optional.ofNullable(multisigSetupComplete).ifPresent(e -> builder.setMultisigSetupComplete(multisigSetupComplete));
@ -282,8 +270,6 @@ public class ProcessModel implements Model, PersistablePayload {
processModel.setSellerPayoutAmountFromMediation(proto.getSellerPayoutAmountFromMediation()); processModel.setSellerPayoutAmountFromMediation(proto.getSellerPayoutAmountFromMediation());
// nullable // nullable
processModel.setReserveTxHash(proto.getReserveTxHash());
processModel.setFrozenKeyImages(proto.getFrozenKeyImagesList());
processModel.setTakeOfferFeeTxId(ProtoUtil.stringOrNullFromProto(proto.getTakeOfferFeeTxId())); processModel.setTakeOfferFeeTxId(ProtoUtil.stringOrNullFromProto(proto.getTakeOfferFeeTxId()));
processModel.setPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getPayoutTxSignature())); processModel.setPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getPayoutTxSignature()));
List<RawTransactionInput> rawTransactionInputs = proto.getRawTransactionInputsList().isEmpty() ? 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.setTempTradingPeerNodeAddress(proto.hasTempTradingPeerNodeAddress() ? NodeAddress.fromProto(proto.getTempTradingPeerNodeAddress()) : null);
processModel.setMediatedPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getMediatedPayoutTxSignature())); processModel.setMediatedPayoutTxSignature(ProtoUtil.byteArrayOrNullFromProto(proto.getMediatedPayoutTxSignature()));
processModel.setMakerSignature(proto.getMakerSignature()); 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.setPreparedMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getPreparedMultisigHex()));
processModel.setMadeMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getMadeMultisigHex())); processModel.setMadeMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getMadeMultisigHex()));
processModel.setMultisigSetupComplete(proto.getMultisigSetupComplete()); processModel.setMultisigSetupComplete(proto.getMultisigSetupComplete());

View File

@ -38,6 +38,7 @@ import bisq.core.trade.protocol.tasks.ProcessSignContractResponse;
import bisq.core.trade.protocol.tasks.SendSignContractRequestAfterMultisig; import bisq.core.trade.protocol.tasks.SendSignContractRequestAfterMultisig;
import bisq.core.trade.protocol.tasks.TradeTask; import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.core.trade.protocol.tasks.maker.MakerRemovesOpenOffer; 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.maker.MakerVerifyTakerFeePayment;
import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx; import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest; import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest;
@ -144,7 +145,7 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
ProcessInitTradeRequest.class, ProcessInitTradeRequest.class,
//ApplyFilter.class, // TODO (woodser): these checks apply when maker signs availability request, but not here //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 //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). MakerRemovesOpenOffer.class).
using(new TradeTaskRunner(trade, using(new TradeTaskRunner(trade,
() -> { () -> {

View File

@ -73,6 +73,7 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
super(trade); super(trade);
Offer offer = checkNotNull(trade.getOffer()); Offer offer = checkNotNull(trade.getOffer());
trade.getTradingPeer().setPubKeyRing(offer.getPubKeyRing()); trade.getTradingPeer().setPubKeyRing(offer.getPubKeyRing());
trade.setMakerPubKeyRing(offer.getPubKeyRing());
} }

View File

@ -27,7 +27,7 @@ import bisq.common.proto.persistable.PersistablePayload;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import com.google.protobuf.Message; import com.google.protobuf.Message;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -102,6 +102,8 @@ public final class TradingPeer implements PersistablePayload {
@Nullable @Nullable
private String reserveTxKey; private String reserveTxKey;
@Nullable @Nullable
private List<String> reserveTxKeyImages = new ArrayList<>();
@Nullable
private String preparedMultisigHex; private String preparedMultisigHex;
@Nullable @Nullable
private String madeMultisigHex; private String madeMultisigHex;
@ -120,7 +122,8 @@ public final class TradingPeer implements PersistablePayload {
@Override @Override
public Message toProtoMessage() { public Message toProtoMessage() {
final protobuf.TradingPeer.Builder builder = protobuf.TradingPeer.newBuilder() final protobuf.TradingPeer.Builder builder = protobuf.TradingPeer.newBuilder()
.setChangeOutputValue(changeOutputValue); .setChangeOutputValue(changeOutputValue)
.addAllReserveTxKeyImages(reserveTxKeyImages);
Optional.ofNullable(accountId).ifPresent(builder::setAccountId); Optional.ofNullable(accountId).ifPresent(builder::setAccountId);
Optional.ofNullable(paymentAccountId).ifPresent(builder::setPaymentAccountId); Optional.ofNullable(paymentAccountId).ifPresent(builder::setPaymentAccountId);
Optional.ofNullable(paymentMethodId).ifPresent(builder::setPaymentMethodId); Optional.ofNullable(paymentMethodId).ifPresent(builder::setPaymentMethodId);
@ -183,6 +186,7 @@ public final class TradingPeer implements PersistablePayload {
tradingPeer.setReserveTxHash(ProtoUtil.stringOrNullFromProto(proto.getReserveTxHash())); tradingPeer.setReserveTxHash(ProtoUtil.stringOrNullFromProto(proto.getReserveTxHash()));
tradingPeer.setReserveTxHex(ProtoUtil.stringOrNullFromProto(proto.getReserveTxHex())); tradingPeer.setReserveTxHex(ProtoUtil.stringOrNullFromProto(proto.getReserveTxHex()));
tradingPeer.setReserveTxKey(ProtoUtil.stringOrNullFromProto(proto.getReserveTxKey())); tradingPeer.setReserveTxKey(ProtoUtil.stringOrNullFromProto(proto.getReserveTxKey()));
tradingPeer.setReserveTxKeyImages(proto.getReserveTxKeyImagesList());
tradingPeer.setPreparedMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getPreparedMultisigHex())); tradingPeer.setPreparedMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getPreparedMultisigHex()));
tradingPeer.setMadeMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getMadeMultisigHex())); tradingPeer.setMadeMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getMadeMultisigHex()));
tradingPeer.setSignedPayoutTxHex(ProtoUtil.stringOrNullFromProto(proto.getSignedPayoutTxHex())); tradingPeer.setSignedPayoutTxHex(ProtoUtil.stringOrNullFromProto(proto.getSignedPayoutTxHex()));

View File

@ -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();
}
}

View File

@ -22,24 +22,33 @@ import bisq.common.app.Version;
import bisq.common.crypto.Sig; import bisq.common.crypto.Sig;
import bisq.common.taskrunner.TaskRunner; import bisq.common.taskrunner.TaskRunner;
import bisq.core.trade.Trade; import bisq.core.trade.Trade;
import bisq.core.trade.messages.InitMultisigRequest;
import bisq.core.trade.messages.InitTradeRequest; import bisq.core.trade.messages.InitTradeRequest;
import bisq.core.trade.protocol.TradeListener; import bisq.core.trade.protocol.TradeListener;
import bisq.network.p2p.AckMessage; import bisq.network.p2p.AckMessage;
import bisq.network.p2p.NodeAddress; import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.P2PService;
import bisq.network.p2p.SendDirectMessageListener; import bisq.network.p2p.SendDirectMessageListener;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import java.util.Date; import java.util.Date;
import java.util.UUID; import java.util.UUID;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import monero.wallet.MoneroWallet;
/** /**
* Arbitrator sends InitTradeRequest to maker after receiving InitTradeRequest * Arbitrator sends InitTradeRequest to maker after receiving InitTradeRequest
* from taker and verifying taker reserve tx. * from taker and verifying taker reserve tx.
*
* Arbitrator sends InitMultisigRequests after the maker acks.
*/ */
@Slf4j @Slf4j
public class ArbitratorSendsInitTradeRequestToMakerIfFromTaker extends TradeTask { public class ArbitratorSendsInitTradeAndMultisigRequests extends TradeTask {
private boolean takerAck;
private boolean makerAck;
@SuppressWarnings({"unused"}) @SuppressWarnings({"unused"})
public ArbitratorSendsInitTradeRequestToMakerIfFromTaker(TaskRunner taskHandler, Trade trade) { public ArbitratorSendsInitTradeAndMultisigRequests(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade); super(taskHandler, trade);
} }
@ -48,12 +57,15 @@ public class ArbitratorSendsInitTradeRequestToMakerIfFromTaker extends TradeTask
try { try {
runInterceptHook(); runInterceptHook();
// collect fields for request // skip if request not from taker
String offerId = processModel.getOffer().getId();
InitTradeRequest request = (InitTradeRequest) processModel.getTradeMessage(); InitTradeRequest request = (InitTradeRequest) processModel.getTradeMessage();
if (!request.getSenderNodeAddress().equals(trade.getTakerNodeAddress())) {
complete();
return;
}
// arbitrator signs offer id as nonce to avoid challenge protocol // 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 // save pub keys
processModel.getArbitrator().setPubKeyRing(processModel.getPubKeyRing()); // TODO (woodser): why duplicating field in process model 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 // create request to initialize trade with maker
InitTradeRequest makerRequest = new InitTradeRequest( InitTradeRequest makerRequest = new InitTradeRequest(
offerId, processModel.getOfferId(),
request.getSenderNodeAddress(), request.getSenderNodeAddress(),
request.getPubKeyRing(), request.getPubKeyRing(),
trade.getTradeAmount().value, trade.getTradeAmount().value,
@ -91,7 +103,7 @@ public class ArbitratorSendsInitTradeRequestToMakerIfFromTaker extends TradeTask
public void onAckMessage(AckMessage ackMessage, NodeAddress sender) { public void onAckMessage(AckMessage ackMessage, NodeAddress sender) {
if (sender.equals(trade.getMakerNodeAddress()) && ackMessage.getSourceMsgClassName().equals(InitTradeRequest.class.getSimpleName())) { if (sender.equals(trade.getMakerNodeAddress()) && ackMessage.getSourceMsgClassName().equals(InitTradeRequest.class.getSimpleName())) {
trade.removeListener(this); 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? 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); 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();
}
} }

View File

@ -23,7 +23,6 @@ import bisq.core.btc.model.XmrAddressEntry;
import bisq.core.offer.Offer; import bisq.core.offer.Offer;
import bisq.core.trade.MakerTrade; import bisq.core.trade.MakerTrade;
import bisq.core.trade.SellerTrade; import bisq.core.trade.SellerTrade;
import bisq.core.trade.TakerTrade;
import bisq.core.trade.Trade; import bisq.core.trade.Trade;
import bisq.core.trade.TradeUtils; import bisq.core.trade.TradeUtils;
import bisq.core.trade.messages.SignContractRequest; import bisq.core.trade.messages.SignContractRequest;
@ -62,8 +61,8 @@ public class SendSignContractRequestAfterMultisig extends TradeTask {
// thaw reserved outputs // thaw reserved outputs
MoneroWallet wallet = trade.getXmrWalletService().getWallet(); MoneroWallet wallet = trade.getXmrWalletService().getWallet();
for (String frozenKeyImage : processModel.getFrozenKeyImages()) { for (String reserveTxKeyImage : trade.getSelf().getReserveTxKeyImages()) {
wallet.thawOutput(frozenKeyImage); wallet.thawOutput(reserveTxKeyImage);
} }
// create deposit tx // create deposit tx

View File

@ -101,8 +101,7 @@ public class UpdateMultisigWithTradingPeer extends TradeTask {
new Date().getTime(), new Date().getTime(),
updatedMultisigHex); updatedMultisigHex);
System.out.println("SENDING MESSAGE!!!!!!!"); System.out.println("Sending message: " + message);
System.out.println(message);
// TODO (woodser): trade.getTradingPeerNodeAddress() and/or trade.getTradingPeerPubKeyRing() are null on restart of application, so cannot send payment to complete trade // 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()); log.info("Send {} with offerId {} and uid {} to peer {}", message.getClass().getSimpleName(), message.getTradeId(), message.getUid(), trade.getTradingPeerNodeAddress());

View File

@ -18,14 +18,11 @@
package bisq.core.trade.protocol.tasks.maker; package bisq.core.trade.protocol.tasks.maker;
import bisq.core.btc.model.XmrAddressEntry; import bisq.core.btc.model.XmrAddressEntry;
import bisq.core.btc.wallet.XmrWalletService; import bisq.core.offer.Offer;
import bisq.core.payment.payload.PaymentAccountPayload;
import bisq.core.trade.Trade; import bisq.core.trade.Trade;
import bisq.core.trade.messages.InitTradeRequest; import bisq.core.trade.messages.InitTradeRequest;
import bisq.core.trade.protocol.tasks.TradeTask; import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.core.user.User;
import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.SendDirectMessageListener; import bisq.network.p2p.SendDirectMessageListener;
import bisq.common.app.Version; import bisq.common.app.Version;
@ -34,8 +31,6 @@ import bisq.common.taskrunner.TaskRunner;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import java.util.Date;
import java.util.List;
import java.util.UUID; import java.util.UUID;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -55,88 +50,63 @@ public class MakerSendsInitTradeRequestIfUnreserved extends TradeTask {
try { try {
runInterceptHook(); 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 // verify trade
InitTradeRequest request = (InitTradeRequest) processModel.getTradeMessage(); InitTradeRequest makerRequest = (InitTradeRequest) processModel.getTradeMessage(); // arbitrator's InitTradeRequest to maker
checkNotNull(request); checkNotNull(makerRequest);
checkTradeId(processModel.getOfferId(), request); 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 // maker signs offer id as nonce to avoid challenge protocol // TODO (woodser): is this necessary?
final PaymentAccountPayload paymentAccountPayload = checkNotNull(processModel.getPaymentAccountPayload(trade), "processModel.getPaymentAccountPayload(trade) must not be null"); byte[] sig = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), offer.getId().getBytes(Charsets.UTF_8));
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 request to arbitrator
InitTradeRequest arbitratorRequest = new InitTradeRequest(
// create message to initialize trade offer.getId(),
InitTradeRequest message = new InitTradeRequest(
offerId,
processModel.getMyNodeAddress(), processModel.getMyNodeAddress(),
processModel.getPubKeyRing(), processModel.getPubKeyRing(),
trade.getTradeAmount().value, offer.getAmount().value,
trade.getTradePrice().getValue(), offer.getPrice().getValue(),
trade.getTakerFee().getValue(), offer.getMakerFee().value,
processModel.getAccountId(), trade.getProcessModel().getAccountId(),
paymentAccountPayload.getId(), offer.getMakerPaymentAccountId(),
paymentAccountPayload.getPaymentMethodId(), offer.getOfferPayload().getPaymentMethodId(),
UUID.randomUUID().toString(), UUID.randomUUID().toString(),
Version.getP2PMessageVersion(), Version.getP2PMessageVersion(),
sig, sig,
new Date().getTime(), makerRequest.getCurrentDate(),
trade.getMakerNodeAddress(), trade.getMakerNodeAddress(),
trade.getTakerNodeAddress(), trade.getTakerNodeAddress(),
trade.getArbitratorNodeAddress(), trade.getArbitratorNodeAddress(),
processModel.getReserveTx().getHash(), // TODO (woodser): need to first create and save reserve tx trade.getSelf().getReserveTxHash(),
processModel.getReserveTx().getFullHex(), trade.getSelf().getReserveTxHex(),
processModel.getReserveTx().getKey(), trade.getSelf().getReserveTxKey(),
processModel.getXmrWalletService().getAddressEntry(offerId, XmrAddressEntry.Context.TRADE_PAYOUT).get().getAddressString(), model.getXmrWalletService().getOrCreateAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString(),
null); 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 // 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( processModel.getP2PService().sendEncryptedDirectMessage(
trade.getArbitratorNodeAddress(), trade.getArbitratorNodeAddress(),
trade.getArbitratorPubKeyRing(), trade.getArbitratorPubKeyRing(),
message, arbitratorRequest,
new SendDirectMessageListener() { new SendDirectMessageListener() {
@Override @Override
public void onArrived() { 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(); complete();
} }
@Override @Override
public void onFault(String errorMessage) { public void onFault(String errorMessage) {
log.error("Sending {} failed: uid={}; peer={}; error={}", message.getClass().getSimpleName(), message.getUid(), trade.getTradingPeerNodeAddress(), errorMessage); log.warn("Failed to send {} to arbitrator, error={}.", InitTradeRequest.class.getSimpleName(), errorMessage);
appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage);
failed(); failed();
} }
} });
);
} catch (Throwable t) { } catch (Throwable t) {
failed(t); failed(t);
} }

View File

@ -49,19 +49,18 @@ public class TakerReservesTradeFunds extends TradeTask {
// freeze trade funds // freeze trade funds
// TODO (woodser): synchronize to handle potential race condition where concurrent trades freeze each other's outputs // 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(); MoneroWallet wallet = model.getXmrWalletService().getWallet();
for (MoneroOutput input : reserveTx.getInputs()) { for (MoneroOutput input : reserveTx.getInputs()) {
frozenKeyImages.add(input.getKeyImage().getHex()); reserveTxKeyImages.add(input.getKeyImage().getHex());
wallet.freezeOutput(input.getKeyImage().getHex()); wallet.freezeOutput(input.getKeyImage().getHex());
} }
// save process state // save process state
// TODO (woodser): persist // TODO (woodser): persist
processModel.setReserveTx(reserveTx); processModel.setReserveTx(reserveTx);
processModel.setReserveTxHash(reserveTx.getHash()); processModel.getTaker().setReserveTxKeyImages(reserveTxKeyImages);
processModel.setFrozenKeyImages(frozenKeyImages); trade.setTakerFeeTxId(reserveTx.getHash()); // TODO (woodser): this should be multisig deposit tx id? how is it used?
trade.setTakerFeeTxId(reserveTx.getHash());
//trade.setState(Trade.State.TAKER_PUBLISHED_TAKER_FEE_TX); // TODO (woodser): fee tx is not broadcast separate, update states //trade.setState(Trade.State.TAKER_PUBLISHED_TAKER_FEE_TX); // TODO (woodser): fee tx is not broadcast separate, update states
complete(); complete();
} catch (Throwable t) { } catch (Throwable t) {

View File

@ -21,7 +21,7 @@ import bisq.core.support.dispute.mediation.mediator.Mediator;
import bisq.core.trade.Trade; import bisq.core.trade.Trade;
import bisq.core.trade.messages.InitTradeRequest; import bisq.core.trade.messages.InitTradeRequest;
import bisq.core.trade.protocol.tasks.TradeTask; import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.SendDirectMessageListener; import bisq.network.p2p.SendDirectMessageListener;
import bisq.common.app.Version; import bisq.common.app.Version;
@ -42,62 +42,85 @@ public class TakerSendsInitTradeRequestToArbitrator extends TradeTask {
try { try {
runInterceptHook(); runInterceptHook();
// get primary arbitrator // send request to offer signer
Mediator arbitrator = processModel.getUser().getAcceptedMediatorByAddress(trade.getArbitratorNodeAddress()); sendInitTradeRequest(trade.getOffer().getOfferPayload().getArbitratorSigner(), new SendDirectMessageListener() {
if (arbitrator == null) throw new RuntimeException("Cannot get arbitrator instance from node address"); // TODO (woodser): null if arbitrator goes offline or never seen? @Override
public void onArrived() {
// save pub keys log.info("{} arrived at arbitrator: offerId={}", InitTradeRequest.class.getSimpleName(), trade.getId());
processModel.getArbitrator().setPubKeyRing(arbitrator.getPubKeyRing()); complete();
trade.setArbitratorPubKeyRing(processModel.getArbitrator().getPubKeyRing()); }
trade.setMakerPubKeyRing(trade.getTradingPeer().getPubKeyRing());
// send request to backup arbitrator if signer unavailable
// send trade request to arbitrator @Override
InitTradeRequest makerRequest = (InitTradeRequest) processModel.getTradeMessage(); public void onFault(String errorMessage) {
InitTradeRequest arbitratorRequest = new InitTradeRequest( log.info("Sending {} to signing arbitrator {} failed, using backup arbitrator {}. error={}", InitTradeRequest.class.getSimpleName(), trade.getOffer().getOfferPayload().getArbitratorSigner(), processModel.getBackupArbitrator(), errorMessage);
makerRequest.getTradeId(), if (processModel.getBackupArbitrator() == null) {
makerRequest.getSenderNodeAddress(), log.warn("Cannot take offer because signing arbitrator is offline and backup arbitrator is null");
makerRequest.getPubKeyRing(), failed();
makerRequest.getTradeAmount(), return;
makerRequest.getTradePrice(), }
makerRequest.getTradeFee(), sendInitTradeRequest(processModel.getBackupArbitrator(), new SendDirectMessageListener() {
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() {
@Override @Override
public void onArrived() { 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 @Override
public void onFault(String errorMessage) { public void onFault(String errorMessage) { // TODO (woodser): distinguish nack from offline
log.error("Sending {} failed: uid={}; peer={}; error={}", arbitratorRequest.getClass().getSimpleName(), arbitratorRequest.getUid(), trade.getArbitratorNodeAddress(), errorMessage); log.warn("Cannot take offer because arbitrators are unavailable: error={}.", InitTradeRequest.class.getSimpleName(), errorMessage);
appendToErrorMessage("Sending message failed: message=" + arbitratorRequest + "\nerrorMessage=" + errorMessage);
failed(); failed();
} }
} });
); }
});
} catch (Throwable t) { } catch (Throwable t) {
failed(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
);
}
} }

View File

@ -39,7 +39,7 @@ public class TradableListTest {
// test adding an OpenOffer and convert toProto // test adding an OpenOffer and convert toProto
Offer offer = new Offer(offerPayload); Offer offer = new Offer(offerPayload);
OpenOffer openOffer = new OpenOffer(offer); OpenOffer openOffer = new OpenOffer(offer, 0, "", "", "");
openOfferTradableList.add(openOffer); openOfferTradableList.add(openOffer);
message = (protobuf.PersistableEnvelope) openOfferTradableList.toProtoMessage(); message = (protobuf.PersistableEnvelope) openOfferTradableList.toProtoMessage();
assertEquals(message.getMessageCase(), TRADABLE_LIST); assertEquals(message.getMessageCase(), TRADABLE_LIST);

View File

@ -219,7 +219,7 @@ class EditOfferDataModel extends MutableOfferDataModel {
offerPayload.getHashOfChallenge(), offerPayload.getHashOfChallenge(),
offerPayload.getExtraDataMap(), offerPayload.getExtraDataMap(),
offerPayload.getProtocolVersion(), offerPayload.getProtocolVersion(),
offerPayload.getArbitratorNodeAddress(), offerPayload.getArbitratorSigner(),
offerPayload.getArbitratorSignature(), offerPayload.getArbitratorSignature(),
offerPayload.getReserveTxKeyImages()); offerPayload.getReserveTxKeyImages());

View File

@ -189,7 +189,7 @@ message OfferAvailabilityResponse {
repeated int32 supported_capabilities = 3; repeated int32 supported_capabilities = 3;
string uid = 4; string uid = 4;
string maker_signature = 5; string maker_signature = 5;
NodeAddress arbitrator_node_address = 6; NodeAddress backup_arbitrator = 6;
} }
message RefreshOfferMessage { message RefreshOfferMessage {
@ -853,7 +853,7 @@ message OfferPayload {
map<string, string> extra_data = 34; map<string, string> extra_data = 34;
int32 protocol_version = 35; int32 protocol_version = 35;
NodeAddress arbitrator_node_address = 1001; NodeAddress arbitrator_signer = 1001;
string arbitrator_signature = 1002; string arbitrator_signature = 1002;
repeated string reserve_tx_key_images = 1003; repeated string reserve_tx_key_images = 1003;
} }
@ -1472,9 +1472,11 @@ message OpenOffer {
Offer offer = 1; Offer offer = 1;
State state = 2; State state = 2;
NodeAddress arbitrator_node_address = 3; NodeAddress backup_arbitrator = 3;
int64 trigger_price = 4; 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 { message Tradable {
@ -1644,18 +1646,16 @@ message ProcessModel {
int64 seller_payout_amount_from_mediation = 20; int64 seller_payout_amount_from_mediation = 20;
string maker_signature = 1001; string maker_signature = 1001;
NodeAddress arbitrator_node_address = 1002; NodeAddress backup_arbitrator = 1002;
TradingPeer maker = 1003; TradingPeer maker = 1003;
TradingPeer taker = 1004; TradingPeer taker = 1004;
TradingPeer arbitrator = 1005; TradingPeer arbitrator = 1005;
NodeAddress temp_trading_peer_node_address = 1006; NodeAddress temp_trading_peer_node_address = 1006;
string reserve_tx_hash = 1007; string prepared_multisig_hex = 1007;
repeated string frozen_key_images = 1008; string made_multisig_hex = 1008;
string prepared_multisig_hex = 1009; bool multisig_setup_complete = 1009;
string made_multisig_hex = 1010; bool maker_ready_to_fund_multisig = 1010;
bool multisig_setup_complete = 1011; bool multisig_deposit_initiated = 1011;
bool maker_ready_to_fund_multisig = 1012;
bool multisig_deposit_initiated = 1013;
} }
message TradingPeer { message TradingPeer {
@ -1681,12 +1681,13 @@ message TradingPeer {
string reserve_tx_hash = 1001; string reserve_tx_hash = 1001;
string reserve_tx_hex = 1002; string reserve_tx_hex = 1002;
string reserve_tx_key = 1003; string reserve_tx_key = 1003;
string prepared_multisig_hex = 1004; repeated string reserve_tx_key_images = 1004;
string made_multisig_hex = 1005; string prepared_multisig_hex = 1005;
string signed_payout_tx_hex = 1006; string made_multisig_hex = 1006;
string deposit_tx_hash = 1007; string signed_payout_tx_hex = 1007;
string deposit_tx_hex = 1008; string deposit_tx_hash = 1008;
string deposit_tx_key = 1009; string deposit_tx_hex = 1009;
string deposit_tx_key = 1010;
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////