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