select online, registered, and least used arbitrator (#400)

support registering and unregistering arbitrators over grpc
maker always sends InitTradeRequest to arbitrator
share original contract for comparision
remove backup arbitator from model
cleanup trade states
This commit is contained in:
woodser 2022-08-13 09:47:33 -04:00 committed by GitHub
parent 757c7cf19c
commit 3727d12ef6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
51 changed files with 472 additions and 382 deletions

View file

@ -14,7 +14,7 @@ import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestInfo;
import static bisq.cli.table.builder.TableType.TRADE_DETAIL_TBL; import static bisq.cli.table.builder.TableType.TRADE_DETAIL_TBL;
import static bisq.core.trade.Trade.Phase.DEPOSIT_UNLOCKED; import static bisq.core.trade.Trade.Phase.DEPOSITS_UNLOCKED;
import static bisq.core.trade.Trade.Phase.PAYMENT_SENT; import static bisq.core.trade.Trade.Phase.PAYMENT_SENT;
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED; import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
import static bisq.core.trade.Trade.State.BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG; import static bisq.core.trade.Trade.State.BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG;
@ -80,7 +80,7 @@ public class AbstractTradeTest extends AbstractOfferTest {
String tradeId) { String tradeId) {
Predicate<TradeInfo> isTradeInDepositUnlockedStateAndPhase = (t) -> Predicate<TradeInfo> isTradeInDepositUnlockedStateAndPhase = (t) ->
t.getState().equals(DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN.name()) t.getState().equals(DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN.name())
&& t.getPhase().equals(DEPOSIT_UNLOCKED.name()); && t.getPhase().equals(DEPOSITS_UNLOCKED.name());
String userName = toUserName.apply(grpcClient); String userName = toUserName.apply(grpcClient);
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) { for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
@ -95,7 +95,7 @@ public class AbstractTradeTest extends AbstractOfferTest {
genBtcBlocksThenWait(1, 4_000); genBtcBlocksThenWait(1, 4_000);
} else { } else {
EXPECTED_PROTOCOL_STATUS.setState(DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN) EXPECTED_PROTOCOL_STATUS.setState(DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN)
.setPhase(DEPOSIT_UNLOCKED) .setPhase(DEPOSITS_UNLOCKED)
.setDepositPublished(true) .setDepositPublished(true)
.setDepositConfirmed(true); .setDepositConfirmed(true);
verifyExpectedProtocolStatus(trade); verifyExpectedProtocolStatus(trade);

View file

@ -406,8 +406,12 @@ public class CoreApi {
// Dispute Agents // Dispute Agents
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public void registerDisputeAgent(String disputeAgentType, String registrationKey) { public void registerDisputeAgent(String disputeAgentType, String registrationKey, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
coreDisputeAgentsService.registerDisputeAgent(disputeAgentType, registrationKey); coreDisputeAgentsService.registerDisputeAgent(disputeAgentType, registrationKey, resultHandler, errorMessageHandler);
}
public void unregisterDisputeAgent(String disputeAgentType, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
coreDisputeAgentsService.unregisterDisputeAgent(disputeAgentType, resultHandler, errorMessageHandler);
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////

View file

@ -31,7 +31,8 @@ import bisq.network.p2p.P2PService;
import bisq.common.config.Config; import bisq.common.config.Config;
import bisq.common.crypto.KeyRing; import bisq.common.crypto.KeyRing;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler;
import org.bitcoinj.core.ECKey; import org.bitcoinj.core.ECKey;
import javax.inject.Inject; import javax.inject.Inject;
@ -88,14 +89,10 @@ class CoreDisputeAgentsService {
this.languageCodes = asList("de", "en", "es", "fr"); this.languageCodes = asList("de", "en", "es", "fr");
} }
void registerDisputeAgent(String disputeAgentType, String registrationKey) { void registerDisputeAgent(String disputeAgentType, String registrationKey, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
if (!p2PService.isBootstrapped()) if (!p2PService.isBootstrapped())
throw new IllegalStateException("p2p service is not bootstrapped yet"); throw new IllegalStateException("p2p service is not bootstrapped yet");
if (config.baseCurrencyNetwork.isMainnet()
|| !config.useLocalhostForP2P)
throw new IllegalStateException("dispute agents must be registered in a Bisq UI");
Optional<SupportType> supportType = getSupportType(disputeAgentType); Optional<SupportType> supportType = getSupportType(disputeAgentType);
if (supportType.isPresent()) { if (supportType.isPresent()) {
ECKey ecKey; ECKey ecKey;
@ -104,16 +101,18 @@ class CoreDisputeAgentsService {
case ARBITRATION: case ARBITRATION:
if (user.getRegisteredArbitrator() != null) { if (user.getRegisteredArbitrator() != null) {
log.warn("ignoring request to re-register as arbitrator"); log.warn("ignoring request to re-register as arbitrator");
resultHandler.handleResult();
return; return;
} }
ecKey = arbitratorManager.getRegistrationKey(registrationKey); ecKey = arbitratorManager.getRegistrationKey(registrationKey);
if (ecKey == null) throw new IllegalStateException("invalid registration key"); if (ecKey == null) throw new IllegalStateException("invalid registration key");
signature = arbitratorManager.signStorageSignaturePubKey(Objects.requireNonNull(ecKey)); signature = arbitratorManager.signStorageSignaturePubKey(Objects.requireNonNull(ecKey));
registerArbitrator(nodeAddress, languageCodes, ecKey, signature); registerArbitrator(nodeAddress, languageCodes, ecKey, signature, resultHandler, errorMessageHandler);
return; return;
case MEDIATION: case MEDIATION:
if (user.getRegisteredMediator() != null) { if (user.getRegisteredMediator() != null) {
log.warn("ignoring request to re-register as mediator"); log.warn("ignoring request to re-register as mediator");
resultHandler.handleResult();
return; return;
} }
ecKey = mediatorManager.getRegistrationKey(registrationKey); ecKey = mediatorManager.getRegistrationKey(registrationKey);
@ -124,6 +123,7 @@ class CoreDisputeAgentsService {
case REFUND: case REFUND:
if (user.getRegisteredRefundAgent() != null) { if (user.getRegisteredRefundAgent() != null) {
log.warn("ignoring request to re-register as refund agent"); log.warn("ignoring request to re-register as refund agent");
resultHandler.handleResult();
return; return;
} }
ecKey = refundAgentManager.getRegistrationKey(registrationKey); ecKey = refundAgentManager.getRegistrationKey(registrationKey);
@ -139,10 +139,38 @@ class CoreDisputeAgentsService {
} }
} }
void unregisterDisputeAgent(String disputeAgentType, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
if (!p2PService.isBootstrapped())
throw new IllegalStateException("p2p service is not bootstrapped yet");
Optional<SupportType> supportType = getSupportType(disputeAgentType);
if (supportType.isPresent()) {
switch (supportType.get()) {
case ARBITRATION:
if (user.getRegisteredArbitrator() == null) {
errorMessageHandler.handleErrorMessage("User is not arbitrator");
return;
}
unregisterDisputeAgent(resultHandler, errorMessageHandler);
return;
case MEDIATION:
throw new IllegalStateException("unregister mediator not implemented");
case REFUND:
throw new IllegalStateException("unregister refund agent not implemented");
case TRADE:
throw new IllegalArgumentException("trade agent registration not supported");
}
} else {
throw new IllegalArgumentException(format("unknown dispute agent type '%s'", disputeAgentType));
}
}
private void registerArbitrator(NodeAddress nodeAddress, private void registerArbitrator(NodeAddress nodeAddress,
List<String> languageCodes, List<String> languageCodes,
ECKey ecKey, ECKey ecKey,
String signature) { String signature,
ResultHandler resultHandler,
ErrorMessageHandler errorMessageHandler) {
Arbitrator arbitrator = new Arbitrator( Arbitrator arbitrator = new Arbitrator(
p2PService.getAddress(), p2PService.getAddress(),
xmrWalletService.getWallet().getPrimaryAddress(), // TODO: how is this used? xmrWalletService.getWallet().getPrimaryAddress(), // TODO: how is this used?
@ -155,10 +183,9 @@ class CoreDisputeAgentsService {
null, null,
null); null);
arbitratorManager.addDisputeAgent(arbitrator, () -> { arbitratorManager.addDisputeAgent(arbitrator, () -> {
}, errorMessage -> { if (!arbitratorManager.getDisputeAgentByNodeAddress(nodeAddress).isPresent()) errorMessageHandler.handleErrorMessage("could not register arbitrator");
}); else resultHandler.handleResult();
arbitratorManager.getDisputeAgentByNodeAddress(nodeAddress).orElseThrow(() -> }, errorMessageHandler);
new IllegalStateException("could not register arbitrator"));
} }
private void registerMediator(NodeAddress nodeAddress, private void registerMediator(NodeAddress nodeAddress,
@ -219,4 +246,10 @@ class CoreDisputeAgentsService {
return Optional.empty(); return Optional.empty();
} }
} }
private void unregisterDisputeAgent(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
arbitratorManager.removeDisputeAgent(resultHandler, errorMesage -> {
errorMessageHandler.handleErrorMessage("Error unregistering dispute agent: " + errorMesage);
});
}
} }

View file

@ -74,6 +74,8 @@ public class OfferInfo implements Payload {
private final String pubKeyRing; private final String pubKeyRing;
private final String versionNumber; private final String versionNumber;
private final int protocolVersion; private final int protocolVersion;
@Nullable
private final String arbitratorSigner;
public OfferInfo(OfferInfoBuilder builder) { public OfferInfo(OfferInfoBuilder builder) {
this.id = builder.getId(); this.id = builder.getId();
@ -104,6 +106,7 @@ public class OfferInfo implements Payload {
this.pubKeyRing = builder.getPubKeyRing(); this.pubKeyRing = builder.getPubKeyRing();
this.versionNumber = builder.getVersionNumber(); this.versionNumber = builder.getVersionNumber();
this.protocolVersion = builder.getProtocolVersion(); this.protocolVersion = builder.getProtocolVersion();
this.arbitratorSigner = builder.getArbitratorSigner();
} }
public static OfferInfo toOfferInfo(Offer offer) { public static OfferInfo toOfferInfo(Offer offer) {
@ -166,7 +169,8 @@ public class OfferInfo implements Payload {
.withOwnerNodeAddress(offer.getOfferPayload().getOwnerNodeAddress().getFullAddress()) .withOwnerNodeAddress(offer.getOfferPayload().getOwnerNodeAddress().getFullAddress())
.withPubKeyRing(offer.getOfferPayload().getPubKeyRing().toString()) .withPubKeyRing(offer.getOfferPayload().getPubKeyRing().toString())
.withVersionNumber(offer.getOfferPayload().getVersionNr()) .withVersionNumber(offer.getOfferPayload().getVersionNr())
.withProtocolVersion(offer.getOfferPayload().getProtocolVersion()); .withProtocolVersion(offer.getOfferPayload().getProtocolVersion())
.withArbitratorSigner(offer.getOfferPayload().getArbitratorSigner() == null ? null : offer.getOfferPayload().getArbitratorSigner().getFullAddress());
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -203,6 +207,7 @@ public class OfferInfo implements Payload {
.setPubKeyRing(pubKeyRing) .setPubKeyRing(pubKeyRing)
.setVersionNr(versionNumber) .setVersionNr(versionNumber)
.setProtocolVersion(protocolVersion); .setProtocolVersion(protocolVersion);
Optional.ofNullable(arbitratorSigner).ifPresent(builder::setArbitratorSigner);
Optional.ofNullable(offerFeePaymentTxId).ifPresent(builder::setOfferFeePaymentTxId); Optional.ofNullable(offerFeePaymentTxId).ifPresent(builder::setOfferFeePaymentTxId);
return builder.build(); return builder.build();
} }
@ -238,6 +243,7 @@ public class OfferInfo implements Payload {
.withPubKeyRing(proto.getPubKeyRing()) .withPubKeyRing(proto.getPubKeyRing())
.withVersionNumber(proto.getVersionNr()) .withVersionNumber(proto.getVersionNr())
.withProtocolVersion(proto.getProtocolVersion()) .withProtocolVersion(proto.getProtocolVersion())
.withArbitratorSigner(proto.getArbitratorSigner())
.build(); .build();
} }
} }

View file

@ -46,6 +46,11 @@ public class TradeInfo implements Payload {
? "" ? ""
: trade.getTradingPeerNodeAddress().getFullAddress(); : trade.getTradingPeerNodeAddress().getFullAddress();
private static final Function<Trade, String> toArbitratorNodeAddress = (trade) ->
trade.getArbitratorNodeAddress() == null
? ""
: trade.getArbitratorNodeAddress().getFullAddress();
private static final Function<Trade, String> toRoundedVolume = (trade) -> private static final Function<Trade, String> toRoundedVolume = (trade) ->
trade.getVolume() == null trade.getVolume() == null
? "" ? ""
@ -70,6 +75,7 @@ public class TradeInfo implements Payload {
private final long amountAsLong; private final long amountAsLong;
private final String price; private final String price;
private final String volume; private final String volume;
private final String arbitratorNodeAddress;
private final String tradingPeerNodeAddress; private final String tradingPeerNodeAddress;
private final String state; private final String state;
private final String phase; private final String phase;
@ -98,6 +104,7 @@ public class TradeInfo implements Payload {
this.amountAsLong = builder.getAmountAsLong(); this.amountAsLong = builder.getAmountAsLong();
this.price = builder.getPrice(); this.price = builder.getPrice();
this.volume = builder.getVolume(); this.volume = builder.getVolume();
this.arbitratorNodeAddress = builder.getArbitratorNodeAddress();
this.tradingPeerNodeAddress = builder.getTradingPeerNodeAddress(); this.tradingPeerNodeAddress = builder.getTradingPeerNodeAddress();
this.state = builder.getState(); this.state = builder.getState();
this.phase = builder.getPhase(); this.phase = builder.getPhase();
@ -149,6 +156,7 @@ public class TradeInfo implements Payload {
.withAmountAsLong(trade.getAmountAsLong()) .withAmountAsLong(trade.getAmountAsLong())
.withPrice(toPreciseTradePrice.apply(trade)) .withPrice(toPreciseTradePrice.apply(trade))
.withVolume(toRoundedVolume.apply(trade)) .withVolume(toRoundedVolume.apply(trade))
.withArbitratorNodeAddress(toArbitratorNodeAddress.apply(trade))
.withTradingPeerNodeAddress(toPeerNodeAddress.apply(trade)) .withTradingPeerNodeAddress(toPeerNodeAddress.apply(trade))
.withState(trade.getState().name()) .withState(trade.getState().name())
.withPhase(trade.getPhase().name()) .withPhase(trade.getPhase().name())
@ -186,6 +194,7 @@ public class TradeInfo implements Payload {
.setAmountAsLong(amountAsLong) .setAmountAsLong(amountAsLong)
.setPrice(price) .setPrice(price)
.setTradeVolume(volume) .setTradeVolume(volume)
.setArbitratorNodeAddress(arbitratorNodeAddress)
.setTradingPeerNodeAddress(tradingPeerNodeAddress) .setTradingPeerNodeAddress(tradingPeerNodeAddress)
.setState(state) .setState(state)
.setPhase(phase) .setPhase(phase)
@ -220,6 +229,7 @@ public class TradeInfo implements Payload {
.withPeriodState(proto.getPeriodState()) .withPeriodState(proto.getPeriodState())
.withState(proto.getState()) .withState(proto.getState())
.withPhase(proto.getPhase()) .withPhase(proto.getPhase())
.withArbitratorNodeAddress(proto.getArbitratorNodeAddress())
.withTradingPeerNodeAddress(proto.getTradingPeerNodeAddress()) .withTradingPeerNodeAddress(proto.getTradingPeerNodeAddress())
.withIsDepositPublished(proto.getIsDepositPublished()) .withIsDepositPublished(proto.getIsDepositPublished())
.withIsDepositUnlocked(proto.getIsDepositUnlocked()) .withIsDepositUnlocked(proto.getIsDepositUnlocked())
@ -247,6 +257,7 @@ public class TradeInfo implements Payload {
", payoutTxId='" + payoutTxId + '\'' + "\n" + ", payoutTxId='" + payoutTxId + '\'' + "\n" +
", amountAsLong='" + amountAsLong + '\'' + "\n" + ", amountAsLong='" + amountAsLong + '\'' + "\n" +
", price='" + price + '\'' + "\n" + ", price='" + price + '\'' + "\n" +
", arbitratorNodeAddress='" + arbitratorNodeAddress + '\'' + "\n" +
", tradingPeerNodeAddress='" + tradingPeerNodeAddress + '\'' + "\n" + ", tradingPeerNodeAddress='" + tradingPeerNodeAddress + '\'' + "\n" +
", state='" + state + '\'' + "\n" + ", state='" + state + '\'' + "\n" +
", phase='" + phase + '\'' + "\n" + ", phase='" + phase + '\'' + "\n" +

View file

@ -61,6 +61,7 @@ public final class OfferInfoBuilder {
private String pubKeyRing; private String pubKeyRing;
private String versionNumber; private String versionNumber;
private int protocolVersion; private int protocolVersion;
private String arbitratorSigner;
public OfferInfoBuilder withId(String id) { public OfferInfoBuilder withId(String id) {
this.id = id; this.id = id;
@ -217,6 +218,11 @@ public final class OfferInfoBuilder {
return this; return this;
} }
public OfferInfoBuilder withArbitratorSigner(String arbitratorSigner) {
this.arbitratorSigner = arbitratorSigner;
return this;
}
public OfferInfo build() { public OfferInfo build() {
return new OfferInfo(this); return new OfferInfo(this);
} }

View file

@ -47,6 +47,7 @@ public final class TradeInfoV1Builder {
private long amountAsLong; private long amountAsLong;
private String price; private String price;
private String volume; private String volume;
private String arbitratorNodeAddress;
private String tradingPeerNodeAddress; private String tradingPeerNodeAddress;
private String state; private String state;
private String phase; private String phase;
@ -151,6 +152,11 @@ public final class TradeInfoV1Builder {
return this; return this;
} }
public TradeInfoV1Builder withArbitratorNodeAddress(String arbitratorNodeAddress) {
this.arbitratorNodeAddress = arbitratorNodeAddress;
return this;
}
public TradeInfoV1Builder withTradingPeerNodeAddress(String tradingPeerNodeAddress) { public TradeInfoV1Builder withTradingPeerNodeAddress(String tradingPeerNodeAddress) {
this.tradingPeerNodeAddress = tradingPeerNodeAddress; this.tradingPeerNodeAddress = tradingPeerNodeAddress;
return this; return this;

View file

@ -72,7 +72,7 @@ public class TradeEvents {
case DEPOSIT_REQUESTED: case DEPOSIT_REQUESTED:
case DEPOSITS_PUBLISHED: case DEPOSITS_PUBLISHED:
break; break;
case DEPOSIT_UNLOCKED: case DEPOSITS_UNLOCKED:
if (trade.getContract() != null && pubKeyRingProvider.get().equals(trade.getContract().getBuyerPubKeyRing())) if (trade.getContract() != null && pubKeyRingProvider.get().equals(trade.getContract().getBuyerPubKeyRing()))
msg = Res.get("account.notifications.trade.message.msg.conf", shortId); msg = Res.get("account.notifications.trade.message.msg.conf", shortId);
break; break;

View file

@ -154,14 +154,6 @@ public class CreateOfferService {
boolean isCryptoCurrency = CurrencyUtil.isCryptoCurrency(currencyCode); boolean isCryptoCurrency = CurrencyUtil.isCryptoCurrency(currencyCode);
String baseCurrencyCode = isCryptoCurrency ? currencyCode : Res.getBaseCurrencyCode(); String baseCurrencyCode = isCryptoCurrency ? currencyCode : Res.getBaseCurrencyCode();
String counterCurrencyCode = isCryptoCurrency ? Res.getBaseCurrencyCode() : currencyCode; String counterCurrencyCode = isCryptoCurrency ? Res.getBaseCurrencyCode() : currencyCode;
List<NodeAddress> acceptedArbitratorAddresses = user.getAcceptedArbitratorAddresses();
ArrayList<NodeAddress> arbitratorNodeAddresses = acceptedArbitratorAddresses != null ?
Lists.newArrayList(acceptedArbitratorAddresses) :
new ArrayList<>();
List<NodeAddress> acceptedMediatorAddresses = user.getAcceptedMediatorAddresses();
ArrayList<NodeAddress> mediatorNodeAddresses = acceptedMediatorAddresses != null ?
Lists.newArrayList(acceptedMediatorAddresses) :
new ArrayList<>();
String countryCode = PaymentAccountUtil.getCountryCode(paymentAccount); String countryCode = PaymentAccountUtil.getCountryCode(paymentAccount);
List<String> acceptedCountryCodes = PaymentAccountUtil.getAcceptedCountryCodes(paymentAccount); List<String> acceptedCountryCodes = PaymentAccountUtil.getAcceptedCountryCodes(paymentAccount);
String bankId = PaymentAccountUtil.getBankId(paymentAccount); String bankId = PaymentAccountUtil.getBankId(paymentAccount);
@ -191,10 +183,6 @@ public class CreateOfferService {
currencyCode, currencyCode,
makerFeeAsCoin); makerFeeAsCoin);
// select signing arbitrator
Arbitrator arbitrator = DisputeAgentSelection.getLeastUsedArbitrator(tradeStatisticsManager, arbitratorManager);
if (arbitrator == null) throw new RuntimeException("No arbitrators available");
OfferPayload offerPayload = new OfferPayload(offerId, OfferPayload offerPayload = new OfferPayload(offerId,
creationTime, creationTime,
makerAddress, makerAddress,
@ -230,7 +218,7 @@ public class CreateOfferService {
hashOfChallenge, hashOfChallenge,
extraDataMap, extraDataMap,
Version.TRADE_PROTOCOL_VERSION, Version.TRADE_PROTOCOL_VERSION,
arbitrator.getNodeAddress(), null,
null, null,
null); null);
Offer offer = new Offer(offerPayload); Offer offer = new Offer(offerPayload);

View file

@ -78,6 +78,7 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
// address and signature of signing arbitrator // address and signature of signing arbitrator
@Setter @Setter
@Nullable
protected NodeAddress arbitratorSigner; protected NodeAddress arbitratorSigner;
@Setter @Setter
@Nullable @Nullable
@ -192,7 +193,7 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
@Nullable String hashOfChallenge, @Nullable String hashOfChallenge,
@Nullable Map<String, String> extraDataMap, @Nullable Map<String, String> extraDataMap,
int protocolVersion, int protocolVersion,
NodeAddress arbitratorSigner, @Nullable NodeAddress arbitratorSigner,
@Nullable String arbitratorSignature, @Nullable String arbitratorSignature,
@Nullable List<String> reserveTxKeyImages) { @Nullable List<String> reserveTxKeyImages) {
this.id = id; this.id = id;
@ -297,8 +298,8 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
.setLowerClosePrice(lowerClosePrice) .setLowerClosePrice(lowerClosePrice)
.setUpperClosePrice(upperClosePrice) .setUpperClosePrice(upperClosePrice)
.setIsPrivateOffer(isPrivateOffer) .setIsPrivateOffer(isPrivateOffer)
.setProtocolVersion(protocolVersion) .setProtocolVersion(protocolVersion);
.setArbitratorSigner(arbitratorSigner.toProtoMessage()); Optional.ofNullable(arbitratorSigner).ifPresent(e -> builder.setArbitratorSigner(arbitratorSigner.toProtoMessage()));
Optional.ofNullable(offerFeePaymentTxId).ifPresent(builder::setOfferFeePaymentTxId); Optional.ofNullable(offerFeePaymentTxId).ifPresent(builder::setOfferFeePaymentTxId);
Optional.ofNullable(countryCode).ifPresent(builder::setCountryCode); Optional.ofNullable(countryCode).ifPresent(builder::setCountryCode);
Optional.ofNullable(bankId).ifPresent(builder::setBankId); Optional.ofNullable(bankId).ifPresent(builder::setBankId);
@ -356,7 +357,7 @@ public final class OfferPayload implements ProtectedStoragePayload, ExpirablePay
hashOfChallenge, hashOfChallenge,
extraDataMapMap, extraDataMapMap,
proto.getProtocolVersion(), proto.getProtocolVersion(),
NodeAddress.fromProto(proto.getArbitratorSigner()), proto.hasArbitratorSigner() ? NodeAddress.fromProto(proto.getArbitratorSigner()) : null,
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()));
} }

View file

@ -19,8 +19,6 @@ package bisq.core.offer;
import bisq.core.trade.Tradable; import bisq.core.trade.Tradable;
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;
@ -55,10 +53,6 @@ public final class OpenOffer implements Tradable {
private final Offer offer; private final Offer offer;
@Getter @Getter
private State state; private State state;
@Getter
@Setter
@Nullable
private NodeAddress backupArbitrator;
@Setter @Setter
@Getter @Getter
private boolean autoSplit; private boolean autoSplit;
@ -113,7 +107,6 @@ public final class OpenOffer implements Tradable {
private OpenOffer(Offer offer, private OpenOffer(Offer offer,
State state, State state,
@Nullable NodeAddress backupArbitrator,
long triggerPrice, long triggerPrice,
boolean autoSplit, boolean autoSplit,
@Nullable String scheduledAmount, @Nullable String scheduledAmount,
@ -123,7 +116,6 @@ public final class OpenOffer implements Tradable {
@Nullable String reserveTxKey) { @Nullable String reserveTxKey) {
this.offer = offer; this.offer = offer;
this.state = state; this.state = state;
this.backupArbitrator = backupArbitrator;
this.triggerPrice = triggerPrice; this.triggerPrice = triggerPrice;
this.autoSplit = autoSplit; this.autoSplit = autoSplit;
this.scheduledTxHashes = scheduledTxHashes; this.scheduledTxHashes = scheduledTxHashes;
@ -144,7 +136,6 @@ public final class OpenOffer implements Tradable {
.setAutoSplit(autoSplit); .setAutoSplit(autoSplit);
Optional.ofNullable(scheduledAmount).ifPresent(e -> builder.setScheduledAmount(scheduledAmount)); Optional.ofNullable(scheduledAmount).ifPresent(e -> builder.setScheduledAmount(scheduledAmount));
Optional.ofNullable(backupArbitrator).ifPresent(nodeAddress -> builder.setBackupArbitrator(nodeAddress.toProtoMessage()));
Optional.ofNullable(scheduledTxHashes).ifPresent(e -> builder.addAllScheduledTxHashes(scheduledTxHashes)); Optional.ofNullable(scheduledTxHashes).ifPresent(e -> builder.addAllScheduledTxHashes(scheduledTxHashes));
Optional.ofNullable(reserveTxHash).ifPresent(e -> builder.setReserveTxHash(reserveTxHash)); Optional.ofNullable(reserveTxHash).ifPresent(e -> builder.setReserveTxHash(reserveTxHash));
Optional.ofNullable(reserveTxHex).ifPresent(e -> builder.setReserveTxHex(reserveTxHex)); Optional.ofNullable(reserveTxHex).ifPresent(e -> builder.setReserveTxHex(reserveTxHex));
@ -156,7 +147,6 @@ public final class OpenOffer implements Tradable {
public static Tradable fromProto(protobuf.OpenOffer proto) { 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.hasBackupArbitrator() ? NodeAddress.fromProto(proto.getBackupArbitrator()) : null,
proto.getTriggerPrice(), proto.getTriggerPrice(),
proto.getAutoSplit(), proto.getAutoSplit(),
proto.getScheduledAmount(), proto.getScheduledAmount(),
@ -227,7 +217,6 @@ public final class OpenOffer implements Tradable {
return "OpenOffer{" + return "OpenOffer{" +
",\n offer=" + offer + ",\n offer=" + offer +
",\n state=" + state + ",\n state=" + state +
",\n arbitratorNodeAddress=" + backupArbitrator +
",\n triggerPrice=" + triggerPrice + ",\n triggerPrice=" + triggerPrice +
"\n}"; "\n}";
} }

View file

@ -24,7 +24,6 @@ import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.btc.wallet.XmrWalletService; import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.exceptions.TradePriceOutOfToleranceException; import bisq.core.exceptions.TradePriceOutOfToleranceException;
import bisq.core.filter.FilterManager; import bisq.core.filter.FilterManager;
import bisq.core.offer.availability.DisputeAgentSelection;
import bisq.core.offer.messages.OfferAvailabilityRequest; import bisq.core.offer.messages.OfferAvailabilityRequest;
import bisq.core.offer.messages.OfferAvailabilityResponse; import bisq.core.offer.messages.OfferAvailabilityResponse;
import bisq.core.offer.messages.SignOfferRequest; import bisq.core.offer.messages.SignOfferRequest;
@ -703,6 +702,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
// handle result // handle result
resultHandler.handleResult(null); resultHandler.handleResult(null);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace();
errorMessageHandler.handleErrorMessage(e.getMessage()); errorMessageHandler.handleErrorMessage(e.getMessage());
} }
} }
@ -760,7 +760,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
// set reserve tx on open offer // set reserve tx on open offer
openOffer.setReserveTxHash(model.getReserveTx().getHash()); openOffer.setReserveTxHash(model.getReserveTx().getHash());
openOffer.setReserveTxHex(model.getReserveTx().getHash()); openOffer.setReserveTxHex(model.getReserveTx().getFullHex());
openOffer.setReserveTxKey(model.getReserveTx().getKey()); openOffer.setReserveTxKey(model.getReserveTx().getKey());
// set offer state // set offer state
@ -948,7 +948,6 @@ 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 backupArbitratorNodeAddress = null;
if (openOfferOptional.isPresent()) { if (openOfferOptional.isPresent()) {
OpenOffer openOffer = openOfferOptional.get(); OpenOffer openOffer = openOfferOptional.get();
if (!apiUserDeniedByOffer(request)) { if (!apiUserDeniedByOffer(request)) {
@ -957,12 +956,7 @@ 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()))) {
// set backup arbitrator if signer is not available // maker signs taker's request
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 = JsonUtil.objectToJson(request.getTradeRequest()); String tradeRequestAsJson = JsonUtil.objectToJson(request.getTradeRequest());
makerSignature = Sig.sign(keyRing.getSignatureKeyPair().getPrivate(), tradeRequestAsJson); makerSignature = Sig.sign(keyRing.getSignatureKeyPair().getPrivate(), tradeRequestAsJson);
@ -1005,8 +999,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
OfferAvailabilityResponse offerAvailabilityResponse = new OfferAvailabilityResponse(request.offerId, OfferAvailabilityResponse offerAvailabilityResponse = new OfferAvailabilityResponse(request.offerId,
availabilityResult, availabilityResult,
makerSignature, makerSignature);
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);

View file

@ -53,7 +53,7 @@ public class DisputeAgentSelection {
public static <T extends DisputeAgent> T getLeastUsedArbitrator(TradeStatisticsManager tradeStatisticsManager, public static <T extends DisputeAgent> T getLeastUsedArbitrator(TradeStatisticsManager tradeStatisticsManager,
DisputeAgentManager<T> disputeAgentManager, DisputeAgentManager<T> disputeAgentManager,
NodeAddress excludedArbitrator) { Set<NodeAddress> excludedArbitrator) {
return getLeastUsedDisputeAgent(tradeStatisticsManager, return getLeastUsedDisputeAgent(tradeStatisticsManager,
disputeAgentManager, disputeAgentManager,
excludedArbitrator); excludedArbitrator);
@ -61,7 +61,7 @@ public class DisputeAgentSelection {
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) { Set<NodeAddress> excludedDisputeAgents) {
// 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));
@ -81,7 +81,7 @@ 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 (excludedDisputeAgents != null) disputeAgents.removeAll(excludedDisputeAgents.stream().map(NodeAddress::getFullAddress).collect(Collectors.toList()));
if (disputeAgents.isEmpty()) return null; if (disputeAgents.isEmpty()) return null;
String result = getLeastUsedDisputeAgent(lastAddressesUsedInTrades, disputeAgents); String result = getLeastUsedDisputeAgent(lastAddressesUsedInTrades, disputeAgents);

View file

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

View file

@ -62,7 +62,6 @@ public class ProcessOfferAvailabilityResponse extends Task<OfferAvailabilityMode
offer.setState(Offer.State.AVAILABLE); offer.setState(Offer.State.AVAILABLE);
model.setMakerSignature(offerAvailabilityResponse.getMakerSignature()); model.setMakerSignature(offerAvailabilityResponse.getMakerSignature());
model.setBackupArbitrator(offerAvailabilityResponse.getBackupArbitrator());
checkNotNull(model.getMakerSignature()); checkNotNull(model.getMakerSignature());
complete(); complete();

View file

@ -20,7 +20,6 @@ package bisq.core.offer.messages;
import bisq.core.offer.AvailabilityResult; import bisq.core.offer.AvailabilityResult;
import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.SupportedCapabilitiesMessage; import bisq.network.p2p.SupportedCapabilitiesMessage;
import bisq.common.app.Capabilities; import bisq.common.app.Capabilities;
@ -46,19 +45,16 @@ public final class OfferAvailabilityResponse extends OfferMessage implements Sup
@Nullable @Nullable
private final String makerSignature; private final String makerSignature;
private final NodeAddress backupArbitrator;
public OfferAvailabilityResponse(String offerId, public OfferAvailabilityResponse(String offerId,
AvailabilityResult availabilityResult, AvailabilityResult availabilityResult,
String makerSignature, String makerSignature) {
NodeAddress backupArbitrator) {
this(offerId, this(offerId,
availabilityResult, availabilityResult,
Capabilities.app, Capabilities.app,
Version.getP2PMessageVersion(), Version.getP2PMessageVersion(),
UUID.randomUUID().toString(), UUID.randomUUID().toString(),
makerSignature, makerSignature);
backupArbitrator);
} }
@ -71,13 +67,11 @@ public final class OfferAvailabilityResponse extends OfferMessage implements Sup
@Nullable Capabilities supportedCapabilities, @Nullable Capabilities supportedCapabilities,
String messageVersion, String messageVersion,
@Nullable String uid, @Nullable String uid,
String makerSignature, String makerSignature) {
NodeAddress arbitratorNodeAddress) {
super(messageVersion, offerId, uid); super(messageVersion, offerId, uid);
this.availabilityResult = availabilityResult; this.availabilityResult = availabilityResult;
this.supportedCapabilities = supportedCapabilities; this.supportedCapabilities = supportedCapabilities;
this.makerSignature = makerSignature; this.makerSignature = makerSignature;
this.backupArbitrator = arbitratorNodeAddress;
} }
@Override @Override
@ -89,7 +83,6 @@ public final class OfferAvailabilityResponse extends OfferMessage implements Sup
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)
@ -102,7 +95,6 @@ public final class OfferAvailabilityResponse extends OfferMessage implements Sup
Capabilities.fromIntList(proto.getSupportedCapabilitiesList()), Capabilities.fromIntList(proto.getSupportedCapabilitiesList()),
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());
proto.hasBackupArbitrator() ? NodeAddress.fromProto(proto.getBackupArbitrator()) : null);
} }
} }

View file

@ -17,13 +17,15 @@
package bisq.core.offer.placeoffer.tasks; package bisq.core.offer.placeoffer.tasks;
import static com.google.common.base.Preconditions.checkNotNull;
import bisq.common.app.Version; import bisq.common.app.Version;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler;
import bisq.common.taskrunner.Task; import bisq.common.taskrunner.Task;
import bisq.common.taskrunner.TaskRunner; import bisq.common.taskrunner.TaskRunner;
import bisq.core.btc.model.XmrAddressEntry; import bisq.core.btc.model.XmrAddressEntry;
import bisq.core.offer.Offer; import bisq.core.offer.Offer;
import bisq.core.offer.availability.DisputeAgentSelection;
import bisq.core.offer.messages.SignOfferRequest; import bisq.core.offer.messages.SignOfferRequest;
import bisq.core.offer.placeoffer.PlaceOfferModel; import bisq.core.offer.placeoffer.PlaceOfferModel;
import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator; import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
@ -34,6 +36,8 @@ import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.P2PService; import bisq.network.p2p.P2PService;
import bisq.network.p2p.SendDirectMessageListener; import bisq.network.p2p.SendDirectMessageListener;
import java.util.Date; import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -50,9 +54,7 @@ public class MakerSendsSignOfferRequest extends Task<PlaceOfferModel> {
@Override @Override
protected void run() { protected void run() {
Offer offer = model.getOffer(); Offer offer = model.getOffer();
try { try {
runInterceptHook(); runInterceptHook();
@ -73,44 +75,13 @@ public class MakerSendsSignOfferRequest extends Task<PlaceOfferModel> {
offer.getOfferPayload().getReserveTxKeyImages(), offer.getOfferPayload().getReserveTxKeyImages(),
returnAddress); returnAddress);
// get signing arbitrator // send request to least used arbitrators until success
Arbitrator arbitrator = checkNotNull(model.getUser().getAcceptedArbitratorByAddress(offer.getOfferPayload().getArbitratorSigner()), "user.getAcceptedArbitratorByAddress(arbitratorNodeAddress) must not be null"); sendSignOfferRequests(request, () -> {
// complete on successful ack message
DecryptedDirectMessageListener ackListener = new DecryptedDirectMessageListener() {
@Override
public void onDirectMessage(DecryptedMessageWithPubKey decryptedMessageWithPubKey, NodeAddress sender) {
if (!(decryptedMessageWithPubKey.getNetworkEnvelope() instanceof AckMessage)) return;
if (!sender.equals(arbitrator.getNodeAddress())) return;
AckMessage ackMessage = (AckMessage) decryptedMessageWithPubKey.getNetworkEnvelope();
if (!ackMessage.getSourceMsgClassName().equals(SignOfferRequest.class.getSimpleName())) return;
if (!ackMessage.getSourceUid().equals(request.getUid())) return;
if (ackMessage.isSuccess()) {
offer.setState(Offer.State.OFFER_FEE_RESERVED);
model.getP2PService().removeDecryptedDirectMessageListener(this);
complete(); complete();
} else { }, (errorMessage) -> {
if (!failed) { log.warn("Error signing offer: " + errorMessage);
failed = true; appendToErrorMessage("Error signing offer: " + errorMessage);
failed(ackMessage.getErrorMessage()); // TODO: (woodser): only fail once? build into task? failed(errorMessage);
}
}
}
};
model.getP2PService().addDecryptedDirectMessageListener(ackListener);
// send request
model.getP2PService().sendEncryptedDirectMessage(arbitrator.getNodeAddress(), arbitrator.getPubKeyRing(), request, new SendDirectMessageListener() {
@Override
public void onArrived() {
log.info("{} arrived: arbitrator={}; offerId={}; uid={}", request.getClass().getSimpleName(), arbitrator.getNodeAddress(), offer.getId());
}
@Override
public void onFault(String errorMessage) {
log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), arbitrator.getNodeAddress(), offer.getId(), errorMessage);
appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage);
failed();
}
}); });
} catch (Throwable t) { } catch (Throwable t) {
offer.setErrorMessage("An error occurred.\n" + offer.setErrorMessage("An error occurred.\n" +
@ -119,4 +90,77 @@ public class MakerSendsSignOfferRequest extends Task<PlaceOfferModel> {
failed(t); failed(t);
} }
} }
private void sendSignOfferRequests(SignOfferRequest request, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
Arbitrator leastUsedArbitrator = DisputeAgentSelection.getLeastUsedArbitrator(model.getTradeStatisticsManager(), model.getArbitratorManager());
sendSignOfferRequests(request, leastUsedArbitrator.getNodeAddress(), new HashSet<NodeAddress>(), resultHandler, errorMessageHandler);
}
private void sendSignOfferRequests(SignOfferRequest request, NodeAddress arbitratorNodeAddress, Set<NodeAddress> excludedArbitrators, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
// complete on successful ack message
DecryptedDirectMessageListener ackListener = new DecryptedDirectMessageListener() {
@Override
public void onDirectMessage(DecryptedMessageWithPubKey decryptedMessageWithPubKey, NodeAddress sender) {
if (!(decryptedMessageWithPubKey.getNetworkEnvelope() instanceof AckMessage)) return;
if (!sender.equals(arbitratorNodeAddress)) return;
AckMessage ackMessage = (AckMessage) decryptedMessageWithPubKey.getNetworkEnvelope();
if (!ackMessage.getSourceMsgClassName().equals(SignOfferRequest.class.getSimpleName())) return;
if (!ackMessage.getSourceUid().equals(request.getUid())) return;
if (ackMessage.isSuccess()) {
model.getP2PService().removeDecryptedDirectMessageListener(this);
model.getOffer().getOfferPayload().setArbitratorSigner(arbitratorNodeAddress);
model.getOffer().setState(Offer.State.OFFER_FEE_RESERVED);
resultHandler.handleResult();
} else {
log.warn("Arbitrator nacked request: {}", errorMessage);
handleArbitratorFailure(request, arbitratorNodeAddress, excludedArbitrators, resultHandler, errorMessageHandler);
}
}
};
model.getP2PService().addDecryptedDirectMessageListener(ackListener);
// send sign offer request
sendSignOfferRequest(request, arbitratorNodeAddress, new SendDirectMessageListener() {
@Override
public void onArrived() {
log.info("{} arrived at arbitrator: offerId={}", request.getClass().getSimpleName(), model.getOffer().getId());
}
// if unavailable, try alternative arbitrator
@Override
public void onFault(String errorMessage) {
log.warn("Arbitrator unavailable: {}", errorMessage);
handleArbitratorFailure(request, arbitratorNodeAddress, excludedArbitrators, resultHandler, errorMessageHandler);
}
});
}
private void sendSignOfferRequest(SignOfferRequest request, NodeAddress arbitratorNodeAddress, SendDirectMessageListener listener) {
// get registered arbitrator
Arbitrator arbitrator = model.getUser().getAcceptedArbitratorByAddress(arbitratorNodeAddress);
if (arbitrator == null) throw new RuntimeException("Node address " + arbitratorNodeAddress + " is not a registered arbitrator");
request.getOfferPayload().setArbitratorSigner(arbitratorNodeAddress);
// send request to arbitrator
log.info("Sending {} with offerId {} and uid {} to arbitrator {}", request.getClass().getSimpleName(), request.getOfferId(), request.getUid(), arbitratorNodeAddress);
model.getP2PService().sendEncryptedDirectMessage(
arbitratorNodeAddress,
arbitrator.getPubKeyRing(),
request,
listener
);
}
private void handleArbitratorFailure(SignOfferRequest request, NodeAddress arbitratorNodeAddress, Set<NodeAddress> excludedArbitrators, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
excludedArbitrators.add(arbitratorNodeAddress);
Arbitrator altArbitrator = DisputeAgentSelection.getLeastUsedArbitrator(model.getTradeStatisticsManager(), model.getArbitratorManager(), excludedArbitrators);
if (altArbitrator == null) {
errorMessageHandler.handleErrorMessage("Offer could not be signed by any arbitrator");
return;
}
log.info("Using alternative arbitrator {}", altArbitrator.getNodeAddress());
sendSignOfferRequests(request, altArbitrator.getNodeAddress(), excludedArbitrators, resultHandler, errorMessageHandler);
}
} }

View file

@ -47,7 +47,6 @@ import java.math.BigInteger;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@ -94,13 +93,12 @@ public abstract class DisputeAgentManager<T extends DisputeAgent> {
public DisputeAgentManager(KeyRing keyRing, public DisputeAgentManager(KeyRing keyRing,
DisputeAgentService<T> disputeAgentService, DisputeAgentService<T> disputeAgentService,
User user, User user,
FilterManager filterManager, FilterManager filterManager) {
boolean useDevPrivilegeKeys) {
this.keyRing = keyRing; this.keyRing = keyRing;
this.disputeAgentService = disputeAgentService; this.disputeAgentService = disputeAgentService;
this.user = user; this.user = user;
this.filterManager = filterManager; this.filterManager = filterManager;
publicKeys = useDevPrivilegeKeys ? Collections.singletonList(DevEnv.DEV_PRIVILEGE_PUB_KEY) : getPubKeyList(); publicKeys = getPubKeyList();
} }
@ -245,6 +243,8 @@ public abstract class DisputeAgentManager<T extends DisputeAgent> {
resultHandler.handleResult(); resultHandler.handleResult();
}, },
errorMessageHandler); errorMessageHandler);
} else {
errorMessageHandler.handleErrorMessage("User is not registered dispute agent");
} }
} }

View file

@ -27,7 +27,6 @@ import bisq.common.crypto.KeyRing;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import javax.inject.Named;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -41,16 +40,18 @@ public class ArbitratorManager extends DisputeAgentManager<Arbitrator> {
public ArbitratorManager(KeyRing keyRing, public ArbitratorManager(KeyRing keyRing,
ArbitratorService arbitratorService, ArbitratorService arbitratorService,
User user, User user,
FilterManager filterManager, FilterManager filterManager) {
@Named(Config.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) { super(keyRing, arbitratorService, user, filterManager);
super(keyRing, arbitratorService, user, filterManager, useDevPrivilegeKeys);
} }
@Override @Override
protected List<String> getPubKeyList() { protected List<String> getPubKeyList() {
switch (Config.baseCurrencyNetwork()) { switch (Config.baseCurrencyNetwork()) {
case XMR_LOCAL: case XMR_LOCAL:
throw new RuntimeException("No arbitrator pub key list for local XMR testnet. Set useDevPrivilegeKeys=true"); return List.of(
"027a381b5333a56e1cc3d90d3a7d07f26509adf7029ed06fc997c656621f8da1ee",
"024baabdba90e7cc0dc4626ef73ea9d722ea7085d1104491da8c76f28187513492",
"026eeec3c119dd6d537249d74e5752a642dd2c3cc5b6a9b44588eb58344f29b519");
case XMR_STAGENET: case XMR_STAGENET:
return List.of( return List.of(
"03bb559ce207a4deb51d4c705076c95b85ad8581d35936b2a422dcb504eaf7cdb0", "03bb559ce207a4deb51d4c705076c95b85ad8581d35936b2a422dcb504eaf7cdb0",

View file

@ -23,11 +23,9 @@ import bisq.core.user.User;
import bisq.network.p2p.storage.payload.ProtectedStorageEntry; import bisq.network.p2p.storage.payload.ProtectedStorageEntry;
import bisq.common.config.Config;
import bisq.common.crypto.KeyRing; import bisq.common.crypto.KeyRing;
import javax.inject.Singleton; import javax.inject.Singleton;
import javax.inject.Named;
import javax.inject.Inject; import javax.inject.Inject;
@ -40,9 +38,8 @@ public class MediatorManager extends DisputeAgentManager<Mediator> {
public MediatorManager(KeyRing keyRing, public MediatorManager(KeyRing keyRing,
MediatorService mediatorService, MediatorService mediatorService,
User user, User user,
FilterManager filterManager, FilterManager filterManager) {
@Named(Config.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) { super(keyRing, mediatorService, user, filterManager);
super(keyRing, mediatorService, user, filterManager, useDevPrivilegeKeys);
} }
@Override @Override

View file

@ -23,12 +23,10 @@ import bisq.core.user.User;
import bisq.network.p2p.storage.payload.ProtectedStorageEntry; import bisq.network.p2p.storage.payload.ProtectedStorageEntry;
import bisq.common.config.Config;
import bisq.common.crypto.KeyRing; import bisq.common.crypto.KeyRing;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import javax.inject.Named;
import java.util.List; import java.util.List;
@ -42,9 +40,8 @@ public class RefundAgentManager extends DisputeAgentManager<RefundAgent> {
public RefundAgentManager(KeyRing keyRing, public RefundAgentManager(KeyRing keyRing,
RefundAgentService refundAgentService, RefundAgentService refundAgentService,
User user, User user,
FilterManager filterManager, FilterManager filterManager) {
@Named(Config.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) { super(keyRing, refundAgentService, user, filterManager);
super(keyRing, refundAgentService, user, filterManager, useDevPrivilegeKeys);
} }
@Override @Override

View file

@ -127,7 +127,7 @@ public abstract class Trade implements Tradable, Model {
// deposit confirmed (TODO) // deposit confirmed (TODO)
// deposit unlocked // deposit unlocked
DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN(Phase.DEPOSIT_UNLOCKED), DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN(Phase.DEPOSITS_UNLOCKED),
// payment sent // payment sent
BUYER_CONFIRMED_IN_UI_PAYMENT_SENT(Phase.PAYMENT_SENT), BUYER_CONFIRMED_IN_UI_PAYMENT_SENT(Phase.PAYMENT_SENT),
@ -191,7 +191,7 @@ public abstract class Trade implements Tradable, Model {
INIT, INIT,
DEPOSIT_REQUESTED, // TODO (woodser): remove unused phases DEPOSIT_REQUESTED, // TODO (woodser): remove unused phases
DEPOSITS_PUBLISHED, DEPOSITS_PUBLISHED,
DEPOSIT_UNLOCKED, // TODO (woodser): rename to or add DEPOSIT_UNLOCKED DEPOSITS_UNLOCKED,
PAYMENT_SENT, PAYMENT_SENT,
PAYMENT_RECEIVED, PAYMENT_RECEIVED,
PAYOUT_PUBLISHED, PAYOUT_PUBLISHED,
@ -1291,7 +1291,7 @@ public abstract class Trade implements Tradable, Model {
} }
public boolean isDepositUnlocked() { public boolean isDepositUnlocked() {
return getState().getPhase().ordinal() >= Phase.DEPOSIT_UNLOCKED.ordinal(); return getState().getPhase().ordinal() >= Phase.DEPOSITS_UNLOCKED.ordinal();
} }
public boolean isPaymentSent() { public boolean isPaymentSent() {

View file

@ -500,9 +500,10 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
Offer offer = openOffer.getOffer(); Offer offer = openOffer.getOffer();
// verify request is from signer or backup arbitrator // verify request is from arbitrator
if (!sender.equals(offer.getOfferPayload().getArbitratorSigner()) && !sender.equals(openOffer.getBackupArbitrator())) { // TODO (woodser): get backup arbitrator from maker-signed InitTradeRequest and remove from OpenOffer Arbitrator arbitrator = user.getAcceptedArbitratorByAddress(sender);
log.warn("Ignoring InitTradeRequest from {} with tradeId {} because request must be from signer or backup arbitrator", sender, request.getTradeId()); if (arbitrator == null) {
log.warn("Ignoring InitTradeRequest from {} with tradeId {} because request is not from accepted arbitrator", sender, request.getTradeId());
return; return;
} }
@ -762,7 +763,6 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
trade.getProcessModel().setTradeMessage(model.getTradeRequest()); trade.getProcessModel().setTradeMessage(model.getTradeRequest());
trade.getProcessModel().setMakerSignature(model.getMakerSignature()); trade.getProcessModel().setMakerSignature(model.getMakerSignature());
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

@ -30,14 +30,13 @@ import java.util.Optional;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Value; import lombok.Value;
import javax.annotation.Nullable;
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@Value @Value
public final class SignContractResponse extends TradeMessage implements DirectMessage { public final class SignContractResponse extends TradeMessage implements DirectMessage {
private final NodeAddress senderNodeAddress; private final NodeAddress senderNodeAddress;
private final PubKeyRing pubKeyRing; private final PubKeyRing pubKeyRing;
private final long currentDate; private final long currentDate;
private final String contractAsJson;
private final String contractSignature; private final String contractSignature;
public SignContractResponse(String tradeId, public SignContractResponse(String tradeId,
@ -46,11 +45,13 @@ public final class SignContractResponse extends TradeMessage implements DirectMe
String uid, String uid,
String messageVersion, String messageVersion,
long currentDate, long currentDate,
String contractAsJson,
String contractSignature) { String contractSignature) {
super(messageVersion, tradeId, uid); super(messageVersion, tradeId, uid);
this.senderNodeAddress = senderNodeAddress; this.senderNodeAddress = senderNodeAddress;
this.pubKeyRing = pubKeyRing; this.pubKeyRing = pubKeyRing;
this.currentDate = currentDate; this.currentDate = currentDate;
this.contractAsJson = contractAsJson;
this.contractSignature = contractSignature; this.contractSignature = contractSignature;
} }
@ -67,6 +68,7 @@ public final class SignContractResponse extends TradeMessage implements DirectMe
.setPubKeyRing(pubKeyRing.toProtoMessage()) .setPubKeyRing(pubKeyRing.toProtoMessage())
.setUid(uid); .setUid(uid);
Optional.ofNullable(contractAsJson).ifPresent(e -> builder.setContractAsJson(contractAsJson));
Optional.ofNullable(contractSignature).ifPresent(e -> builder.setContractSignature(contractSignature)); Optional.ofNullable(contractSignature).ifPresent(e -> builder.setContractSignature(contractSignature));
builder.setCurrentDate(currentDate); builder.setCurrentDate(currentDate);
@ -83,6 +85,7 @@ public final class SignContractResponse extends TradeMessage implements DirectMe
proto.getUid(), proto.getUid(),
messageVersion, messageVersion,
proto.getCurrentDate(), proto.getCurrentDate(),
ProtoUtil.stringOrNullFromProto(proto.getContractAsJson()),
ProtoUtil.stringOrNullFromProto(proto.getContractSignature())); ProtoUtil.stringOrNullFromProto(proto.getContractSignature()));
} }
@ -92,6 +95,7 @@ public final class SignContractResponse extends TradeMessage implements DirectMe
"\n senderNodeAddress=" + senderNodeAddress + "\n senderNodeAddress=" + senderNodeAddress +
",\n pubKeyRing=" + pubKeyRing + ",\n pubKeyRing=" + pubKeyRing +
",\n currentDate=" + currentDate + ",\n currentDate=" + currentDate +
",\n contractAsJson='" + contractAsJson +
",\n contractSignature='" + contractSignature + ",\n contractSignature='" + contractSignature +
"\n} " + super.toString(); "\n} " + super.toString();
} }

View file

@ -8,7 +8,7 @@ import bisq.core.trade.messages.InitTradeRequest;
import bisq.core.trade.messages.PaymentAccountPayloadRequest; import bisq.core.trade.messages.PaymentAccountPayloadRequest;
import bisq.core.trade.messages.SignContractResponse; import bisq.core.trade.messages.SignContractResponse;
import bisq.core.trade.protocol.tasks.ApplyFilter; import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.ArbitratorSendsInitTradeAndMultisigRequests; import bisq.core.trade.protocol.tasks.ArbitratorSendsInitTradeOrMultisigRequests;
import bisq.core.trade.protocol.tasks.ArbitratorProcessesDepositRequest; import bisq.core.trade.protocol.tasks.ArbitratorProcessesDepositRequest;
import bisq.core.trade.protocol.tasks.ArbitratorProcessesReserveTx; import bisq.core.trade.protocol.tasks.ArbitratorProcessesReserveTx;
import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest; import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest;
@ -42,7 +42,7 @@ public class ArbitratorProtocol extends DisputeProtocol {
ApplyFilter.class, ApplyFilter.class,
ProcessInitTradeRequest.class, ProcessInitTradeRequest.class,
ArbitratorProcessesReserveTx.class, ArbitratorProcessesReserveTx.class,
ArbitratorSendsInitTradeAndMultisigRequests.class) ArbitratorSendsInitTradeOrMultisigRequests.class)
.using(new TradeTaskRunner(trade, .using(new TradeTaskRunner(trade,
() -> { () -> {
startTimeout(TRADE_TIMEOUT); startTimeout(TRADE_TIMEOUT);

View file

@ -26,9 +26,8 @@ import bisq.core.trade.messages.PaymentAccountPayloadRequest;
import bisq.core.trade.messages.PaymentReceivedMessage; import bisq.core.trade.messages.PaymentReceivedMessage;
import bisq.core.trade.messages.SignContractRequest; import bisq.core.trade.messages.SignContractRequest;
import bisq.core.trade.messages.SignContractResponse; import bisq.core.trade.messages.SignContractResponse;
import bisq.core.trade.protocol.tasks.MakerSendsInitTradeRequestIfUnreserved; import bisq.core.trade.protocol.tasks.MakerSendsInitTradeRequest;
import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest; import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.network.p2p.NodeAddress; import bisq.network.p2p.NodeAddress;
import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler; import bisq.common.handlers.ResultHandler;
@ -65,7 +64,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) MakerSendsInitTradeRequest.class)
.using(new TradeTaskRunner(trade, .using(new TradeTaskRunner(trade,
() -> { () -> {
startTimeout(TRADE_TIMEOUT); startTimeout(TRADE_TIMEOUT);

View file

@ -90,7 +90,7 @@ public abstract class BuyerProtocol extends DisputeProtocol {
latchTrade(); latchTrade();
this.errorMessageHandler = errorMessageHandler; this.errorMessageHandler = errorMessageHandler;
BuyerEvent event = BuyerEvent.PAYMENT_SENT; BuyerEvent event = BuyerEvent.PAYMENT_SENT;
expect(phase(Trade.Phase.DEPOSIT_UNLOCKED) expect(phase(Trade.Phase.DEPOSITS_UNLOCKED)
.with(event) .with(event)
.preCondition(trade.confirmPermitted())) .preCondition(trade.confirmPermitted()))
.setup(tasks(ApplyFilter.class, .setup(tasks(ApplyFilter.class,

View file

@ -58,7 +58,7 @@ public abstract class DisputeProtocol extends TradeProtocol {
// Trader has not yet received the peer's signature but has clicked the accept button. // Trader has not yet received the peer's signature but has clicked the accept button.
public void onAcceptMediationResult(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { public void onAcceptMediationResult(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
DisputeEvent event = DisputeEvent.MEDIATION_RESULT_ACCEPTED; DisputeEvent event = DisputeEvent.MEDIATION_RESULT_ACCEPTED;
expect(anyPhase(Trade.Phase.DEPOSIT_UNLOCKED, expect(anyPhase(Trade.Phase.DEPOSITS_UNLOCKED,
Trade.Phase.PAYMENT_SENT, Trade.Phase.PAYMENT_SENT,
Trade.Phase.PAYMENT_RECEIVED) Trade.Phase.PAYMENT_RECEIVED)
.with(event) .with(event)
@ -85,7 +85,7 @@ public abstract class DisputeProtocol extends TradeProtocol {
// Trader has already received the peer's signature and has clicked the accept button as well. // Trader has already received the peer's signature and has clicked the accept button as well.
public void onFinalizeMediationResultPayout(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) { public void onFinalizeMediationResultPayout(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
DisputeEvent event = DisputeEvent.MEDIATION_RESULT_ACCEPTED; DisputeEvent event = DisputeEvent.MEDIATION_RESULT_ACCEPTED;
expect(anyPhase(Trade.Phase.DEPOSIT_UNLOCKED, expect(anyPhase(Trade.Phase.DEPOSITS_UNLOCKED,
Trade.Phase.PAYMENT_SENT, Trade.Phase.PAYMENT_SENT,
Trade.Phase.PAYMENT_RECEIVED) Trade.Phase.PAYMENT_RECEIVED)
.with(event) .with(event)
@ -113,7 +113,7 @@ public abstract class DisputeProtocol extends TradeProtocol {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
protected void handle(MediatedPayoutTxSignatureMessage message, NodeAddress peer) { protected void handle(MediatedPayoutTxSignatureMessage message, NodeAddress peer) {
expect(anyPhase(Trade.Phase.DEPOSIT_UNLOCKED, expect(anyPhase(Trade.Phase.DEPOSITS_UNLOCKED,
Trade.Phase.PAYMENT_SENT, Trade.Phase.PAYMENT_SENT,
Trade.Phase.PAYMENT_RECEIVED) Trade.Phase.PAYMENT_RECEIVED)
.with(message) .with(message)
@ -123,7 +123,7 @@ public abstract class DisputeProtocol extends TradeProtocol {
} }
protected void handle(MediatedPayoutTxPublishedMessage message, NodeAddress peer) { protected void handle(MediatedPayoutTxPublishedMessage message, NodeAddress peer) {
expect(anyPhase(Trade.Phase.DEPOSIT_UNLOCKED, expect(anyPhase(Trade.Phase.DEPOSITS_UNLOCKED,
Trade.Phase.PAYMENT_SENT, Trade.Phase.PAYMENT_SENT,
Trade.Phase.PAYMENT_RECEIVED) Trade.Phase.PAYMENT_RECEIVED)
.with(message) .with(message)

View file

@ -160,9 +160,6 @@ public class ProcessModel implements Model, PersistablePayload {
@Getter @Getter
@Setter @Setter
private String makerSignature; private String makerSignature;
@Getter
@Setter
private NodeAddress backupArbitrator;
@Nullable @Nullable
@Getter @Getter
@Setter @Setter
@ -231,7 +228,6 @@ public class ProcessModel implements Model, PersistablePayload {
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(backupArbitrator).ifPresent(e -> builder.setBackupArbitrator(backupArbitrator.toProtoMessage()));
Optional.ofNullable(multisigAddress).ifPresent(e -> builder.setMultisigAddress(multisigAddress)); Optional.ofNullable(multisigAddress).ifPresent(e -> builder.setMultisigAddress(multisigAddress));
return builder.build(); return builder.build();
} }
@ -260,7 +256,6 @@ 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.setBackupArbitrator(proto.hasBackupArbitrator() ? NodeAddress.fromProto(proto.getBackupArbitrator()) : null);
processModel.setMultisigAddress(ProtoUtil.stringOrNullFromProto(proto.getMultisigAddress())); processModel.setMultisigAddress(ProtoUtil.stringOrNullFromProto(proto.getMultisigAddress()));
String paymentStartedMessageStateString = ProtoUtil.stringOrNullFromProto(proto.getPaymentStartedMessageState()); String paymentStartedMessageStateString = ProtoUtil.stringOrNullFromProto(proto.getPaymentStartedMessageState());

View file

@ -28,7 +28,7 @@ import bisq.core.trade.messages.InitMultisigRequest;
import bisq.core.trade.messages.InitTradeRequest; import bisq.core.trade.messages.InitTradeRequest;
import bisq.core.trade.messages.PaymentAccountPayloadRequest; import bisq.core.trade.messages.PaymentAccountPayloadRequest;
import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.MakerSendsInitTradeRequestIfUnreserved; import bisq.core.trade.protocol.tasks.MakerSendsInitTradeRequest;
import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest; import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest;
import bisq.network.p2p.NodeAddress; import bisq.network.p2p.NodeAddress;
import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ErrorMessageHandler;
@ -66,7 +66,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) MakerSendsInitTradeRequest.class)
.using(new TradeTaskRunner(trade, .using(new TradeTaskRunner(trade,
() -> { () -> {
startTimeout(TRADE_TIMEOUT); startTimeout(TRADE_TIMEOUT);
@ -123,7 +123,6 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
@Override @Override
protected void onTradeMessage(TradeMessage message, NodeAddress peer) { protected void onTradeMessage(TradeMessage message, NodeAddress peer) {
super.onTradeMessage(message, peer); super.onTradeMessage(message, peer);
log.info("Received {} from {} with tradeId {} and uid {}", message.getClass().getSimpleName(), peer, message.getTradeId(), message.getUid());
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////

View file

@ -139,6 +139,5 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
@Override @Override
protected void onTradeMessage(TradeMessage message, NodeAddress peer) { protected void onTradeMessage(TradeMessage message, NodeAddress peer) {
super.onTradeMessage(message, peer); super.onTradeMessage(message, peer);
log.info("Received {} from {} with tradeId {} and uid {}", message.getClass().getSimpleName(), peer, message.getTradeId(), message.getUid());
} }
} }

View file

@ -86,7 +86,7 @@ public abstract class SellerProtocol extends DisputeProtocol {
return; return;
} }
latchTrade(); latchTrade();
expect(anyPhase(Trade.Phase.DEPOSIT_UNLOCKED, Trade.Phase.DEPOSITS_PUBLISHED) expect(anyPhase(Trade.Phase.DEPOSITS_UNLOCKED, Trade.Phase.DEPOSITS_PUBLISHED)
.with(message) .with(message)
.from(peer) .from(peer)
.preCondition(trade.getPayoutTx() == null, .preCondition(trade.getPayoutTx() == null,

View file

@ -17,16 +17,24 @@
package bisq.core.trade.protocol; package bisq.core.trade.protocol;
import bisq.core.offer.availability.DisputeAgentSelection;
import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator; import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
import bisq.core.support.dispute.mediation.mediator.Mediator;
import bisq.core.trade.Trade; import bisq.core.trade.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.trade.statistics.TradeStatisticsManager;
import bisq.network.p2p.NodeAddress; import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.SendDirectMessageListener; import bisq.network.p2p.SendDirectMessageListener;
import java.util.HashSet;
import java.util.Set;
import bisq.common.app.Version; import bisq.common.app.Version;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler;
import bisq.common.taskrunner.TaskRunner; import bisq.common.taskrunner.TaskRunner;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@Slf4j @Slf4j
@ -42,41 +50,42 @@ public class TakerSendsInitTradeRequestToArbitrator extends TradeTask {
try { try {
runInterceptHook(); runInterceptHook();
// send request to arbitrator // send request to signing arbitrator then least used arbitrators until success
sendInitTradeRequest(trade.getOffer().getOfferPayload().getArbitratorSigner(), new SendDirectMessageListener() { sendInitTradeRequests(trade.getOffer().getOfferPayload().getArbitratorSigner(), new HashSet<NodeAddress>(), () -> {
@Override complete();
public void onArrived() { }, (errorMessage) -> {
log.info("{} arrived at arbitrator: offerId={}", InitTradeRequest.class.getSimpleName(), trade.getId()); log.warn("Cannot initialize trade with arbitrators: " + errorMessage);
} failed(errorMessage);
// send request to backup arbitrator if signer unavailable
@Override
public void onFault(String errorMessage) {
log.info("Sending {} to signing arbitrator {} failed, using backup arbitrator {}. error={}", InitTradeRequest.class.getSimpleName(), trade.getOffer().getOfferPayload().getArbitratorSigner(), processModel.getBackupArbitrator(), errorMessage);
if (processModel.getBackupArbitrator() == null) {
log.warn("Cannot take offer because signing arbitrator is offline and backup arbitrator is null");
failed();
return;
}
sendInitTradeRequest(processModel.getBackupArbitrator(), new SendDirectMessageListener() {
@Override
public void onArrived() {
log.info("{} arrived at backup arbitrator: offerId={}", InitTradeRequest.class.getSimpleName(), trade.getId());
}
@Override
public void onFault(String errorMessage) { // TODO (woodser): distinguish nack from offline
log.warn("Cannot take offer because arbitrators are unavailable: error={}.", InitTradeRequest.class.getSimpleName(), errorMessage);
failed();
}
}); });
}
});
complete(); // TODO (woodser): onArrived() doesn't get called if arbitrator rejects concurrent requests. always complete before onArrived()?
} catch (Throwable t) { } catch (Throwable t) {
failed(t); failed(t);
} }
} }
private void sendInitTradeRequests(NodeAddress arbitratorNodeAddress, Set<NodeAddress> excludedArbitrators, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
sendInitTradeRequest(arbitratorNodeAddress, new SendDirectMessageListener() {
@Override
public void onArrived() {
log.info("{} arrived at arbitrator: offerId={}", InitTradeRequest.class.getSimpleName(), trade.getId());
resultHandler.handleResult();
}
// if unavailable, try alternative arbitrator
@Override
public void onFault(String errorMessage) {
log.warn("Arbitrator {} unavailable: {}", arbitratorNodeAddress, errorMessage);
excludedArbitrators.add(arbitratorNodeAddress);
Arbitrator altArbitrator = DisputeAgentSelection.getLeastUsedArbitrator(processModel.getTradeStatisticsManager(), processModel.getArbitratorManager(), excludedArbitrators);
if (altArbitrator == null) {
errorMessageHandler.handleErrorMessage("Cannot take offer because no arbitrators are available");
return;
}
log.info("Using alternative arbitrator {}", altArbitrator.getNodeAddress());
sendInitTradeRequests(altArbitrator.getNodeAddress(), excludedArbitrators, resultHandler, errorMessageHandler);
}
});
}
private void sendInitTradeRequest(NodeAddress arbitratorNodeAddress, SendDirectMessageListener listener) { private void sendInitTradeRequest(NodeAddress arbitratorNodeAddress, SendDirectMessageListener listener) {
// get registered arbitrator // get registered arbitrator

View file

@ -638,8 +638,9 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
} }
protected void unlatchTrade() { protected void unlatchTrade() {
if (tradeLatch != null) tradeLatch.countDown(); CountDownLatch lastLatch = tradeLatch;
tradeLatch = null; tradeLatch = null;
if (lastLatch != null) lastLatch.countDown();
} }
protected void awaitTradeLatch() { protected void awaitTradeLatch() {

View file

@ -22,7 +22,6 @@ import bisq.common.app.Version;
import bisq.common.crypto.PubKeyRing; import bisq.common.crypto.PubKeyRing;
import bisq.common.crypto.Sig; import bisq.common.crypto.Sig;
import bisq.common.taskrunner.TaskRunner; import bisq.common.taskrunner.TaskRunner;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.offer.Offer; import bisq.core.offer.Offer;
import bisq.core.offer.OfferDirection; import bisq.core.offer.OfferDirection;
import bisq.core.trade.Trade; import bisq.core.trade.Trade;
@ -84,13 +83,8 @@ public class ArbitratorProcessesDepositRequest extends TradeTask {
else if (trader == processModel.getTaker()) tradeFee = ParsingUtils.coinToAtomicUnits(trade.getTakerFee()); else if (trader == processModel.getTaker()) tradeFee = ParsingUtils.coinToAtomicUnits(trade.getTakerFee());
else throw new RuntimeException("DepositRequest is not from maker or taker"); else throw new RuntimeException("DepositRequest is not from maker or taker");
// flush reserve tx from pool
XmrWalletService xmrWalletService = trade.getXmrWalletService();
MoneroDaemon daemon = xmrWalletService.getDaemon();
daemon.flushTxPool(trader.getReserveTxHash());
// verify deposit tx // verify deposit tx
xmrWalletService.verifyTradeTx(depositAddress, trade.getXmrWalletService().verifyTradeTx(depositAddress,
depositAmount, depositAmount,
tradeFee, tradeFee,
trader.getDepositTxHash(), trader.getDepositTxHash(),
@ -108,6 +102,7 @@ public class ArbitratorProcessesDepositRequest extends TradeTask {
if (processModel.getMaker().getDepositTxHex() != null && processModel.getTaker().getDepositTxHex() != null) { if (processModel.getMaker().getDepositTxHex() != null && processModel.getTaker().getDepositTxHex() != null) {
// relay txs // relay txs
MoneroDaemon daemon = trade.getXmrWalletService().getDaemon();
daemon.submitTxHex(processModel.getMaker().getDepositTxHex()); // TODO (woodser): check that result is good. will need to release funds if one is submitted daemon.submitTxHex(processModel.getMaker().getDepositTxHex()); // TODO (woodser): check that result is good. will need to release funds if one is submitted
daemon.submitTxHex(processModel.getTaker().getDepositTxHex()); daemon.submitTxHex(processModel.getTaker().getDepositTxHex());

View file

@ -20,9 +20,7 @@ package bisq.core.trade.protocol.tasks;
import bisq.common.taskrunner.TaskRunner; import bisq.common.taskrunner.TaskRunner;
import bisq.core.offer.Offer; import bisq.core.offer.Offer;
import bisq.core.offer.OfferDirection; import bisq.core.offer.OfferDirection;
import bisq.core.offer.OfferPayload;
import bisq.core.trade.Trade; import bisq.core.trade.Trade;
import bisq.core.trade.TradeUtils;
import bisq.core.trade.messages.InitTradeRequest; import bisq.core.trade.messages.InitTradeRequest;
import bisq.core.trade.protocol.TradingPeer; import bisq.core.trade.protocol.TradingPeer;
import bisq.core.util.ParsingUtils; import bisq.core.util.ParsingUtils;

View file

@ -27,7 +27,6 @@ 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;
@ -42,10 +41,10 @@ import monero.wallet.MoneroWallet;
* Arbitrator sends InitMultisigRequests after the maker acks. * Arbitrator sends InitMultisigRequests after the maker acks.
*/ */
@Slf4j @Slf4j
public class ArbitratorSendsInitTradeAndMultisigRequests extends TradeTask { public class ArbitratorSendsInitTradeOrMultisigRequests extends TradeTask {
@SuppressWarnings({"unused"}) @SuppressWarnings({"unused"})
public ArbitratorSendsInitTradeAndMultisigRequests(TaskRunner taskHandler, Trade trade) { public ArbitratorSendsInitTradeOrMultisigRequests(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade); super(taskHandler, trade);
} }
@ -53,22 +52,13 @@ public class ArbitratorSendsInitTradeAndMultisigRequests extends TradeTask {
protected void run() { protected void run() {
try { try {
runInterceptHook(); runInterceptHook();
// skip if request not from taker
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(), processModel.getOfferId().getBytes(Charsets.UTF_8)); byte[] sig = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), processModel.getOfferId().getBytes(Charsets.UTF_8));
// save pub keys // handle request from taker
processModel.getArbitrator().setPubKeyRing(processModel.getPubKeyRing()); // TODO (woodser): why duplicating field in process model if (request.getSenderNodeAddress().equals(trade.getTakerNodeAddress())) {
trade.setArbitratorPubKeyRing(processModel.getPubKeyRing());
trade.setMakerPubKeyRing(trade.getOffer().getPubKeyRing());
trade.setTakerPubKeyRing(request.getPubKeyRing());
// create request to initialize trade with maker // create request to initialize trade with maker
InitTradeRequest makerRequest = new InitTradeRequest( InitTradeRequest makerRequest = new InitTradeRequest(
@ -94,20 +84,6 @@ public class ArbitratorSendsInitTradeAndMultisigRequests extends TradeTask {
null, null,
null); null);
// send init multisig requests on ack // TODO (woodser): only send InitMultisigRequests if arbitrator has maker reserve tx, else wait for that
TradeListener listener = new TradeListener() {
@Override
public void onAckMessage(AckMessage ackMessage, NodeAddress sender) {
if (sender.equals(trade.getMakerNodeAddress()) &&
ackMessage.getSourceMsgClassName().equals(InitTradeRequest.class.getSimpleName()) &&
ackMessage.getSourceUid().equals(makerRequest.getUid())) {
trade.removeListener(this);
if (ackMessage.isSuccess()) sendInitMultisigRequests();
}
}
};
trade.addListener(listener);
// send request to maker // send request to maker
log.info("Send {} with offerId {} and uid {} to maker {} with pub key ring", makerRequest.getClass().getSimpleName(), makerRequest.getTradeId(), makerRequest.getUid(), trade.getMakerNodeAddress(), trade.getMakerPubKeyRing()); log.info("Send {} with offerId {} and uid {} to maker {} with pub key ring", makerRequest.getClass().getSimpleName(), makerRequest.getTradeId(), makerRequest.getUid(), trade.getMakerNodeAddress(), trade.getMakerPubKeyRing());
processModel.getP2PService().sendEncryptedDirectMessage( processModel.getP2PService().sendEncryptedDirectMessage(
@ -124,11 +100,19 @@ public class ArbitratorSendsInitTradeAndMultisigRequests extends TradeTask {
public void onFault(String errorMessage) { public void onFault(String errorMessage) {
log.error("Sending {} failed: uid={}; peer={}; error={}", makerRequest.getClass().getSimpleName(), makerRequest.getUid(), trade.getArbitratorNodeAddress(), errorMessage); log.error("Sending {} failed: uid={}; peer={}; error={}", makerRequest.getClass().getSimpleName(), makerRequest.getUid(), trade.getArbitratorNodeAddress(), errorMessage);
appendToErrorMessage("Sending message failed: message=" + makerRequest + "\nerrorMessage=" + errorMessage); appendToErrorMessage("Sending message failed: message=" + makerRequest + "\nerrorMessage=" + errorMessage);
trade.removeListener(listener);
failed(); failed();
} }
} }
); );
}
// handle request from maker
else if (request.getSenderNodeAddress().equals(trade.getMakerNodeAddress())) {
sendInitMultisigRequests();
complete(); // TODO: wait for InitMultisigRequest arrivals?
} else {
throw new RuntimeException("Request is not from maker or taker");
}
} catch (Throwable t) { } catch (Throwable t) {
failed(t); failed(t);
} }
@ -138,9 +122,7 @@ public class ArbitratorSendsInitTradeAndMultisigRequests extends TradeTask {
// ensure arbitrator has maker's reserve tx // ensure arbitrator has maker's reserve tx
if (processModel.getMaker().getReserveTxHash() == null) { if (processModel.getMaker().getReserveTxHash() == null) {
log.warn("Arbitrator {} does not have maker's reserve tx after initializing trade", P2PService.getMyNodeAddress()); throw new RuntimeException("Arbitrator does not have maker's reserve tx after initializing trade");
failed();
return;
} }
// create wallet for multisig // create wallet for multisig
@ -176,8 +158,6 @@ public class ArbitratorSendsInitTradeAndMultisigRequests extends TradeTask {
@Override @Override
public void onFault(String errorMessage) { public void onFault(String errorMessage) {
log.error("Sending {} failed: uid={}; peer={}; error={}", initMultisigRequest.getClass().getSimpleName(), initMultisigRequest.getUid(), trade.getMakerNodeAddress(), errorMessage); log.error("Sending {} failed: uid={}; peer={}; error={}", initMultisigRequest.getClass().getSimpleName(), initMultisigRequest.getUid(), trade.getMakerNodeAddress(), errorMessage);
appendToErrorMessage("Sending message failed: message=" + initMultisigRequest + "\nerrorMessage=" + errorMessage);
failed();
} }
} }
); );
@ -196,8 +176,6 @@ public class ArbitratorSendsInitTradeAndMultisigRequests extends TradeTask {
@Override @Override
public void onFault(String errorMessage) { public void onFault(String errorMessage) {
log.error("Sending {} failed: uid={}; peer={}; error={}", initMultisigRequest.getClass().getSimpleName(), initMultisigRequest.getUid(), trade.getTakerNodeAddress(), 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();
} }
} }
); );

View file

@ -37,9 +37,9 @@ import static bisq.core.util.Validator.checkTradeId;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j @Slf4j
public class MakerSendsInitTradeRequestIfUnreserved extends TradeTask { public class MakerSendsInitTradeRequest extends TradeTask {
@SuppressWarnings({"unused"}) @SuppressWarnings({"unused"})
public MakerSendsInitTradeRequestIfUnreserved(TaskRunner taskHandler, Trade trade) { public MakerSendsInitTradeRequest(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade); super(taskHandler, trade);
} }
@ -48,19 +48,13 @@ public class MakerSendsInitTradeRequestIfUnreserved extends TradeTask {
try { try {
runInterceptHook(); runInterceptHook();
// skip if arbitrator is signer and therefore already has reserve tx
Offer offer = processModel.getOffer();
if (offer.getOfferPayload().getArbitratorSigner().equals(trade.getArbitratorNodeAddress())) {
complete();
return;
}
// verify trade // verify trade
InitTradeRequest makerRequest = (InitTradeRequest) processModel.getTradeMessage(); // arbitrator's InitTradeRequest to maker InitTradeRequest makerRequest = (InitTradeRequest) processModel.getTradeMessage(); // arbitrator's InitTradeRequest to maker
checkNotNull(makerRequest); checkNotNull(makerRequest);
checkTradeId(processModel.getOfferId(), makerRequest); checkTradeId(processModel.getOfferId(), makerRequest);
// maker signs offer id as nonce to avoid challenge protocol // TODO (woodser): is this necessary? // maker signs offer id as nonce to avoid challenge protocol // TODO (woodser): is this necessary?
Offer offer = processModel.getOffer();
byte[] sig = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), offer.getId().getBytes(Charsets.UTF_8)); byte[] sig = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), offer.getId().getBytes(Charsets.UTF_8));
// create request to arbitrator // create request to arbitrator
@ -69,7 +63,7 @@ public class MakerSendsInitTradeRequestIfUnreserved extends TradeTask {
processModel.getMyNodeAddress(), processModel.getMyNodeAddress(),
processModel.getPubKeyRing(), processModel.getPubKeyRing(),
offer.getAmount().value, offer.getAmount().value,
offer.getPrice().getValue(), trade.getPrice().getValue(),
offer.getMakerFee().value, offer.getMakerFee().value,
trade.getProcessModel().getAccountId(), trade.getProcessModel().getAccountId(),
offer.getMakerPaymentAccountId(), offer.getMakerPaymentAccountId(),

View file

@ -60,8 +60,8 @@ public class ProcessDepositResponse extends TradeTask {
processModel.getP2PService().sendEncryptedDirectMessage(trade.getTradingPeerNodeAddress(), trade.getTradingPeerPubKeyRing(), request, new SendDirectMessageListener() { processModel.getP2PService().sendEncryptedDirectMessage(trade.getTradingPeerNodeAddress(), trade.getTradingPeerPubKeyRing(), request, new SendDirectMessageListener() {
@Override @Override
public void onArrived() { public void onArrived() {
complete();
log.info("{} arrived: trading peer={}; offerId={}; uid={}", request.getClass().getSimpleName(), trade.getTradingPeerNodeAddress(), trade.getId()); log.info("{} arrived: trading peer={}; offerId={}; uid={}", request.getClass().getSimpleName(), trade.getTradingPeerNodeAddress(), trade.getId());
complete();
} }
@Override @Override
public void onFault(String errorMessage) { public void onFault(String errorMessage) {

View file

@ -25,7 +25,6 @@ import bisq.core.trade.Trade;
import bisq.core.trade.TradeUtils; import bisq.core.trade.TradeUtils;
import bisq.core.trade.messages.InitTradeRequest; import bisq.core.trade.messages.InitTradeRequest;
import bisq.core.trade.protocol.TradingPeer; import bisq.core.trade.protocol.TradingPeer;
import bisq.core.user.User;
import bisq.common.taskrunner.TaskRunner; import bisq.common.taskrunner.TaskRunner;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
@ -62,6 +61,9 @@ public class ProcessInitTradeRequest extends TradeTask {
// handle request as arbitrator // handle request as arbitrator
TradingPeer multisigParticipant; TradingPeer multisigParticipant;
if (trade instanceof ArbitratorTrade) { if (trade instanceof ArbitratorTrade) {
trade.setMakerPubKeyRing((trade.getOffer().getPubKeyRing()));
trade.setArbitratorPubKeyRing(processModel.getPubKeyRing());
processModel.getArbitrator().setPubKeyRing(processModel.getPubKeyRing()); // TODO (woodser): why duplicating field in process model
// handle request from taker // handle request from taker
if (request.getSenderNodeAddress().equals(request.getTakerNodeAddress())) { if (request.getSenderNodeAddress().equals(request.getTakerNodeAddress())) {
@ -70,6 +72,17 @@ public class ProcessInitTradeRequest extends TradeTask {
if (trade.getTakerPubKeyRing() != null) throw new RuntimeException("Pub key ring should not be initialized before processing InitTradeRequest"); if (trade.getTakerPubKeyRing() != null) throw new RuntimeException("Pub key ring should not be initialized before processing InitTradeRequest");
trade.setTakerPubKeyRing(request.getPubKeyRing()); trade.setTakerPubKeyRing(request.getPubKeyRing());
if (!TradeUtils.isMakerSignatureValid(request, request.getMakerSignature(), offer.getPubKeyRing())) throw new RuntimeException("Maker signature is invalid for the trade request"); // verify maker signature if (!TradeUtils.isMakerSignatureValid(request, request.getMakerSignature(), offer.getPubKeyRing())) throw new RuntimeException("Maker signature is invalid for the trade request"); // verify maker signature
// check trade price
try {
long tradePrice = request.getTradePrice();
offer.verifyTakersTradePrice(tradePrice);
trade.setPrice(tradePrice);
} catch (TradePriceOutOfToleranceException e) {
failed(e.getMessage());
} catch (Throwable e2) {
failed(e2);
}
} }
// handle request from maker // handle request from maker
@ -79,6 +92,7 @@ public class ProcessInitTradeRequest extends TradeTask {
if (trade.getMakerPubKeyRing() == null) trade.setMakerPubKeyRing(request.getPubKeyRing()); if (trade.getMakerPubKeyRing() == null) trade.setMakerPubKeyRing(request.getPubKeyRing());
else if (!trade.getMakerPubKeyRing().equals(request.getPubKeyRing())) throw new RuntimeException("Init trade requests from maker and taker do not agree"); // TODO (woodser): proper handling else if (!trade.getMakerPubKeyRing().equals(request.getPubKeyRing())) throw new RuntimeException("Init trade requests from maker and taker do not agree"); // TODO (woodser): proper handling
trade.setMakerPubKeyRing(request.getPubKeyRing()); trade.setMakerPubKeyRing(request.getPubKeyRing());
if (trade.getPrice().getValue() != request.getTradePrice()) throw new RuntimeException("Maker and taker price do not agree");
} else { } else {
throw new RuntimeException("Sender is not trade's maker or taker"); throw new RuntimeException("Sender is not trade's maker or taker");
} }
@ -89,6 +103,17 @@ public class ProcessInitTradeRequest extends TradeTask {
multisigParticipant = processModel.getTaker(); multisigParticipant = processModel.getTaker();
trade.setTakerNodeAddress(request.getSenderNodeAddress()); // arbitrator sends maker InitTradeRequest with taker's node address and pub key ring trade.setTakerNodeAddress(request.getSenderNodeAddress()); // arbitrator sends maker InitTradeRequest with taker's node address and pub key ring
trade.setTakerPubKeyRing(request.getPubKeyRing()); trade.setTakerPubKeyRing(request.getPubKeyRing());
// check trade price
try {
long tradePrice = request.getTradePrice();
offer.verifyTakersTradePrice(tradePrice);
trade.setPrice(tradePrice);
} catch (TradePriceOutOfToleranceException e) {
failed(e.getMessage());
} catch (Throwable e2) {
failed(e2);
}
} }
// handle invalid trade type // handle invalid trade type
@ -106,17 +131,6 @@ public class ProcessInitTradeRequest extends TradeTask {
multisigParticipant.setAccountAgeWitnessSignature(request.getAccountAgeWitnessSignatureOfOfferId()); multisigParticipant.setAccountAgeWitnessSignature(request.getAccountAgeWitnessSignatureOfOfferId());
multisigParticipant.setCurrentDate(request.getCurrentDate()); multisigParticipant.setCurrentDate(request.getCurrentDate());
// check trade price
try {
long tradePrice = request.getTradePrice();
offer.verifyTakersTradePrice(tradePrice);
trade.setPrice(tradePrice);
} catch (TradePriceOutOfToleranceException e) {
failed(e.getMessage());
} catch (Throwable e2) {
failed(e2);
}
// check trade amount // check trade amount
checkArgument(request.getTradeAmount() > 0); checkArgument(request.getTradeAmount() > 0);
trade.setAmount(Coin.valueOf(request.getTradeAmount())); trade.setAmount(Coin.valueOf(request.getTradeAmount()));

View file

@ -18,7 +18,10 @@
package bisq.core.trade.protocol.tasks; package bisq.core.trade.protocol.tasks;
import static com.google.common.base.Preconditions.checkNotNull;
import bisq.common.app.Version; import bisq.common.app.Version;
import bisq.common.crypto.Hash;
import bisq.common.crypto.PubKeyRing; import bisq.common.crypto.PubKeyRing;
import bisq.common.crypto.Sig; import bisq.common.crypto.Sig;
import bisq.common.taskrunner.TaskRunner; import bisq.common.taskrunner.TaskRunner;
@ -77,6 +80,7 @@ public class ProcessSignContractRequest extends TradeTask {
// save contract and signature // save contract and signature
trade.setContract(contract); trade.setContract(contract);
trade.setContractAsJson(contractAsJson); trade.setContractAsJson(contractAsJson);
trade.setContractHash(Hash.getSha256Hash(checkNotNull(contractAsJson)));
trade.getSelf().setContractSignature(signature); trade.getSelf().setContractSignature(signature);
// create response with contract signature // create response with contract signature
@ -87,6 +91,7 @@ public class ProcessSignContractRequest extends TradeTask {
UUID.randomUUID().toString(), UUID.randomUUID().toString(),
Version.getP2PMessageVersion(), Version.getP2PMessageVersion(),
new Date().getTime(), new Date().getTime(),
contractAsJson,
signature); signature);
// get response recipients. only arbitrator sends response to both peers // get response recipients. only arbitrator sends response to both peers

View file

@ -44,10 +44,13 @@ public class ProcessSignContractResponse extends TradeTask {
try { try {
runInterceptHook(); runInterceptHook();
// get contract and signature // compare contracts
String contractAsJson = trade.getContractAsJson(); String contractAsJson = trade.getContractAsJson();
SignContractResponse response = (SignContractResponse) processModel.getTradeMessage(); // TODO (woodser): verify response SignContractResponse response = (SignContractResponse) processModel.getTradeMessage();
String signature = response.getContractSignature(); if (!contractAsJson.equals(response.getContractAsJson())) {
trade.getContract().printDiff(response.getContractAsJson());
failed("Contracts are not matching");
}
// get peer info // get peer info
// TODO (woodser): make these utilities / refactor model // TODO (woodser): make these utilities / refactor model
@ -60,6 +63,7 @@ public class ProcessSignContractResponse extends TradeTask {
// verify signature // verify signature
// TODO (woodser): transfer contract for convenient comparison? // TODO (woodser): transfer contract for convenient comparison?
String signature = response.getContractSignature();
if (!Sig.verify(peerPubKeyRing.getSignaturePubKey(), contractAsJson, signature)) throw new RuntimeException("Peer's contract signature is invalid"); if (!Sig.verify(peerPubKeyRing.getSignaturePubKey(), contractAsJson, signature)) throw new RuntimeException("Peer's contract signature is invalid");
// set peer's signature // set peer's signature

View file

@ -44,7 +44,7 @@ public class ArbitratorManagerTest {
User user = mock(User.class); User user = mock(User.class);
ArbitratorService arbitratorService = mock(ArbitratorService.class); ArbitratorService arbitratorService = mock(ArbitratorService.class);
ArbitratorManager manager = new ArbitratorManager(null, arbitratorService, user, null, false); ArbitratorManager manager = new ArbitratorManager(null, arbitratorService, user, null);
ArrayList<String> languagesOne = new ArrayList<String>() {{ ArrayList<String> languagesOne = new ArrayList<String>() {{
add("en"); add("en");
@ -80,7 +80,7 @@ public class ArbitratorManagerTest {
User user = mock(User.class); User user = mock(User.class);
ArbitratorService arbitratorService = mock(ArbitratorService.class); ArbitratorService arbitratorService = mock(ArbitratorService.class);
ArbitratorManager manager = new ArbitratorManager(null, arbitratorService, user, null, false); ArbitratorManager manager = new ArbitratorManager(null, arbitratorService, user, null);
ArrayList<String> languagesOne = new ArrayList<String>() {{ ArrayList<String> languagesOne = new ArrayList<String>() {{
add("en"); add("en");

View file

@ -1,9 +1,10 @@
package bisq.daemon.grpc; package bisq.daemon.grpc;
import bisq.core.api.CoreApi; import bisq.core.api.CoreApi;
import bisq.proto.grpc.RegisterDisputeAgentReply; import bisq.proto.grpc.RegisterDisputeAgentReply;
import bisq.proto.grpc.RegisterDisputeAgentRequest; import bisq.proto.grpc.RegisterDisputeAgentRequest;
import bisq.proto.grpc.UnregisterDisputeAgentReply;
import bisq.proto.grpc.UnregisterDisputeAgentRequest;
import io.grpc.ServerInterceptor; import io.grpc.ServerInterceptor;
import io.grpc.stub.StreamObserver; import io.grpc.stub.StreamObserver;
@ -18,6 +19,7 @@ import lombok.extern.slf4j.Slf4j;
import static bisq.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig.getCustomRateMeteringInterceptor; import static bisq.daemon.grpc.interceptor.GrpcServiceRateMeteringConfig.getCustomRateMeteringInterceptor;
import static bisq.proto.grpc.DisputeAgentsGrpc.DisputeAgentsImplBase; import static bisq.proto.grpc.DisputeAgentsGrpc.DisputeAgentsImplBase;
import static bisq.proto.grpc.DisputeAgentsGrpc.getRegisterDisputeAgentMethod; import static bisq.proto.grpc.DisputeAgentsGrpc.getRegisterDisputeAgentMethod;
import static bisq.proto.grpc.DisputeAgentsGrpc.getUnregisterDisputeAgentMethod;
import static java.util.concurrent.TimeUnit.SECONDS; import static java.util.concurrent.TimeUnit.SECONDS;
@ -41,10 +43,39 @@ class GrpcDisputeAgentsService extends DisputeAgentsImplBase {
public void registerDisputeAgent(RegisterDisputeAgentRequest req, public void registerDisputeAgent(RegisterDisputeAgentRequest req,
StreamObserver<RegisterDisputeAgentReply> responseObserver) { StreamObserver<RegisterDisputeAgentReply> responseObserver) {
try { try {
coreApi.registerDisputeAgent(req.getDisputeAgentType(), req.getRegistrationKey()); GrpcErrorMessageHandler errorMessageHandler = new GrpcErrorMessageHandler(getRegisterDisputeAgentMethod().getFullMethodName(), responseObserver, exceptionHandler, log);
coreApi.registerDisputeAgent(
req.getDisputeAgentType(),
req.getRegistrationKey(),
() -> {
var reply = RegisterDisputeAgentReply.newBuilder().build(); var reply = RegisterDisputeAgentReply.newBuilder().build();
responseObserver.onNext(reply); responseObserver.onNext(reply);
responseObserver.onCompleted(); responseObserver.onCompleted();
},
errorMessage -> {
if (!errorMessageHandler.isErrorHandled()) errorMessageHandler.handleErrorMessage(errorMessage);
});
} catch (Throwable cause) {
exceptionHandler.handleException(log, cause, responseObserver);
}
}
@Override
public void unregisterDisputeAgent(UnregisterDisputeAgentRequest req,
StreamObserver<UnregisterDisputeAgentReply> responseObserver) {
try {
GrpcErrorMessageHandler errorMessageHandler = new GrpcErrorMessageHandler(getUnregisterDisputeAgentMethod().getFullMethodName(), responseObserver, exceptionHandler, log);
coreApi.unregisterDisputeAgent(
req.getDisputeAgentType(),
() -> {
var reply = UnregisterDisputeAgentReply.newBuilder().build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
},
errorMessage -> {
if (!errorMessageHandler.isErrorHandled()) errorMessageHandler.handleErrorMessage(errorMessage);
});
} catch (Throwable cause) { } catch (Throwable cause) {
exceptionHandler.handleException(log, cause, responseObserver); exceptionHandler.handleException(log, cause, responseObserver);
} }

View file

@ -415,7 +415,7 @@ class GrpcWalletsService extends WalletsImplBase {
return getCustomRateMeteringInterceptor(coreApi.getConfig().appDataDir, this.getClass()) return getCustomRateMeteringInterceptor(coreApi.getConfig().appDataDir, this.getClass())
.or(() -> Optional.of(CallRateMeteringInterceptor.valueOf( .or(() -> Optional.of(CallRateMeteringInterceptor.valueOf(
new HashMap<>() {{ new HashMap<>() {{
put(getGetBalancesMethod().getFullMethodName(), new GrpcCallRateMeter(20, SECONDS)); put(getGetBalancesMethod().getFullMethodName(), new GrpcCallRateMeter(50, SECONDS));
put(getGetAddressBalanceMethod().getFullMethodName(), new GrpcCallRateMeter(1, SECONDS)); put(getGetAddressBalanceMethod().getFullMethodName(), new GrpcCallRateMeter(1, SECONDS));
put(getGetFundingAddressesMethod().getFullMethodName(), new GrpcCallRateMeter(1, SECONDS)); put(getGetFundingAddressesMethod().getFullMethodName(), new GrpcCallRateMeter(1, SECONDS));
put(getSendBtcMethod().getFullMethodName(), new GrpcCallRateMeter(1, MINUTES)); put(getSendBtcMethod().getFullMethodName(), new GrpcCallRateMeter(1, MINUTES));

View file

@ -192,7 +192,7 @@ public class NotificationCenter {
message = Res.get("notification.trade.accepted", role); message = Res.get("notification.trade.accepted", role);
} }
if (trade instanceof BuyerTrade && phase.ordinal() == Trade.Phase.DEPOSIT_UNLOCKED.ordinal()) if (trade instanceof BuyerTrade && phase.ordinal() == Trade.Phase.DEPOSITS_UNLOCKED.ordinal())
message = Res.get("notification.trade.confirmed"); message = Res.get("notification.trade.confirmed");
else if (trade instanceof SellerTrade && phase.ordinal() == Trade.Phase.PAYMENT_SENT.ordinal()) else if (trade instanceof SellerTrade && phase.ordinal() == Trade.Phase.PAYMENT_SENT.ordinal())
message = Res.get("notification.trade.paymentStarted"); message = Res.get("notification.trade.paymentStarted");

View file

@ -162,7 +162,7 @@ public class BuyerStep2View extends TradeStepView {
switch (state) { switch (state) {
case BUYER_CONFIRMED_IN_UI_PAYMENT_SENT: case BUYER_CONFIRMED_IN_UI_PAYMENT_SENT:
busyAnimation.play(); busyAnimation.play();
statusLabel.setText("Confirming payment sent. This can take up to a few minutes depending on connection speed. Please wait..."); statusLabel.setText("Confirming payment sent. This can take up to a few minutes. Please wait...");
break; break;
case BUYER_SENT_PAYMENT_SENT_MSG: case BUYER_SENT_PAYMENT_SENT_MSG:
busyAnimation.play(); busyAnimation.play();

View file

@ -121,7 +121,7 @@ public class SellerStep3View extends TradeStepView {
switch (state) { switch (state) {
case SELLER_CONFIRMED_IN_UI_PAYMENT_RECEIPT: case SELLER_CONFIRMED_IN_UI_PAYMENT_RECEIPT:
busyAnimation.play(); busyAnimation.play();
statusLabel.setText(Res.get("Confirming payment received. This can take up to a few minutes depending on connection speed. Please wait...")); statusLabel.setText(Res.get("Confirming payment received. This can take up to a few minutes. Please wait..."));
break; break;
case SELLER_PUBLISHED_PAYOUT_TX: case SELLER_PUBLISHED_PAYOUT_TX:
case SELLER_SENT_PAYOUT_TX_PUBLISHED_MSG: case SELLER_SENT_PAYOUT_TX_PUBLISHED_MSG:

View file

@ -845,14 +845,6 @@
<sha256 value="4728eddd64e6ae3e1f205a775c6a327b24bd990b86d528584a17450a8b5f00d6" origin="Generated by Gradle"/> <sha256 value="4728eddd64e6ae3e1f205a775c6a327b24bd990b86d528584a17450a8b5f00d6" origin="Generated by Gradle"/>
</artifact> </artifact>
</component> </component>
<component group="io.github.monero-ecosystem" name="monero-java" version="0.7.0">
<artifact name="monero-java-0.7.0.jar">
<sha256 value="dbd9249df77c4d313b385bc5352a9e61ad930fae79acb5178813be19c2bb4771" origin="Generated by Gradle"/>
</artifact>
<artifact name="monero-java-0.7.0.pom">
<sha256 value="ead76a2facdfaa55fcc2bc4aa706e3c6eebd5df4b9dcb153a9ff01f8f0324596" origin="Generated by Gradle"/>
</artifact>
</component>
<component group="io.github.monero-ecosystem" name="monero-java" version="0.7.2"> <component group="io.github.monero-ecosystem" name="monero-java" version="0.7.2">
<artifact name="monero-java-0.7.2.jar"> <artifact name="monero-java-0.7.2.jar">
<sha256 value="166903729f2f554f2c7a9c908bc79e5940a96510852d9f9673494d346cec3c82" origin="Generated by Gradle"/> <sha256 value="166903729f2f554f2c7a9c908bc79e5940a96510852d9f9673494d346cec3c82" origin="Generated by Gradle"/>

View file

@ -220,6 +220,8 @@ message SendDisputeChatMessageReply {
service DisputeAgents { service DisputeAgents {
rpc RegisterDisputeAgent (RegisterDisputeAgentRequest) returns (RegisterDisputeAgentReply) { rpc RegisterDisputeAgent (RegisterDisputeAgentRequest) returns (RegisterDisputeAgentReply) {
} }
rpc UnregisterDisputeAgent (UnregisterDisputeAgentRequest) returns (UnregisterDisputeAgentReply) {
}
} }
message RegisterDisputeAgentRequest { message RegisterDisputeAgentRequest {
@ -230,6 +232,13 @@ message RegisterDisputeAgentRequest {
message RegisterDisputeAgentReply { message RegisterDisputeAgentReply {
} }
message UnregisterDisputeAgentRequest {
string dispute_agent_type = 1;
}
message UnregisterDisputeAgentReply {
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Notifications // Notifications
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -530,6 +539,7 @@ message OfferInfo {
string pub_key_ring = 26; string pub_key_ring = 26;
string version_nr = 27; string version_nr = 27;
int32 protocol_version = 28; int32 protocol_version = 28;
string arbitrator_signer = 29;
} }
message AvailabilityResultWithDescription { message AvailabilityResultWithDescription {
@ -822,19 +832,20 @@ message TradeInfo {
string payout_tx_id = 11; string payout_tx_id = 11;
uint64 amount_as_long = 12; uint64 amount_as_long = 12;
string price = 13; string price = 13;
string trading_peer_node_address = 14; string arbitrator_node_address = 14;
string state = 15; string trading_peer_node_address = 15;
string phase = 16; string state = 16;
string period_state = 17; string phase = 17;
bool is_deposit_published = 18; string period_state = 18;
bool is_deposit_unlocked = 19; bool is_deposit_published = 19;
bool is_payment_sent = 20; bool is_deposit_unlocked = 20;
bool is_payment_received = 21; bool is_payment_sent = 21;
bool is_payout_published = 22; bool is_payment_received = 22;
bool is_completed = 23; bool is_payout_published = 23;
string contract_as_json = 24; bool is_completed = 24;
ContractInfo contract = 25; string contract_as_json = 25;
string trade_volume = 26; ContractInfo contract = 26;
string trade_volume = 27;
string maker_deposit_tx_id = 100; string maker_deposit_tx_id = 100;
string taker_deposit_tx_id = 101; string taker_deposit_tx_id = 101;

View file

@ -184,7 +184,6 @@ 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 backup_arbitrator = 6;
} }
message RefreshOfferMessage { message RefreshOfferMessage {
@ -328,7 +327,8 @@ message SignContractResponse {
PubKeyRing pub_key_ring = 3; PubKeyRing pub_key_ring = 3;
string uid = 4; string uid = 4;
int64 current_date = 5; int64 current_date = 5;
string contract_signature = 6; string contract_as_json = 6;
string contract_signature = 7;
} }
message DepositRequest { message DepositRequest {
@ -1598,14 +1598,13 @@ message OpenOffer {
Offer offer = 1; Offer offer = 1;
State state = 2; State state = 2;
NodeAddress backup_arbitrator = 3; int64 trigger_price = 3;
int64 trigger_price = 4; bool auto_split = 4;
bool auto_split = 5; repeated string scheduled_tx_hashes = 5;
repeated string scheduled_tx_hashes = 6; string scheduled_amount = 6; // BigInteger
string scheduled_amount = 7; // BigInteger string reserve_tx_hash = 7;
string reserve_tx_hash = 8; string reserve_tx_hex = 8;
string reserve_tx_hex = 9; string reserve_tx_key = 9;
string reserve_tx_key = 10;
} }
message Tradable { message Tradable {
@ -1665,7 +1664,7 @@ message Trade {
INIT = 1; INIT = 1;
DEPOSIT_REQUESTED = 2; DEPOSIT_REQUESTED = 2;
DEPOSITS_PUBLISHED = 3; DEPOSITS_PUBLISHED = 3;
DEPOSITS_CONFIRMED = 4; DEPOSITS_UNLOCKED = 4;
PAYMENT_SENT = 5; PAYMENT_SENT = 5;
PAYMENT_RECEIVED = 6; PAYMENT_RECEIVED = 6;
PAYOUT_PUBLISHED = 7; PAYOUT_PUBLISHED = 7;
@ -1778,12 +1777,11 @@ 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 backup_arbitrator = 1002; TradingPeer maker = 1002;
TradingPeer maker = 1003; TradingPeer taker = 1003;
TradingPeer taker = 1004; TradingPeer arbitrator = 1004;
TradingPeer arbitrator = 1005; NodeAddress temp_trading_peer_node_address = 1005;
NodeAddress temp_trading_peer_node_address = 1006; string multisig_address = 1006;
string multisig_address = 1007;
} }
message TradingPeer { message TradingPeer {