Merge branch 'arbitration-system-improvements' of https://github.com/ManfredKarrer/bisq into ManfredKarrer-arbitration-system-improvements

# Conflicts:
#	desktop/src/main/java/bisq/desktop/main/account/content/arbitratorselection/ArbitratorSelectionView.java
This commit is contained in:
Manfred Karrer 2018-10-16 11:48:11 -05:00
commit ce5a8b4f19
No known key found for this signature in database
GPG key ID: 401250966A6B2C46
42 changed files with 398 additions and 805 deletions

View file

@ -125,6 +125,7 @@ message OfferAvailabilityResponse {
AvailabilityResult availability_result = 2;
repeated int32 supported_capabilities = 3;
string uid = 4;
NodeAddress arbitrator = 5;
}
message RefreshOfferMessage {
@ -1056,6 +1057,7 @@ message OpenOffer {
Offer offer = 1;
State state = 2;
NodeAddress arbitrator_node_address = 3;
}
message Tradable {

View file

@ -55,6 +55,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
@ -229,30 +230,14 @@ public class ArbitratorManager {
);
});
if (preferences.isAutoSelectArbitrators()) {
arbitratorsObservableMap.values().stream()
.filter(user::hasMatchingLanguage)
.forEach(a -> {
user.addAcceptedArbitrator(a);
user.addAcceptedMediator(getMediator(a)
);
});
} else {
// if we don't have any arbitrator we set all matching
// we use a delay as we might get our matching arbitrator a bit delayed (first we get one we did not selected
// then we get our selected one - we don't want to activate the first in that case)
UserThread.runAfter(() -> {
if (user.getAcceptedArbitrators().isEmpty()) {
arbitratorsObservableMap.values().stream()
.filter(user::hasMatchingLanguage)
.forEach(a -> {
user.addAcceptedArbitrator(a);
user.addAcceptedMediator(getMediator(a)
);
});
}
}, 100, TimeUnit.MILLISECONDS);
}
// We keep the domain with storing the arbitrators in user as it might be still useful for mediators
arbitratorsObservableMap.values().forEach(a -> {
user.addAcceptedArbitrator(a);
user.addAcceptedMediator(getMediator(a)
);
});
log.info("Available arbitrators: {}", arbitratorsObservableMap.keySet());
}
// TODO we mirror arbitrator data for mediator as long we have not impl. it in the UI
@ -381,4 +366,10 @@ public class ArbitratorManager {
republishArbitratorTimer = null;
}
}
public Optional<Arbitrator> getArbitratorByNodeAddress(NodeAddress nodeAddress) {
return arbitratorsObservableMap.containsKey(nodeAddress) ?
Optional.of(arbitratorsObservableMap.get(nodeAddress)) :
Optional.empty();
}
}

View file

@ -91,6 +91,7 @@ class DefaultSeedNodeAddresses {
// 4. Rename the directory with your local onion address
// 5. Edit here your found onion address (new NodeAddress("YOUR_ONION.onion:8002")
new NodeAddress("rxdkppp3vicnbgqt.onion:8002"),
new NodeAddress("4ie52dse64kaarxw.onion:8002"),
// LTC mainnet
new NodeAddress("acyvotgewx46pebw.onion:8003"),

View file

@ -20,6 +20,8 @@ package bisq.core.offer;
import bisq.core.trade.Tradable;
import bisq.core.trade.TradableList;
import bisq.network.p2p.NodeAddress;
import bisq.common.Timer;
import bisq.common.UserThread;
import bisq.common.proto.ProtoUtil;
@ -28,11 +30,15 @@ import bisq.common.storage.Storage;
import io.bisq.generated.protobuffer.PB;
import java.util.Date;
import java.util.Optional;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
@EqualsAndHashCode
@Slf4j
public final class OpenOffer implements Tradable {
@ -52,6 +58,10 @@ public final class OpenOffer implements Tradable {
private final Offer offer;
@Getter
private State state;
@Getter
@Setter
@Nullable
private NodeAddress arbitratorNodeAddress;
transient private Storage<TradableList<OpenOffer>> storage;
@ -65,9 +75,10 @@ public final class OpenOffer implements Tradable {
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
private OpenOffer(Offer offer, State state) {
private OpenOffer(Offer offer, State state, @Nullable NodeAddress arbitratorNodeAddress) {
this.offer = offer;
this.state = state;
this.arbitratorNodeAddress = arbitratorNodeAddress;
if (this.state == State.RESERVED)
setState(State.AVAILABLE);
@ -75,15 +86,19 @@ public final class OpenOffer implements Tradable {
@Override
public PB.Tradable toProtoMessage() {
return PB.Tradable.newBuilder().setOpenOffer(PB.OpenOffer.newBuilder()
PB.OpenOffer.Builder builder = PB.OpenOffer.newBuilder()
.setOffer(offer.toProtoMessage())
.setState(PB.OpenOffer.State.valueOf(state.name())))
.build();
.setState(PB.OpenOffer.State.valueOf(state.name()));
Optional.ofNullable(arbitratorNodeAddress).ifPresent(nodeAddress -> builder.setArbitratorNodeAddress(nodeAddress.toProtoMessage()));
return PB.Tradable.newBuilder().setOpenOffer(builder).build();
}
public static Tradable fromProto(PB.OpenOffer proto) {
return new OpenOffer(Offer.fromProto(proto.getOffer()),
ProtoUtil.enumFromProto(OpenOffer.State.class, proto.getState().name()));
ProtoUtil.enumFromProto(OpenOffer.State.class, proto.getState().name()),
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null);
}

View file

@ -17,10 +17,12 @@
package bisq.core.offer;
import bisq.core.arbitration.ArbitratorManager;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.exceptions.TradePriceOutOfToleranceException;
import bisq.core.offer.availability.ArbitratorSelection;
import bisq.core.offer.messages.OfferAvailabilityRequest;
import bisq.core.offer.messages.OfferAvailabilityResponse;
import bisq.core.offer.placeoffer.PlaceOfferModel;
@ -29,6 +31,7 @@ import bisq.core.provider.price.PriceFeedService;
import bisq.core.trade.TradableList;
import bisq.core.trade.closed.ClosedTradableManager;
import bisq.core.trade.handlers.TransactionResultHandler;
import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.core.user.Preferences;
import bisq.core.user.User;
import bisq.core.util.Validator;
@ -100,6 +103,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
private final ClosedTradableManager closedTradableManager;
private final PriceFeedService priceFeedService;
private final Preferences preferences;
private final TradeStatisticsManager tradeStatisticsManager;
private final ArbitratorManager arbitratorManager;
private final Storage<TradableList<OpenOffer>> openOfferTradableListStorage;
private final Map<String, OpenOffer> offersToBeEdited = new HashMap<>();
private boolean stopped;
@ -124,6 +129,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
PriceFeedService priceFeedService,
Preferences preferences,
PersistenceProtoResolver persistenceProtoResolver,
TradeStatisticsManager tradeStatisticsManager,
ArbitratorManager arbitratorManager,
@Named(Storage.STORAGE_DIR) File storageDir) {
this.keyRing = keyRing;
this.user = user;
@ -135,6 +142,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
this.closedTradableManager = closedTradableManager;
this.priceFeedService = priceFeedService;
this.preferences = preferences;
this.tradeStatisticsManager = tradeStatisticsManager;
this.arbitratorManager = arbitratorManager;
openOfferTradableListStorage = new Storage<>(storageDir, persistenceProtoResolver);
@ -324,6 +333,8 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
tradeWalletService,
bsqWalletService,
offerBookService,
arbitratorManager,
tradeStatisticsManager,
user);
PlaceOfferProtocol placeOfferProtocol = new PlaceOfferProtocol(
model,
@ -548,12 +559,17 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
try {
Optional<OpenOffer> openOfferOptional = getOpenOfferById(request.offerId);
AvailabilityResult availabilityResult;
NodeAddress arbitratorNodeAddress = null;
if (openOfferOptional.isPresent()) {
if (openOfferOptional.get().getState() == OpenOffer.State.AVAILABLE) {
final Offer offer = openOfferOptional.get().getOffer();
if (preferences.getIgnoreTradersList().stream().noneMatch(hostName -> hostName.equals(peer.getHostNameWithoutPostFix()))) {
OpenOffer openOffer = openOfferOptional.get();
if (openOffer.getState() == OpenOffer.State.AVAILABLE) {
final Offer offer = openOffer.getOffer();
if (preferences.getIgnoreTradersList().stream().noneMatch(hostName -> hostName.equals(peer.getHostName()))) {
availabilityResult = AvailabilityResult.AVAILABLE;
arbitratorNodeAddress = ArbitratorSelection.getLeastUsedArbitrator(tradeStatisticsManager, arbitratorManager).getNodeAddress();
openOffer.setArbitratorNodeAddress(arbitratorNodeAddress);
List<NodeAddress> acceptedArbitrators = user.getAcceptedArbitratorAddresses();
if (acceptedArbitrators != null && !acceptedArbitrators.isEmpty()) {
// Check also tradePrice to avoid failures after taker fee is paid caused by a too big difference
@ -586,7 +602,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
availabilityResult = AvailabilityResult.OFFER_TAKEN;
}
OfferAvailabilityResponse offerAvailabilityResponse = new OfferAvailabilityResponse(request.offerId, availabilityResult);
OfferAvailabilityResponse offerAvailabilityResponse = new OfferAvailabilityResponse(request.offerId, availabilityResult, arbitratorNodeAddress);
log.info("Send {} with offerId {} and uid {} to peer {}",
offerAvailabilityResponse.getClass().getSimpleName(), offerAvailabilityResponse.getOfferId(),
offerAvailabilityResponse.getUid(), peer);

View file

@ -0,0 +1,105 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.offer.availability;
import bisq.core.arbitration.Arbitrator;
import bisq.core.arbitration.ArbitratorManager;
import bisq.core.trade.statistics.TradeStatistics2;
import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.common.util.Tuple2;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkArgument;
@Slf4j
public class ArbitratorSelection {
@Getter
private static boolean isNewRuleActivated;
static {
try {
//TODO set activation data to 1 month after release
Date activationDate = new SimpleDateFormat("dd/MM/yyyy").parse("20/11/2018");
isNewRuleActivated = new Date().after(activationDate);
} catch (ParseException e) {
e.printStackTrace();
}
}
public static Arbitrator getLeastUsedArbitrator(TradeStatisticsManager tradeStatisticsManager,
ArbitratorManager arbitratorManager) {
// We take last 100 entries from trade statistics
List<TradeStatistics2> list = new ArrayList<>(tradeStatisticsManager.getObservableTradeStatisticsSet());
list.sort(Comparator.comparing(TradeStatistics2::getTradeDate));
Collections.reverse(list);
if (!list.isEmpty()) {
int max = Math.min(list.size(), 100);
list = list.subList(0, max);
}
// We stored only first 4 chars of arbitrators onion address
List<String> lastAddressesUsedInTrades = list.stream()
.filter(tradeStatistics2 -> tradeStatistics2.getExtraDataMap() != null)
.map(tradeStatistics2 -> tradeStatistics2.getExtraDataMap().get(TradeStatistics2.ARBITRATOR_ADDRESS))
.collect(Collectors.toList());
Set<String> arbitrators = arbitratorManager.getArbitratorsObservableMap().values().stream()
.map(arbitrator -> arbitrator.getNodeAddress().getHostName())
.collect(Collectors.toSet());
String result = getLeastUsedArbitrator(lastAddressesUsedInTrades, arbitrators);
Optional<Arbitrator> optionalArbitrator = arbitratorManager.getArbitratorsObservableMap().values().stream()
.filter(e -> e.getNodeAddress().getHostName().equals(result))
.findAny();
checkArgument(optionalArbitrator.isPresent(), "optionalArbitrator has to be present");
return optionalArbitrator.get();
}
static String getLeastUsedArbitrator(List<String> lastAddressesUsedInTrades, Set<String> arbitrators) {
checkArgument(!arbitrators.isEmpty(), "arbitrators must nto be empty");
List<Tuple2<String, AtomicInteger>> arbitratorTuples = arbitrators.stream()
.map(e -> new Tuple2<>(e, new AtomicInteger(0)))
.collect(Collectors.toList());
arbitratorTuples.forEach(tuple -> {
int count = (int) lastAddressesUsedInTrades.stream()
.filter(tuple.first::startsWith) // we use only first 4 chars for comparing
.mapToInt(e -> 1)
.count();
tuple.second.set(count);
});
arbitratorTuples.sort(Comparator.comparingInt(e -> e.second.get()));
return arbitratorTuples.get(0).first;
}
}

View file

@ -27,6 +27,9 @@ import bisq.common.crypto.PubKeyRing;
import bisq.common.taskrunner.Model;
import lombok.Getter;
import lombok.Setter;
import javax.annotation.Nullable;
public class OfferAvailabilityModel implements Model {
@Getter
@ -38,6 +41,10 @@ public class OfferAvailabilityModel implements Model {
private NodeAddress peerNodeAddress; // maker
private OfferAvailabilityResponse message;
@Nullable
@Setter
@Getter
private NodeAddress selectedArbitrator;
public OfferAvailabilityModel(Offer offer,
PubKeyRing pubKeyRing,

View file

@ -19,9 +19,12 @@ package bisq.core.offer.availability.tasks;
import bisq.core.offer.AvailabilityResult;
import bisq.core.offer.Offer;
import bisq.core.offer.availability.ArbitratorSelection;
import bisq.core.offer.availability.OfferAvailabilityModel;
import bisq.core.offer.messages.OfferAvailabilityResponse;
import bisq.network.p2p.NodeAddress;
import bisq.common.taskrunner.Task;
import bisq.common.taskrunner.TaskRunner;
@ -39,6 +42,13 @@ public class ProcessOfferAvailabilityResponse extends Task<OfferAvailabilityMode
if (model.getOffer().getState() != Offer.State.REMOVED) {
if (offerAvailabilityResponse.getAvailabilityResult() == AvailabilityResult.AVAILABLE) {
model.getOffer().setState(Offer.State.AVAILABLE);
if (ArbitratorSelection.isNewRuleActivated()) {
NodeAddress selectedArbitrator = offerAvailabilityResponse.getArbitrator();
if (selectedArbitrator == null)
failed("You cannot take that offer because the offer maker is running an incompatible version.");
else
model.setSelectedArbitrator(selectedArbitrator);
}
} else {
model.getOffer().setState(Offer.State.NOT_AVAILABLE);
failed("Take offer attempt rejected because of: " + offerAvailabilityResponse.getAvailabilityResult());

View file

@ -20,6 +20,7 @@ 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,12 +47,17 @@ public final class OfferAvailabilityResponse extends OfferMessage implements Sup
@Nullable
private final List<Integer> supportedCapabilities;
public OfferAvailabilityResponse(String offerId, AvailabilityResult availabilityResult) {
// Was introduced in v 0.9.0. Might be null if msg received from node with old version
@Nullable
private final NodeAddress arbitrator;
public OfferAvailabilityResponse(String offerId, AvailabilityResult availabilityResult, NodeAddress arbitrator) {
this(offerId,
availabilityResult,
Capabilities.getSupportedCapabilities(),
Version.getP2PMessageVersion(),
UUID.randomUUID().toString());
UUID.randomUUID().toString(),
arbitrator);
}
@ -63,10 +69,12 @@ public final class OfferAvailabilityResponse extends OfferMessage implements Sup
AvailabilityResult availabilityResult,
@Nullable List<Integer> supportedCapabilities,
int messageVersion,
@Nullable String uid) {
@Nullable String uid,
@Nullable NodeAddress arbitrator) {
super(messageVersion, offerId, uid);
this.availabilityResult = availabilityResult;
this.supportedCapabilities = supportedCapabilities;
this.arbitrator = arbitrator;
}
@Override
@ -77,6 +85,7 @@ public final class OfferAvailabilityResponse extends OfferMessage implements Sup
Optional.ofNullable(supportedCapabilities).ifPresent(e -> builder.addAllSupportedCapabilities(supportedCapabilities));
Optional.ofNullable(uid).ifPresent(e -> builder.setUid(uid));
Optional.ofNullable(arbitrator).ifPresent(e -> builder.setArbitrator(arbitrator.toProtoMessage()));
return getNetworkEnvelopeBuilder()
.setOfferAvailabilityResponse(builder)
@ -88,6 +97,7 @@ public final class OfferAvailabilityResponse extends OfferMessage implements Sup
ProtoUtil.enumFromProto(AvailabilityResult.class, proto.getAvailabilityResult().name()),
proto.getSupportedCapabilitiesList().isEmpty() ? null : proto.getSupportedCapabilitiesList(),
messageVersion,
proto.getUid().isEmpty() ? null : proto.getUid());
proto.getUid().isEmpty() ? null : proto.getUid(),
proto.hasArbitrator() ? NodeAddress.fromProto(proto.getArbitrator()) : null);
}
}

View file

@ -17,11 +17,13 @@
package bisq.core.offer.placeoffer;
import bisq.core.arbitration.ArbitratorManager;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferBookService;
import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.core.user.User;
import bisq.common.taskrunner.Model;
@ -44,6 +46,8 @@ public class PlaceOfferModel implements Model {
private final TradeWalletService tradeWalletService;
private final BsqWalletService bsqWalletService;
private final OfferBookService offerBookService;
private final ArbitratorManager arbitratorManager;
private final TradeStatisticsManager tradeStatisticsManager;
private final User user;
// Mutable
@ -59,6 +63,8 @@ public class PlaceOfferModel implements Model {
TradeWalletService tradeWalletService,
BsqWalletService bsqWalletService,
OfferBookService offerBookService,
ArbitratorManager arbitratorManager,
TradeStatisticsManager tradeStatisticsManager,
User user) {
this.offer = offer;
this.reservedFundsForOffer = reservedFundsForOffer;
@ -67,6 +73,8 @@ public class PlaceOfferModel implements Model {
this.tradeWalletService = tradeWalletService;
this.bsqWalletService = bsqWalletService;
this.offerBookService = offerBookService;
this.arbitratorManager = arbitratorManager;
this.tradeStatisticsManager = tradeStatisticsManager;
this.user = user;
}

View file

@ -26,10 +26,8 @@ import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.btc.wallet.TxBroadcaster;
import bisq.core.btc.wallet.WalletService;
import bisq.core.offer.Offer;
import bisq.core.offer.availability.ArbitratorSelection;
import bisq.core.offer.placeoffer.PlaceOfferModel;
import bisq.core.trade.protocol.ArbitratorSelectionRule;
import bisq.network.p2p.NodeAddress;
import bisq.common.UserThread;
import bisq.common.taskrunner.Task;
@ -43,8 +41,6 @@ import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkNotNull;
public class CreateMakerFeeTx extends Task<PlaceOfferModel> {
private static final Logger log = LoggerFactory.getLogger(CreateMakerFeeTx.class);
private Transaction tradeFeeTx = null;
@ -64,11 +60,8 @@ public class CreateMakerFeeTx extends Task<PlaceOfferModel> {
String id = offer.getId();
BtcWalletService walletService = model.getWalletService();
NodeAddress selectedArbitratorNodeAddress = ArbitratorSelectionRule.select(model.getUser().getAcceptedArbitratorAddresses(),
model.getOffer());
log.debug("selectedArbitratorAddress " + selectedArbitratorNodeAddress);
Arbitrator selectedArbitrator = model.getUser().getAcceptedArbitratorByAddress(selectedArbitratorNodeAddress);
checkNotNull(selectedArbitrator, "selectedArbitrator must not be null at CreateOfferFeeTx");
Arbitrator arbitrator = ArbitratorSelection.getLeastUsedArbitrator(model.getTradeStatisticsManager(),
model.getArbitratorManager());
Address fundingAddress = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.OFFER_FUNDING).getAddress();
Address reservedForTradeAddress = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.RESERVED_FOR_TRADE).getAddress();
@ -85,7 +78,7 @@ public class CreateMakerFeeTx extends Task<PlaceOfferModel> {
model.isUseSavingsWallet(),
offer.getMakerFee(),
offer.getTxFee(),
selectedArbitrator.getBtcAddress(),
arbitrator.getBtcAddress(),
new TxBroadcaster.Callback() {
@Override
public void onSuccess(Transaction transaction) {

View file

@ -35,6 +35,8 @@ import org.bitcoinj.core.Coin;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
@Slf4j
public final class BuyerAsMakerTrade extends BuyerTrade implements MakerTrade {
@ -46,10 +48,10 @@ public final class BuyerAsMakerTrade extends BuyerTrade implements MakerTrade {
Coin txFee,
Coin takeOfferFee,
boolean isCurrencyForTakerFeeBtc,
@Nullable NodeAddress arbitratorNodeAddress,
Storage<? extends TradableList> storage,
BtcWalletService btcWalletService) {
super(offer, txFee, takeOfferFee, isCurrencyForTakerFeeBtc,
storage, btcWalletService);
super(offer, txFee, takeOfferFee, isCurrencyForTakerFeeBtc, arbitratorNodeAddress, storage, btcWalletService);
}
///////////////////////////////////////////////////////////////////////////////////////////
@ -69,11 +71,12 @@ public final class BuyerAsMakerTrade extends BuyerTrade implements MakerTrade {
BtcWalletService btcWalletService,
CoreProtoResolver coreProtoResolver) {
PB.Trade proto = buyerAsMakerTradeProto.getTrade();
final BuyerAsMakerTrade trade = new BuyerAsMakerTrade(
BuyerAsMakerTrade trade = new BuyerAsMakerTrade(
Offer.fromProto(proto.getOffer()),
Coin.valueOf(proto.getTxFeeAsLong()),
Coin.valueOf(proto.getTakerFeeAsLong()),
proto.getIsCurrencyForTakerFeeBtc(),
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
storage,
btcWalletService);

View file

@ -33,6 +33,8 @@ import org.bitcoinj.core.Coin;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkArgument;
@Slf4j
@ -49,10 +51,11 @@ public final class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade {
boolean isCurrencyForTakerFeeBtc,
long tradePrice,
NodeAddress tradingPeerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
Storage<? extends TradableList> storage,
BtcWalletService btcWalletService) {
super(offer, tradeAmount, txFee, takerFee, isCurrencyForTakerFeeBtc, tradePrice,
tradingPeerNodeAddress, storage, btcWalletService);
super(offer, tradeAmount, txFee, takerFee, isCurrencyForTakerFeeBtc, tradePrice, tradingPeerNodeAddress,
arbitratorNodeAddress, storage, btcWalletService);
}
@ -81,6 +84,7 @@ public final class BuyerAsTakerTrade extends BuyerTrade implements TakerTrade {
proto.getIsCurrencyForTakerFeeBtc(),
proto.getTradePrice(),
proto.hasTradingPeerNodeAddress() ? NodeAddress.fromProto(proto.getTradingPeerNodeAddress()) : null,
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
storage,
btcWalletService),
proto,

View file

@ -31,6 +31,8 @@ import org.bitcoinj.core.Coin;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
@ -43,19 +45,21 @@ public abstract class BuyerTrade extends Trade {
boolean isCurrencyForTakerFeeBtc,
long tradePrice,
NodeAddress tradingPeerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
Storage<? extends TradableList> storage,
BtcWalletService btcWalletService) {
super(offer, tradeAmount, txFee, takerFee, isCurrencyForTakerFeeBtc, tradePrice,
tradingPeerNodeAddress, storage, btcWalletService);
tradingPeerNodeAddress, arbitratorNodeAddress, storage, btcWalletService);
}
BuyerTrade(Offer offer,
Coin txFee,
Coin takerFee,
boolean isCurrencyForTakerFeeBtc,
@Nullable NodeAddress arbitratorNodeAddress,
Storage<? extends TradableList> storage,
BtcWalletService btcWalletService) {
super(offer, txFee, takerFee, isCurrencyForTakerFeeBtc, storage, btcWalletService);
super(offer, txFee, takerFee, isCurrencyForTakerFeeBtc, arbitratorNodeAddress, storage, btcWalletService);
}
public void onFiatPaymentStarted(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {

View file

@ -35,6 +35,8 @@ import org.bitcoinj.core.Coin;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
@Slf4j
public final class SellerAsMakerTrade extends SellerTrade implements MakerTrade {
@ -46,9 +48,10 @@ public final class SellerAsMakerTrade extends SellerTrade implements MakerTrade
Coin txFee,
Coin takerFee,
boolean isCurrencyForTakerFeeBtc,
@Nullable NodeAddress arbitratorNodeAddress,
Storage<? extends TradableList> storage,
BtcWalletService btcWalletService) {
super(offer, txFee, takerFee, isCurrencyForTakerFeeBtc, storage, btcWalletService);
super(offer, txFee, takerFee, isCurrencyForTakerFeeBtc, arbitratorNodeAddress, storage, btcWalletService);
}
@ -69,11 +72,12 @@ public final class SellerAsMakerTrade extends SellerTrade implements MakerTrade
BtcWalletService btcWalletService,
CoreProtoResolver coreProtoResolver) {
PB.Trade proto = sellerAsMakerTradeProto.getTrade();
final SellerAsMakerTrade trade = new SellerAsMakerTrade(
SellerAsMakerTrade trade = new SellerAsMakerTrade(
Offer.fromProto(proto.getOffer()),
Coin.valueOf(proto.getTxFeeAsLong()),
Coin.valueOf(proto.getTakerFeeAsLong()),
proto.getIsCurrencyForTakerFeeBtc(),
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
storage,
btcWalletService);

View file

@ -33,6 +33,8 @@ import org.bitcoinj.core.Coin;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkArgument;
@Slf4j
@ -49,10 +51,11 @@ public final class SellerAsTakerTrade extends SellerTrade implements TakerTrade
boolean isCurrencyForTakerFeeBtc,
long tradePrice,
NodeAddress tradingPeerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
Storage<? extends TradableList> storage,
BtcWalletService btcWalletService) {
super(offer, tradeAmount, txFee, takerFee, isCurrencyForTakerFeeBtc, tradePrice,
tradingPeerNodeAddress, storage, btcWalletService);
tradingPeerNodeAddress, arbitratorNodeAddress, storage, btcWalletService);
}
@ -81,6 +84,7 @@ public final class SellerAsTakerTrade extends SellerTrade implements TakerTrade
proto.getIsCurrencyForTakerFeeBtc(),
proto.getTradePrice(),
proto.hasTradingPeerNodeAddress() ? NodeAddress.fromProto(proto.getTradingPeerNodeAddress()) : null,
proto.hasArbitratorNodeAddress() ? NodeAddress.fromProto(proto.getArbitratorNodeAddress()) : null,
storage,
btcWalletService),
proto,

View file

@ -31,6 +31,8 @@ import org.bitcoinj.core.Coin;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkArgument;
@Slf4j
@ -42,19 +44,21 @@ public abstract class SellerTrade extends Trade {
boolean isCurrencyForTakerFeeBtc,
long tradePrice,
NodeAddress tradingPeerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
Storage<? extends TradableList> storage,
BtcWalletService btcWalletService) {
super(offer, tradeAmount, txFee, takerFee, isCurrencyForTakerFeeBtc, tradePrice,
tradingPeerNodeAddress, storage, btcWalletService);
tradingPeerNodeAddress, arbitratorNodeAddress, storage, btcWalletService);
}
SellerTrade(Offer offer,
Coin txFee,
Coin takeOfferFee,
boolean isCurrencyForTakerFeeBtc,
@Nullable NodeAddress arbitratorNodeAddress,
Storage<? extends TradableList> storage,
BtcWalletService btcWalletService) {
super(offer, txFee, takeOfferFee, isCurrencyForTakerFeeBtc, storage, btcWalletService);
super(offer, txFee, takeOfferFee, isCurrencyForTakerFeeBtc, arbitratorNodeAddress, storage, btcWalletService);
}
public void onFiatPaymentReceived(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {

View file

@ -18,6 +18,7 @@
package bisq.core.trade;
import bisq.core.arbitration.Arbitrator;
import bisq.core.arbitration.ArbitratorManager;
import bisq.core.arbitration.Mediator;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
@ -29,18 +30,21 @@ import bisq.core.monetary.Volume;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferUtil;
import bisq.core.offer.OpenOfferManager;
import bisq.core.offer.availability.ArbitratorSelection;
import bisq.core.payment.AccountAgeWitnessService;
import bisq.core.payment.payload.PaymentMethod;
import bisq.core.proto.CoreProtoResolver;
import bisq.core.trade.protocol.ProcessModel;
import bisq.core.trade.protocol.TradeProtocol;
import bisq.core.trade.statistics.ReferralIdService;
import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.core.user.User;
import bisq.network.p2p.DecryptedMessageWithPubKey;
import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.P2PService;
import bisq.common.UserThread;
import bisq.common.app.Log;
import bisq.common.crypto.KeyRing;
import bisq.common.crypto.PubKeyRing;
@ -373,6 +377,7 @@ public abstract class Trade implements Tradable, Model {
Coin txFee,
Coin takerFee,
boolean isCurrencyForTakerFeeBtc,
@Nullable NodeAddress arbitratorNodeAddress,
Storage<? extends TradableList> storage,
BtcWalletService btcWalletService) {
this.offer = offer;
@ -381,6 +386,7 @@ public abstract class Trade implements Tradable, Model {
this.isCurrencyForTakerFeeBtc = isCurrencyForTakerFeeBtc;
this.storage = storage;
this.btcWalletService = btcWalletService;
this.arbitratorNodeAddress = arbitratorNodeAddress;
txFeeAsLong = txFee.value;
takerFeeAsLong = takerFee.value;
@ -398,10 +404,11 @@ public abstract class Trade implements Tradable, Model {
boolean isCurrencyForTakerFeeBtc,
long tradePrice,
NodeAddress tradingPeerNodeAddress,
@Nullable NodeAddress arbitratorNodeAddress,
Storage<? extends TradableList> storage,
BtcWalletService btcWalletService) {
this(offer, txFee, takerFee, isCurrencyForTakerFeeBtc, storage, btcWalletService);
this(offer, txFee, takerFee, isCurrencyForTakerFeeBtc, arbitratorNodeAddress, storage, btcWalletService);
this.tradePrice = tradePrice;
this.tradingPeerNodeAddress = tradingPeerNodeAddress;
@ -493,6 +500,8 @@ public abstract class Trade implements Tradable, Model {
User user,
FilterManager filterManager,
AccountAgeWitnessService accountAgeWitnessService,
TradeStatisticsManager tradeStatisticsManager,
ArbitratorManager arbitratorManager,
KeyRing keyRing,
boolean useSavingsWallet,
Coin fundsNeededForTrade) {
@ -508,10 +517,22 @@ public abstract class Trade implements Tradable, Model {
user,
filterManager,
accountAgeWitnessService,
tradeStatisticsManager,
arbitratorManager,
keyRing,
useSavingsWallet,
fundsNeededForTrade);
if (ArbitratorSelection.isNewRuleActivated()) {
Optional<Arbitrator> optionalArbitrator = processModel.getArbitratorManager().getArbitratorByNodeAddress(arbitratorNodeAddress);
if (optionalArbitrator.isPresent()) {
Arbitrator arbitrator = optionalArbitrator.get();
arbitratorBtcPubKey = arbitrator.getBtcPubKey();
arbitratorPubKeyRing = arbitrator.getPubKeyRing();
UserThread.runAfter(() -> this.persist(), 1);
}
}
createTradeProtocol();
// If we have already received a msg we apply it.
@ -666,6 +687,7 @@ public abstract class Trade implements Tradable, Model {
errorMessageProperty.set(errorMessage);
}
//TODO can be removed after new rule is actiavted
@SuppressWarnings("NullableProblems")
public void setArbitratorNodeAddress(NodeAddress arbitratorNodeAddress) {
this.arbitratorNodeAddress = arbitratorNodeAddress;

View file

@ -17,6 +17,7 @@
package bisq.core.trade;
import bisq.core.arbitration.ArbitratorManager;
import bisq.core.btc.exceptions.AddressEntryException;
import bisq.core.btc.model.AddressEntry;
import bisq.core.btc.wallet.BsqWalletService;
@ -116,6 +117,7 @@ public class TradeManager implements PersistedDataHost {
private final TradeStatisticsManager tradeStatisticsManager;
private final ReferralIdService referralIdService;
private final AccountAgeWitnessService accountAgeWitnessService;
private final ArbitratorManager arbitratorManager;
private final Clock clock;
private final Storage<TradableList<Trade>> tradableListStorage;
@ -149,6 +151,7 @@ public class TradeManager implements PersistedDataHost {
ReferralIdService referralIdService,
PersistenceProtoResolver persistenceProtoResolver,
AccountAgeWitnessService accountAgeWitnessService,
ArbitratorManager arbitratorManager,
Clock clock,
@Named(Storage.STORAGE_DIR) File storageDir) {
this.user = user;
@ -165,6 +168,7 @@ public class TradeManager implements PersistedDataHost {
this.tradeStatisticsManager = tradeStatisticsManager;
this.referralIdService = referralIdService;
this.accountAgeWitnessService = accountAgeWitnessService;
this.arbitratorManager = arbitratorManager;
this.clock = clock;
tradableListStorage = new Storage<>(storageDir, persistenceProtoResolver);
@ -313,14 +317,16 @@ public class TradeManager implements PersistedDataHost {
Optional<OpenOffer> openOfferOptional = openOfferManager.getOpenOfferById(payDepositRequest.getTradeId());
if (openOfferOptional.isPresent() && openOfferOptional.get().getState() == OpenOffer.State.AVAILABLE) {
Offer offer = openOfferOptional.get().getOffer();
openOfferManager.reserveOpenOffer(openOfferOptional.get());
OpenOffer openOffer = openOfferOptional.get();
Offer offer = openOffer.getOffer();
openOfferManager.reserveOpenOffer(openOffer);
Trade trade;
if (offer.isBuyOffer())
trade = new BuyerAsMakerTrade(offer,
Coin.valueOf(payDepositRequest.getTxFee()),
Coin.valueOf(payDepositRequest.getTakerFee()),
payDepositRequest.isCurrencyForTakerFeeBtc(),
openOffer.getArbitratorNodeAddress(),
tradableListStorage,
btcWalletService);
else
@ -328,6 +334,7 @@ public class TradeManager implements PersistedDataHost {
Coin.valueOf(payDepositRequest.getTxFee()),
Coin.valueOf(payDepositRequest.getTakerFee()),
payDepositRequest.isCurrencyForTakerFeeBtc(),
openOffer.getArbitratorNodeAddress(),
tradableListStorage,
btcWalletService);
@ -356,6 +363,8 @@ public class TradeManager implements PersistedDataHost {
user,
filterManager,
accountAgeWitnessService,
tradeStatisticsManager,
arbitratorManager,
keyRing,
useSavingsWallet,
fundsNeededForTrade);
@ -436,6 +445,7 @@ public class TradeManager implements PersistedDataHost {
isCurrencyForTakerFeeBtc,
tradePrice,
model.getPeerNodeAddress(),
model.getSelectedArbitrator(),
tradableListStorage,
btcWalletService);
else
@ -446,6 +456,7 @@ public class TradeManager implements PersistedDataHost {
isCurrencyForTakerFeeBtc,
tradePrice,
model.getPeerNodeAddress(),
model.getSelectedArbitrator(),
tradableListStorage,
btcWalletService);

View file

@ -36,7 +36,6 @@ import bisq.core.trade.protocol.tasks.maker.MakerProcessDepositTxPublishedMessag
import bisq.core.trade.protocol.tasks.maker.MakerProcessPayDepositRequest;
import bisq.core.trade.protocol.tasks.maker.MakerSendPublishDepositTxRequest;
import bisq.core.trade.protocol.tasks.maker.MakerSetupDepositTxListener;
import bisq.core.trade.protocol.tasks.maker.MakerVerifyArbitratorSelection;
import bisq.core.trade.protocol.tasks.maker.MakerVerifyMediatorSelection;
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerAccount;
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment;
@ -132,7 +131,6 @@ public class BuyerAsMakerProtocol extends TradeProtocol implements BuyerProtocol
taskRunner.addTasks(
MakerProcessPayDepositRequest.class,
CheckIfPeerIsBanned.class,
MakerVerifyArbitratorSelection.class,
MakerVerifyMediatorSelection.class,
MakerVerifyTakerAccount.class,
VerifyPeersAccountAgeWitness.class,

View file

@ -18,9 +18,10 @@
package bisq.core.trade.protocol;
import bisq.core.app.BisqEnvironment;
import bisq.core.arbitration.ArbitratorManager;
import bisq.core.btc.model.RawTransactionInput;
import bisq.core.btc.wallet.BsqWalletService;
import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.model.RawTransactionInput;
import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.filter.FilterManager;
import bisq.core.network.MessageState;
@ -35,6 +36,7 @@ import bisq.core.trade.Trade;
import bisq.core.trade.TradeManager;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.statistics.ReferralIdService;
import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.core.user.User;
import bisq.network.p2p.AckMessage;
@ -82,6 +84,8 @@ public class ProcessModel implements Model, PersistablePayload {
transient private User user;
transient private FilterManager filterManager;
transient private AccountAgeWitnessService accountAgeWitnessService;
transient private TradeStatisticsManager tradeStatisticsManager;
transient private ArbitratorManager arbitratorManager;
transient private KeyRing keyRing;
transient private P2PService p2PService;
transient private ReferralIdService referralIdService;
@ -229,6 +233,8 @@ public class ProcessModel implements Model, PersistablePayload {
User user,
FilterManager filterManager,
AccountAgeWitnessService accountAgeWitnessService,
TradeStatisticsManager tradeStatisticsManager,
ArbitratorManager arbitratorManager,
KeyRing keyRing,
boolean useSavingsWallet,
Coin fundsNeededForTrade) {
@ -242,6 +248,8 @@ public class ProcessModel implements Model, PersistablePayload {
this.user = user;
this.filterManager = filterManager;
this.accountAgeWitnessService = accountAgeWitnessService;
this.tradeStatisticsManager = tradeStatisticsManager;
this.arbitratorManager = arbitratorManager;
this.keyRing = keyRing;
this.p2PService = p2PService;
this.useSavingsWallet = useSavingsWallet;

View file

@ -32,7 +32,6 @@ import bisq.core.trade.protocol.tasks.maker.MakerProcessDepositTxPublishedMessag
import bisq.core.trade.protocol.tasks.maker.MakerProcessPayDepositRequest;
import bisq.core.trade.protocol.tasks.maker.MakerSendPublishDepositTxRequest;
import bisq.core.trade.protocol.tasks.maker.MakerSetupDepositTxListener;
import bisq.core.trade.protocol.tasks.maker.MakerVerifyArbitratorSelection;
import bisq.core.trade.protocol.tasks.maker.MakerVerifyMediatorSelection;
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerAccount;
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment;
@ -127,7 +126,6 @@ public class SellerAsMakerProtocol extends TradeProtocol implements SellerProtoc
taskRunner.addTasks(
MakerProcessPayDepositRequest.class,
CheckIfPeerIsBanned.class,
MakerVerifyArbitratorSelection.class,
MakerVerifyMediatorSelection.class,
MakerVerifyTakerAccount.class,
VerifyPeersAccountAgeWitness.class,

View file

@ -22,14 +22,18 @@ import bisq.core.offer.OfferPayload;
import bisq.core.trade.Trade;
import bisq.core.trade.statistics.TradeStatistics2;
import bisq.network.p2p.NodeAddress;
import bisq.common.taskrunner.TaskRunner;
import java.util.HashMap;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
public class PublishTradeStatistics extends TradeTask {
public PublishTradeStatistics(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
@ -40,12 +44,18 @@ public class PublishTradeStatistics extends TradeTask {
try {
runInterceptHook();
if (trade.getDepositTx() != null) {
Map<String, String> extraDataMap = null;
Map<String, String> extraDataMap = new HashMap<>();
if (processModel.getReferralIdService().getOptionalReferralId().isPresent()) {
extraDataMap = new HashMap<>();
extraDataMap.put(OfferPayload.REFERRAL_ID, processModel.getReferralIdService().getOptionalReferralId().get());
}
NodeAddress arbitratorNodeAddress = trade.getArbitratorNodeAddress();
if (arbitratorNodeAddress != null) {
// The first 4 chars are sufficient to identify an arbitrator
String address = arbitratorNodeAddress.getHostName().substring(0, 4);
extraDataMap.put(TradeStatistics2.ARBITRATOR_ADDRESS, address);
}
Offer offer = trade.getOffer();
checkNotNull(offer, "offer must not ne null");
checkNotNull(trade.getTradeAmount(), "trade.getTradeAmount() must not ne null");

View file

@ -25,12 +25,9 @@ import bisq.core.btc.wallet.BtcWalletService;
import bisq.core.btc.wallet.TradeWalletService;
import bisq.core.btc.wallet.TxBroadcaster;
import bisq.core.btc.wallet.WalletService;
import bisq.core.offer.availability.ArbitratorSelection;
import bisq.core.trade.Trade;
import bisq.core.trade.protocol.ArbitratorSelectionRule;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.core.user.User;
import bisq.network.p2p.NodeAddress;
import bisq.common.UserThread;
import bisq.common.taskrunner.TaskRunner;
@ -42,8 +39,6 @@ import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
public class CreateTakerFeeTx extends TradeTask {
private Transaction tradeFeeTx;
@ -58,13 +53,8 @@ public class CreateTakerFeeTx extends TradeTask {
try {
runInterceptHook();
User user = processModel.getUser();
NodeAddress selectedArbitratorNodeAddress = ArbitratorSelectionRule.select(user.getAcceptedArbitratorAddresses(),
processModel.getOffer());
log.debug("selectedArbitratorAddress " + selectedArbitratorNodeAddress);
Arbitrator selectedArbitrator = user.getAcceptedArbitratorByAddress(selectedArbitratorNodeAddress);
checkNotNull(selectedArbitrator, "selectedArbitrator must not be null at CreateTakeOfferFeeTx");
Arbitrator arbitrator = ArbitratorSelection.getLeastUsedArbitrator(processModel.getTradeStatisticsManager(),
processModel.getArbitratorManager());
BtcWalletService walletService = processModel.getBtcWalletService();
String id = processModel.getOffer().getId();
AddressEntry addressEntry = walletService.getOrCreateAddressEntry(id, AddressEntry.Context.OFFER_FUNDING);
@ -83,7 +73,7 @@ public class CreateTakerFeeTx extends TradeTask {
processModel.isUseSavingsWallet(),
trade.getTakerFee(),
trade.getTxFee(),
selectedArbitrator.getBtcAddress(),
arbitrator.getBtcAddress(),
new TxBroadcaster.Callback() {
@Override
public void onSuccess(Transaction transaction) {

View file

@ -17,6 +17,7 @@
package bisq.core.trade.protocol.tasks.taker;
import bisq.core.offer.availability.ArbitratorSelection;
import bisq.core.trade.Trade;
import bisq.core.trade.protocol.ArbitratorSelectionRule;
import bisq.core.trade.protocol.tasks.TradeTask;
@ -37,7 +38,10 @@ public class TakerSelectArbitrator extends TradeTask {
try {
runInterceptHook();
trade.setArbitratorNodeAddress(ArbitratorSelectionRule.select(processModel.getUser().getAcceptedArbitratorAddresses(), processModel.getOffer()));
// TODO can be removed after new rule is activated
if (!ArbitratorSelection.isNewRuleActivated()) {
trade.setArbitratorNodeAddress(ArbitratorSelectionRule.select(processModel.getUser().getAcceptedArbitratorAddresses(), processModel.getOffer()));
}
complete();
} catch (Throwable t) {

View file

@ -66,6 +66,8 @@ import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
@Value
public final class TradeStatistics2 implements LazyProcessedPayload, PersistableNetworkPayload, PersistableEnvelope, CapabilityRequiringPayload {
public static final String ARBITRATOR_ADDRESS = "arbAddr";
private final OfferPayload.Direction direction;
private final String baseCurrency;
private final String counterCurrency;

View file

@ -380,6 +380,7 @@ public final class User implements PersistedDataHost {
return userPayload.getRegisteredMediator();
}
//TODO
@Nullable
public List<Arbitrator> getAcceptedArbitrators() {
return userPayload.getAcceptedArbitrators();

View file

@ -828,7 +828,6 @@ settings.tab.about=About
setting.preferences.general=General preferences
setting.preferences.explorer=Bitcoin block explorer:
setting.preferences.deviation=Max. deviation from market price:
setting.preferences.autoSelectArbitrators=Auto select arbitrators:
setting.preferences.avoidStandbyMode=Avoid standby mode:
setting.preferences.deviationToLarge=Values higher than {0}% are not allowed.
setting.preferences.txFee=Withdrawal transaction fee (satoshis/byte):
@ -941,7 +940,6 @@ Bisq is a decentralized exchange meaning all of your data is kept on your co
account.menu.paymentAccount=National currency accounts
account.menu.altCoinsAccountView=Altcoin accounts
account.menu.arbitratorSelection=Arbitrator selection
account.menu.password=Wallet password
account.menu.seedWords=Wallet seed
account.menu.backup=Backup
@ -1649,7 +1647,6 @@ offerDetailsWindow.agree=I agree:
offerDetailsWindow.tac=Terms and conditions:
offerDetailsWindow.confirm.maker=Confirm: Place offer to {0} bitcoin
offerDetailsWindow.confirm.taker=Confirm: Take offer to {0} bitcoin
offerDetailsWindow.warn.noArbitrator=You have no arbitrator selected.\nPlease select at least one arbitrator.
offerDetailsWindow.creationDate=Creation date:
offerDetailsWindow.makersOnion=Maker's onion address:
@ -1783,8 +1780,7 @@ popup.warning.tradePeriod.halfReached=Your trade with ID {0} has reached the hal
popup.warning.tradePeriod.ended=Your trade with ID {0} has reached the max. allowed trading period and is not completed.\n\nThe trade period ended on {1}\n\nPlease check your trade at \"Portfolio/Open trades\" for contacting the arbitrator.
popup.warning.noTradingAccountSetup.headline=You have not setup a trading account
popup.warning.noTradingAccountSetup.msg=You need to setup a national currency or altcoin account before you can create an offer.\nDo you want to setup an account?
popup.warning.noArbitratorSelected.headline=You don't have an arbitrator selected.
popup.warning.noArbitratorSelected.msg=You need to setup at least one arbitrator to be able to trade.\nDo you want to do this now?
popup.warning.noArbitratorsAvailable=There are no arbitrators available.
popup.warning.notFullyConnected=You need to wait until you are fully connected to the network.\nThat might take up to about 2 minutes at startup.
popup.warning.notSufficientConnectionsToBtcNetwork=You need to wait until you have at least {0} connections to the Bitcoin network.
popup.warning.downloadNotComplete=You need to wait until the download of missing Bitcoin blocks is complete.
@ -1952,7 +1948,6 @@ txIdTextField.blockExplorerIcon.tooltip=Open a blockchain explorer with that tra
navigation.account=\"Account\"
navigation.account.walletSeed=\"Account/Wallet seed\"
navigation.arbitratorSelection=\"Arbitrator selection\"
navigation.funds.availableForWithdrawal=\"Fund/Send funds\"
navigation.portfolio.myOpenOffers=\"Portfolio/My open offers\"
navigation.portfolio.pending=\"Portfolio/Open trades\"

View file

@ -37,7 +37,7 @@ public class OpenOfferManagerTest {
final OpenOfferManager manager = new OpenOfferManager(null, null, p2PService,
null, null, null, offerBookService,
null, null, null, null,
null);
null, null, null);
AtomicBoolean startEditOfferSuccessful = new AtomicBoolean(false);
@ -72,7 +72,7 @@ public class OpenOfferManagerTest {
final OpenOfferManager manager = new OpenOfferManager(null, null, p2PService,
null, null, null, offerBookService,
null, null, null, null,
null);
null, null, null);
AtomicBoolean startEditOfferSuccessful = new AtomicBoolean(false);
@ -99,7 +99,7 @@ public class OpenOfferManagerTest {
final OpenOfferManager manager = new OpenOfferManager(null, null, p2PService,
null, null, null, offerBookService,
null, null, null, null,
null);
null, null, null);
AtomicBoolean startEditOfferSuccessful = new AtomicBoolean(false);

View file

@ -0,0 +1,70 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.offer.availability;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class ArbitratorSelectionTest {
@Test
public void testGetLeastUsedArbitrator() {
// We get least used selected
List<String> lastAddressesUsedInTrades = Arrays.asList("arb1", "arb2", "arb1");
Set<String> arbitrators = new HashSet<>(Arrays.asList("arb1", "arb2"));
String result = ArbitratorSelection.getLeastUsedArbitrator(lastAddressesUsedInTrades, arbitrators);
assertEquals("arb2", result);
// if all are same we use first according to alphanumeric sorting
lastAddressesUsedInTrades = Arrays.asList("arb1", "arb2", "arb3");
arbitrators = new HashSet<>(Arrays.asList("arb1", "arb2", "arb3"));
result = ArbitratorSelection.getLeastUsedArbitrator(lastAddressesUsedInTrades, arbitrators);
assertEquals("arb1", result);
lastAddressesUsedInTrades = Arrays.asList("xxx", "ccc", "aaa");
arbitrators = new HashSet<>(Arrays.asList("aaa", "ccc", "xxx"));
result = ArbitratorSelection.getLeastUsedArbitrator(lastAddressesUsedInTrades, arbitrators);
assertEquals("aaa", result);
lastAddressesUsedInTrades = Arrays.asList("333", "000", "111");
arbitrators = new HashSet<>(Arrays.asList("111", "333", "000"));
result = ArbitratorSelection.getLeastUsedArbitrator(lastAddressesUsedInTrades, arbitrators);
assertEquals("000", result);
// if winner is not in our arb list we use our arb from arbitrators even if never used in trades
lastAddressesUsedInTrades = Arrays.asList("arb1", "arb2", "arb3");
arbitrators = new HashSet<>(Arrays.asList("arb4"));
result = ArbitratorSelection.getLeastUsedArbitrator(lastAddressesUsedInTrades, arbitrators);
assertEquals("arb4", result);
// if winner (arb2) is not in our arb list we use our arb from arbitrators
lastAddressesUsedInTrades = Arrays.asList("arb1", "arb1", "arb1", "arb2");
arbitrators = new HashSet<>(Arrays.asList("arb1"));
result = ArbitratorSelection.getLeastUsedArbitrator(lastAddressesUsedInTrades, arbitrators);
assertEquals("arb1", result);
// arb1 is used least
lastAddressesUsedInTrades = Arrays.asList("arb1", "arb2", "arb2", "arb2", "arb1", "arb1", "arb2");
arbitrators = new HashSet<>(Arrays.asList("arb1", "arb2"));
result = ArbitratorSelection.getLeastUsedArbitrator(lastAddressesUsedInTrades, arbitrators);
assertEquals("arb1", result);
}
}

View file

@ -1,62 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.desktop.main.account.content.arbitratorselection;
import bisq.core.arbitration.Arbitrator;
import bisq.core.util.BSFormatter;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import java.util.Date;
public class ArbitratorListItem {
public final Arbitrator arbitrator;
private final BSFormatter formatter;
private final BooleanProperty isSelected = new SimpleBooleanProperty();
public ArbitratorListItem(Arbitrator arbitrator, BSFormatter formatter) {
this.arbitrator = arbitrator;
this.formatter = formatter;
}
public String getAddressString() {
return arbitrator != null ? arbitrator.getNodeAddress().getFullAddress() : "";
}
public String getLanguageCodes() {
return arbitrator != null && arbitrator.getLanguageCodes() != null ?
formatter.languageCodesToString(arbitrator.getLanguageCodes()) : "";
}
public String getRegistrationDate() {
return arbitrator != null ? formatter.formatDate(new Date(arbitrator.getRegistrationDate())) : "";
}
public boolean getIsSelected() {
return isSelected.get();
}
public BooleanProperty isSelectedProperty() {
return isSelected;
}
public void setIsSelected(boolean isSelected) {
this.isSelected.set(isSelected);
}
}

View file

@ -1,34 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ This file is part of Bisq.
~
~ Bisq is free software: you can redistribute it and/or modify it
~ under the terms of the GNU Affero General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or (at
~ your option) any later version.
~
~ Bisq is distributed in the hope that it will be useful, but WITHOUT
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
~ License for more details.
~
~ You should have received a copy of the GNU Affero General Public License
~ along with Bisq. If not, see <http://www.gnu.org/licenses/>.
-->
<?import javafx.scene.layout.AnchorPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<GridPane fx:id="root" fx:controller="bisq.desktop.main.account.content.arbitratorselection.ArbitratorSelectionView"
hgap="5.0" vgap="5.0"
AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="20.0"
AnchorPane.rightAnchor="25.0" AnchorPane.topAnchor="20.0"
xmlns:fx="http://javafx.com/fxml">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" halignment="RIGHT" minWidth="140.0"/>
<ColumnConstraints hgrow="ALWAYS" minWidth="300.0"/>
</columnConstraints>
</GridPane>

View file

@ -1,359 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.desktop.main.account.content.arbitratorselection;
import bisq.desktop.common.view.ActivatableViewAndModel;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.AutoTooltipButton;
import bisq.desktop.components.AutoTooltipCheckBox;
import bisq.desktop.components.AutoTooltipLabel;
import bisq.desktop.components.AutoTooltipTableColumn;
import bisq.desktop.components.TableGroupHeadline;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.util.FormBuilder;
import bisq.desktop.util.ImageUtil;
import bisq.desktop.util.Layout;
import bisq.core.locale.LanguageUtil;
import bisq.core.locale.Res;
import bisq.common.UserThread;
import bisq.common.util.Tuple2;
import javax.inject.Inject;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
import javafx.scene.control.Tooltip;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.GridPane;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.VPos;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.value.ChangeListener;
import javafx.collections.ListChangeListener;
import javafx.util.Callback;
import javafx.util.StringConverter;
import static bisq.desktop.util.FormBuilder.addCheckBox;
import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
@FxmlView
public class ArbitratorSelectionView extends ActivatableViewAndModel<GridPane, ArbitratorSelectionViewModel> {
private final ArbitratorSelectionViewModel model;
private ListView<String> languagesListView;
private ComboBox<String> languageComboBox;
private TableView<ArbitratorListItem> tableView;
private int gridRow = 0;
private CheckBox autoSelectAllMatchingCheckBox;
private ListChangeListener<String> listChangeListener;
private ListChangeListener<String> languageCodesListChangeListener;
private ChangeListener<Boolean> isSelectedChangeListener;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private ArbitratorSelectionView(ArbitratorSelectionViewModel model) {
super(model);
this.model = model;
}
@Override
public void initialize() {
addLanguageGroup();
addArbitratorsGroup();
listChangeListener = c -> languagesListView.setPrefHeight(languagesListView.getItems().size() * Layout.LIST_ROW_HEIGHT + 2);
}
@Override
protected void activate() {
languagesListView.getItems().addListener(listChangeListener);
languageComboBox.setItems(model.allLanguageCodes);
languagesListView.setItems(model.languageCodes);
languagesListView.setPrefHeight(languagesListView.getItems().size() * Layout.LIST_ROW_HEIGHT + 2);
tableView.setItems(model.arbitratorListItems);
// TODO should scale with stage resize
tableView.setPrefHeight(200);
autoSelectAllMatchingCheckBox.setSelected(model.getAutoSelectArbitrators());
}
@Override
protected void deactivate() {
languagesListView.getItems().removeListener(listChangeListener);
if (languageCodesListChangeListener != null)
model.languageCodes.removeListener(languageCodesListChangeListener);
}
///////////////////////////////////////////////////////////////////////////////////////////
// UI actions
///////////////////////////////////////////////////////////////////////////////////////////
private void onAddLanguage() {
model.onAddLanguage(languageComboBox.getSelectionModel().getSelectedItem());
UserThread.execute(() -> languageComboBox.getSelectionModel().clearSelection());
}
private void onRemoveLanguage(String locale) {
model.onRemoveLanguage(locale);
if (languagesListView.getItems().size() == 0) {
new Popup<>().warning(Res.get("account.arbitratorSelection.minOneArbitratorRequired")).show();
model.onAddLanguage(LanguageUtil.getDefaultLanguageLocaleAsCode());
}
}
private void onAddArbitrator(ArbitratorListItem arbitratorListItem) {
model.onAddArbitrator(arbitratorListItem.arbitrator);
}
private void onRemoveArbitrator(ArbitratorListItem arbitratorListItem) {
model.onRemoveArbitrator(arbitratorListItem.arbitrator);
}
///////////////////////////////////////////////////////////////////////////////////////////
// UI builder
///////////////////////////////////////////////////////////////////////////////////////////
private void addLanguageGroup() {
addTitledGroupBg(root, gridRow, 1, Res.get("account.arbitratorSelection.whichLanguages"));
Tuple2<Label, ListView<String>> tuple = FormBuilder.addLabelListView(root, gridRow, Res.get("shared.yourLanguage"), Layout.FIRST_ROW_DISTANCE);
GridPane.setValignment(tuple.first, VPos.TOP);
languagesListView = tuple.second;
languagesListView.setMinHeight(3 * Layout.LIST_ROW_HEIGHT + 2);
languagesListView.setMaxHeight(6 * Layout.LIST_ROW_HEIGHT + 2);
languagesListView.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
@Override
public ListCell<String> call(ListView<String> list) {
return new ListCell<String>() {
final Label label = new AutoTooltipLabel();
final ImageView icon = ImageUtil.getImageViewById(ImageUtil.REMOVE_ICON);
final Button removeButton = new AutoTooltipButton("", icon);
final AnchorPane pane = new AnchorPane(label, removeButton);
{
label.setLayoutY(5);
removeButton.setId("icon-button");
AnchorPane.setRightAnchor(removeButton, 0d);
}
@Override
public void updateItem(final String item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
label.setText(LanguageUtil.getDisplayName(item));
removeButton.setOnAction(e -> onRemoveLanguage(item));
setGraphic(pane);
} else {
setGraphic(null);
}
}
};
}
});
languageComboBox = FormBuilder.<String>addLabelComboBox(root, ++gridRow, "", 15).second;
languageComboBox.setPromptText(Res.get("shared.addLanguage"));
languageComboBox.setButtonCell(new ListCell<String>() {
@Override
protected void updateItem(final String item, boolean empty) {
super.updateItem(item, empty) ;
if (empty || item == null) {
setText(Res.get("shared.addLanguage"));
} else {
setText(LanguageUtil.getDisplayName(item));
}
}
});
languageComboBox.setConverter(new StringConverter<String>() {
@Override
public String toString(String code) {
return LanguageUtil.getDisplayName(code);
}
@Override
public String fromString(String s) {
return null;
}
});
languageComboBox.setOnHiding(e -> onAddLanguage());
}
private void addArbitratorsGroup() {
TableGroupHeadline tableGroupHeadline = new TableGroupHeadline(Res.get("account.arbitratorSelection.whichDoYouAccept"));
GridPane.setRowIndex(tableGroupHeadline, ++gridRow);
GridPane.setColumnSpan(tableGroupHeadline, 2);
GridPane.setMargin(tableGroupHeadline, new Insets(40, -10, -10, -10));
root.getChildren().add(tableGroupHeadline);
tableView = new TableView<>();
GridPane.setRowIndex(tableView, gridRow);
GridPane.setColumnSpan(tableView, 2);
GridPane.setMargin(tableView, new Insets(60, -10, 5, -10));
root.getChildren().add(tableView);
autoSelectAllMatchingCheckBox = addCheckBox(root, ++gridRow, Res.get("account.arbitratorSelection.autoSelect"));
GridPane.setColumnSpan(autoSelectAllMatchingCheckBox, 2);
GridPane.setHalignment(autoSelectAllMatchingCheckBox, HPos.LEFT);
GridPane.setColumnIndex(autoSelectAllMatchingCheckBox, 0);
GridPane.setMargin(autoSelectAllMatchingCheckBox, new Insets(0, -10, 0, -10));
autoSelectAllMatchingCheckBox.setOnAction(event ->
model.setAutoSelectArbitrators(autoSelectAllMatchingCheckBox.isSelected()));
TableColumn<ArbitratorListItem, String> dateColumn = new AutoTooltipTableColumn<>(Res.get("account.arbitratorSelection.regDate"));
dateColumn.setSortable(false);
dateColumn.setCellValueFactory(param -> new ReadOnlyObjectWrapper<>(param.getValue().getRegistrationDate()));
dateColumn.setMinWidth(140);
dateColumn.setMaxWidth(140);
TableColumn<ArbitratorListItem, String> nameColumn = new AutoTooltipTableColumn<>(Res.get("shared.onionAddress"));
nameColumn.setSortable(false);
nameColumn.setCellValueFactory(param -> new ReadOnlyObjectWrapper<>(param.getValue().getAddressString()));
nameColumn.setMinWidth(90);
TableColumn<ArbitratorListItem, String> languagesColumn = new AutoTooltipTableColumn<>(Res.get("account.arbitratorSelection.languages"));
languagesColumn.setSortable(false);
languagesColumn.setCellValueFactory(param -> new ReadOnlyObjectWrapper<>(param.getValue().getLanguageCodes()));
languagesColumn.setMinWidth(130);
TableColumn<ArbitratorListItem, ArbitratorListItem> selectionColumn = new AutoTooltipTableColumn<ArbitratorListItem, ArbitratorListItem>(
Res.get("shared.accept")) {
{
setMinWidth(60);
setMaxWidth(60);
setSortable(false);
}
};
selectionColumn.setCellValueFactory((arbitrator) -> new ReadOnlyObjectWrapper<>(arbitrator.getValue()));
selectionColumn.setCellFactory(
new Callback<TableColumn<ArbitratorListItem, ArbitratorListItem>, TableCell<ArbitratorListItem, ArbitratorListItem>>() {
@Override
public TableCell<ArbitratorListItem, ArbitratorListItem> call(TableColumn<ArbitratorListItem, ArbitratorListItem> column) {
return new TableCell<ArbitratorListItem, ArbitratorListItem>() {
private final CheckBox checkBox = new AutoTooltipCheckBox();
private TableRow tableRow;
private BooleanProperty selectedProperty;
private void updateDisableState(final ArbitratorListItem item) {
boolean selected = model.isAcceptedArbitrator(item.arbitrator);
item.setIsSelected(selected);
boolean hasMatchingLanguage = model.hasMatchingLanguage(item.arbitrator);
if (!hasMatchingLanguage) {
model.onRemoveArbitrator(item.arbitrator);
if (selected)
item.setIsSelected(false);
}
boolean isMyOwnRegisteredArbitrator = model.isMyOwnRegisteredArbitrator(item.arbitrator);
checkBox.setDisable(!hasMatchingLanguage || isMyOwnRegisteredArbitrator);
tableRow = getTableRow();
if (tableRow != null) {
tableRow.setOpacity(hasMatchingLanguage && !isMyOwnRegisteredArbitrator ? 1 : 0.4);
if (isMyOwnRegisteredArbitrator) {
String text = Res.get("account.arbitratorSelection.cannotSelectHimself");
tableRow.setTooltip(new Tooltip(text));
tableRow.setOnMouseClicked(e -> new Popup<>().warning(
text).show());
} else if (!hasMatchingLanguage) {
tableRow.setTooltip(new Tooltip(Res.get("account.arbitratorSelection.noMatchingLang")));
tableRow.setOnMouseClicked(e -> new Popup<>()
.warning(Res.get("account.arbitratorSelection.noLang")).show());
} else {
tableRow.setOnMouseClicked(null);
tableRow.setTooltip(null);
}
}
}
@Override
public void updateItem(final ArbitratorListItem item, boolean empty) {
super.updateItem(item, empty);
if (item != null && !empty) {
selectedProperty = item.isSelectedProperty();
languageCodesListChangeListener = c -> updateDisableState(item);
model.languageCodes.addListener(languageCodesListChangeListener);
isSelectedChangeListener = (observable, oldValue, newValue) -> checkBox.setSelected(newValue);
selectedProperty.addListener(isSelectedChangeListener);
checkBox.setSelected(model.isAcceptedArbitrator(item.arbitrator));
checkBox.setOnAction(e -> {
if (checkBox.isSelected()) {
onAddArbitrator(item);
} else if (model.isDeselectAllowed(item)) {
onRemoveArbitrator(item);
} else {
new Popup<>().warning(Res.get("account.arbitratorSelection.minOne")).show();
checkBox.setSelected(true);
}
item.setIsSelected(checkBox.isSelected());
}
);
updateDisableState(item);
setGraphic(checkBox);
} else {
model.languageCodes.removeListener(languageCodesListChangeListener);
if (selectedProperty != null)
selectedProperty.removeListener(isSelectedChangeListener);
setGraphic(null);
checkBox.setOnAction(null);
if (tableRow != null)
tableRow.setOnMouseClicked(null);
}
}
};
}
});
//noinspection unchecked
tableView.getColumns().addAll(dateColumn, nameColumn, languagesColumn, selectionColumn);
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
}
}

View file

@ -1,175 +0,0 @@
/*
* This file is part of Bisq.
*
* Bisq is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bisq is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.desktop.main.account.content.arbitratorselection;
import bisq.desktop.common.model.ActivatableDataModel;
import bisq.core.arbitration.Arbitrator;
import bisq.core.arbitration.ArbitratorManager;
import bisq.core.locale.LanguageUtil;
import bisq.core.user.Preferences;
import bisq.core.user.User;
import bisq.core.util.BSFormatter;
import bisq.network.p2p.NodeAddress;
import bisq.common.crypto.KeyRing;
import com.google.inject.Inject;
import javafx.collections.FXCollections;
import javafx.collections.MapChangeListener;
import javafx.collections.ObservableList;
import java.util.stream.Collectors;
class ArbitratorSelectionViewModel extends ActivatableDataModel {
private final User user;
private final ArbitratorManager arbitratorManager;
private final Preferences preferences;
private final KeyRing keyRing;
private final BSFormatter formatter;
final ObservableList<String> languageCodes = FXCollections.observableArrayList();
final ObservableList<ArbitratorListItem> arbitratorListItems = FXCollections.observableArrayList();
final ObservableList<String> allLanguageCodes = FXCollections.observableArrayList(LanguageUtil.getAllLanguageCodes());
private final MapChangeListener<NodeAddress, Arbitrator> arbitratorMapChangeListener;
@Inject
public ArbitratorSelectionViewModel(User user, ArbitratorManager arbitratorManager, Preferences preferences,
KeyRing keyRing, BSFormatter formatter) {
this.user = user;
this.arbitratorManager = arbitratorManager;
this.preferences = preferences;
this.keyRing = keyRing;
this.formatter = formatter;
arbitratorMapChangeListener = change -> applyArbitratorMap();
}
private void applyArbitratorMap() {
arbitratorListItems.clear();
arbitratorListItems.addAll(arbitratorManager.getArbitratorsObservableMap().values().stream()
.map(e -> new ArbitratorListItem(e, formatter)).collect(Collectors.toList()));
}
@Override
protected void activate() {
languageCodes.setAll(user.getAcceptedLanguageLocaleCodes());
arbitratorManager.getArbitratorsObservableMap().addListener(arbitratorMapChangeListener);
arbitratorManager.updateArbitratorMap();
applyArbitratorMap();
updateAutoSelectArbitrators();
}
@Override
protected void deactivate() {
arbitratorManager.getArbitratorsObservableMap().removeListener(arbitratorMapChangeListener);
}
void onAddLanguage(String code) {
if (code != null) {
boolean changed = user.addAcceptedLanguageLocale(code);
if (changed)
languageCodes.add(code);
}
updateAutoSelectArbitrators();
}
void onRemoveLanguage(String code) {
if (code != null) {
boolean changed = user.removeAcceptedLanguageLocale(code);
if (changed)
languageCodes.remove(code);
}
updateAutoSelectArbitrators();
}
void onAddArbitrator(Arbitrator arbitrator) {
if (!arbitratorIsTrader(arbitrator)) {
user.addAcceptedArbitrator(arbitrator);
// TODO we mirror arbitrator data for mediator as long we have not impl. it in the UI
user.addAcceptedMediator(ArbitratorManager.getMediator(arbitrator));
}
}
void onRemoveArbitrator(Arbitrator arbitrator) {
if (arbitrator != null) {
user.removeAcceptedArbitrator(arbitrator);
// TODO we mirror arbitrator data for mediator as long we have not impl. it in the UI
user.removeAcceptedMediator(ArbitratorManager.getMediator(arbitrator));
}
}
public boolean isDeselectAllowed(ArbitratorListItem arbitratorListItem) {
return arbitratorListItem != null
&& user.getAcceptedArbitrators() != null
&& user.getAcceptedArbitrators().size() > 1;
}
public boolean isAcceptedArbitrator(Arbitrator arbitrator) {
return arbitrator != null &&
user.getAcceptedArbitrators() != null &&
user.getAcceptedArbitrators().contains(arbitrator) &&
!isMyOwnRegisteredArbitrator(arbitrator);
}
public boolean arbitratorIsTrader(Arbitrator arbitrator) {
return keyRing.getPubKeyRing().equals(arbitrator.getPubKeyRing());
}
public boolean hasMatchingLanguage(Arbitrator arbitrator) {
return user.hasMatchingLanguage(arbitrator);
}
public boolean isMyOwnRegisteredArbitrator(Arbitrator arbitrator) {
return user.isMyOwnRegisteredArbitrator(arbitrator);
}
private void updateAutoSelectArbitrators() {
if (preferences.isAutoSelectArbitrators()) {
arbitratorListItems.stream().forEach(item -> {
Arbitrator arbitrator = item.arbitrator;
if (!isMyOwnRegisteredArbitrator(arbitrator)) {
if (hasMatchingLanguage(arbitrator)) {
onAddArbitrator(arbitrator);
item.setIsSelected(true);
} else {
onRemoveArbitrator(arbitrator);
item.setIsSelected(false);
}
} else {
item.setIsSelected(false);
}
});
}
}
public void setAutoSelectArbitrators(boolean doAutoSelect) {
preferences.setAutoSelectArbitrators(doAutoSelect);
updateAutoSelectArbitrators();
}
public boolean getAutoSelectArbitrators() {
return preferences.isAutoSelectArbitrators();
}
}

View file

@ -28,7 +28,6 @@ import bisq.desktop.components.AutoTooltipToggleButton;
import bisq.desktop.main.MainView;
import bisq.desktop.main.account.AccountView;
import bisq.desktop.main.account.content.altcoinaccounts.AltCoinAccountsView;
import bisq.desktop.main.account.content.arbitratorselection.ArbitratorSelectionView;
import bisq.desktop.main.account.content.backup.BackupView;
import bisq.desktop.main.account.content.fiataccounts.FiatAccountsView;
import bisq.desktop.main.account.content.notifications.MobileNotificationsView;
@ -64,7 +63,7 @@ public class AccountSettingsView extends ActivatableViewAndModel {
private final Navigation navigation;
private MenuItem paymentAccount, altCoinsAccountView, arbitratorSelection, notifications, password, seedWords, backup;
private MenuItem paymentAccount, altCoinsAccountView, notifications, password, seedWords, backup;
private Navigation.Listener listener;
@FXML
@ -95,21 +94,18 @@ public class AccountSettingsView extends ActivatableViewAndModel {
ToggleGroup toggleGroup = new ToggleGroup();
paymentAccount = new MenuItem(navigation, toggleGroup, Res.get("account.menu.paymentAccount"), FiatAccountsView.class, AwesomeIcon.MONEY);
altCoinsAccountView = new MenuItem(navigation, toggleGroup, Res.get("account.menu.altCoinsAccountView"), AltCoinAccountsView.class, AwesomeIcon.LINK);
arbitratorSelection = new MenuItem(navigation, toggleGroup, Res.get("account.menu.arbitratorSelection"),
ArbitratorSelectionView.class, AwesomeIcon.USER_MD);
notifications = new MenuItem(navigation, toggleGroup, Res.get("account.menu.notifications"), MobileNotificationsView.class, AwesomeIcon.BELL);
password = new MenuItem(navigation, toggleGroup, Res.get("account.menu.password"), PasswordView.class, AwesomeIcon.UNLOCK_ALT);
seedWords = new MenuItem(navigation, toggleGroup, Res.get("account.menu.seedWords"), SeedWordsView.class, AwesomeIcon.KEY);
backup = new MenuItem(navigation, toggleGroup, Res.get("account.menu.backup"), BackupView.class, AwesomeIcon.CLOUD_DOWNLOAD);
leftVBox.getChildren().addAll(paymentAccount, altCoinsAccountView, arbitratorSelection, notifications, password, seedWords, backup);
leftVBox.getChildren().addAll(paymentAccount, altCoinsAccountView, notifications, password, seedWords, backup);
}
@Override
protected void activate() {
paymentAccount.activate();
altCoinsAccountView.activate();
arbitratorSelection.activate();
notifications.activate();
password.activate();
seedWords.activate();
@ -135,7 +131,6 @@ public class AccountSettingsView extends ActivatableViewAndModel {
paymentAccount.deactivate();
altCoinsAccountView.deactivate();
arbitratorSelection.deactivate();
notifications.deactivate();
password.deactivate();
seedWords.deactivate();
@ -148,7 +143,6 @@ public class AccountSettingsView extends ActivatableViewAndModel {
if (view instanceof FiatAccountsView) paymentAccount.setSelected(true);
else if (view instanceof AltCoinAccountsView) altCoinsAccountView.setSelected(true);
else if (view instanceof ArbitratorSelectionView) arbitratorSelection.setSelected(true);
else if (view instanceof MobileNotificationsView) notifications.setSelected(true);
else if (view instanceof PasswordView) password.setSelected(true);
else if (view instanceof SeedWordsView) seedWords.setSelected(true);

View file

@ -40,7 +40,6 @@ import bisq.core.trade.protocol.tasks.maker.MakerProcessDepositTxPublishedMessag
import bisq.core.trade.protocol.tasks.maker.MakerProcessPayDepositRequest;
import bisq.core.trade.protocol.tasks.maker.MakerSendPublishDepositTxRequest;
import bisq.core.trade.protocol.tasks.maker.MakerSetupDepositTxListener;
import bisq.core.trade.protocol.tasks.maker.MakerVerifyArbitratorSelection;
import bisq.core.trade.protocol.tasks.maker.MakerVerifyMediatorSelection;
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerAccount;
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment;
@ -108,7 +107,6 @@ public class DebugView extends InitializableView<GridPane, Void> {
FXCollections.observableArrayList(Arrays.asList(
MakerProcessPayDepositRequest.class,
CheckIfPeerIsBanned.class,
MakerVerifyArbitratorSelection.class,
MakerVerifyMediatorSelection.class,
MakerVerifyTakerAccount.class,
MakerVerifyTakerFeePayment.class,
@ -187,7 +185,6 @@ public class DebugView extends InitializableView<GridPane, Void> {
FXCollections.observableArrayList(Arrays.asList(
MakerProcessPayDepositRequest.class,
CheckIfPeerIsBanned.class,
MakerVerifyArbitratorSelection.class,
MakerVerifyMediatorSelection.class,
MakerVerifyTakerAccount.class,
MakerVerifyTakerFeePayment.class,

View file

@ -30,7 +30,6 @@ import bisq.desktop.components.InputTextField;
import bisq.desktop.components.TitledGroupBg;
import bisq.desktop.main.MainView;
import bisq.desktop.main.account.AccountView;
import bisq.desktop.main.account.content.arbitratorselection.ArbitratorSelectionView;
import bisq.desktop.main.account.content.fiataccounts.FiatAccountsView;
import bisq.desktop.main.account.settings.AccountSettingsView;
import bisq.desktop.main.dao.DaoView;
@ -352,13 +351,7 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel> extends
});
}
} else {
new Popup<>().headLine(Res.get("popup.warning.noArbitratorSelected.headline"))
.instruction(Res.get("popup.warning.noArbitratorSelected.msg"))
.actionButtonTextWithGoTo("navigation.arbitratorSelection")
.onAction(() -> {
navigation.setReturnPath(navigation.getCurrentPath());
navigation.navigateTo(MainView.class, AccountView.class, AccountSettingsView.class, ArbitratorSelectionView.class);
}).show();
new Popup<>().warning(Res.get("popup.warning.noArbitratorsAvailable")).show();
}
} else {
showInsufficientBsqFundsForBtcFeePaymentPopup();
@ -728,9 +721,9 @@ public abstract class MutableOfferView<M extends MutableOfferViewModel> extends
};
volumeListener = (observable, oldValue, newValue) -> {
if (!newValue.equals("") && CurrencyUtil.isFiatCurrency(model.tradeCurrencyCode.get())) {
volumeInfoInputTextField.setContentForPrivacyPopOver(createPopoverLabel(Res.get("offerbook.info.roundedFiatVolume")));
}
if (!newValue.equals("") && CurrencyUtil.isFiatCurrency(model.tradeCurrencyCode.get())) {
volumeInfoInputTextField.setContentForPrivacyPopOver(createPopoverLabel(Res.get("offerbook.info.roundedFiatVolume")));
}
};
marketPriceMarginListener = (observable, oldValue, newValue) -> {

View file

@ -29,7 +29,6 @@ import bisq.desktop.components.InfoAutoTooltipLabel;
import bisq.desktop.components.PeerInfoIcon;
import bisq.desktop.main.MainView;
import bisq.desktop.main.account.AccountView;
import bisq.desktop.main.account.content.arbitratorselection.ArbitratorSelectionView;
import bisq.desktop.main.account.content.fiataccounts.FiatAccountsView;
import bisq.desktop.main.account.settings.AccountSettingsView;
import bisq.desktop.main.funds.FundsView;
@ -409,10 +408,7 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
})
.show();
} else if (!model.hasAcceptedArbitrators()) {
openPopupForMissingAccountSetup(Res.get("popup.warning.noArbitratorSelected.headline"),
Res.get("popup.warning.noArbitratorSelected.msg"),
ArbitratorSelectionView.class,
"navigation.arbitratorSelection");
new Popup<>().warning(Res.get("popup.warning.noArbitratorsAvailable")).show();
} else {
createOfferButton.setDisable(true);
offerActionHandler.onCreateOffer(model.getSelectedTradeCurrency());
@ -421,7 +417,6 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
private void onShowInfo(Offer offer,
boolean isPaymentAccountValidForOffer,
boolean hasMatchingArbitrator,
boolean hasSameProtocolVersion,
boolean isIgnored,
boolean isOfferBanned,
@ -429,12 +424,7 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
boolean isPaymentMethodBanned,
boolean isNodeAddressBanned,
boolean isInsufficientTradeLimit) {
if (!hasMatchingArbitrator) {
openPopupForMissingAccountSetup(Res.get("popup.warning.noArbitratorSelected.headline"),
Res.get("popup.warning.noArbitratorSelected.msg"),
ArbitratorSelectionView.class,
"navigation.arbitratorSelection");
} else if (!isPaymentAccountValidForOffer) {
if (!isPaymentAccountValidForOffer) {
openPopupForMissingAccountSetup(Res.get("offerbook.warning.noMatchingAccount.headline"),
Res.get("offerbook.warning.noMatchingAccount.msg"),
FiatAccountsView.class,
@ -804,7 +794,7 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
return new TableCell<OfferBookListItem, OfferBookListItem>() {
final ImageView iconView = new ImageView();
final Button button = new AutoTooltipButton();
boolean isTradable, isPaymentAccountValidForOffer, hasMatchingArbitrator,
boolean isTradable, isPaymentAccountValidForOffer,
hasSameProtocolVersion, isIgnored, isOfferBanned, isCurrencyBanned,
isPaymentMethodBanned, isNodeAddressBanned, isInsufficientTradeLimit;
@ -825,7 +815,6 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
boolean myOffer = model.isMyOffer(offer);
if (tableRow != null) {
isPaymentAccountValidForOffer = model.isAnyPaymentAccountValidForOffer(offer);
hasMatchingArbitrator = model.hasMatchingArbitrator(offer);
hasSameProtocolVersion = model.hasSameProtocolVersion(offer);
isIgnored = model.isIgnored(offer);
isOfferBanned = model.isOfferBanned(offer);
@ -834,7 +823,6 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
isNodeAddressBanned = model.isNodeAddressBanned(offer);
isInsufficientTradeLimit = model.isInsufficientTradeLimit(offer);
isTradable = isPaymentAccountValidForOffer &&
hasMatchingArbitrator &&
hasSameProtocolVersion &&
!isIgnored &&
!isOfferBanned &&
@ -856,7 +844,6 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
if (!(e.getTarget() instanceof ImageView || e.getTarget() instanceof Canvas))
onShowInfo(offer,
isPaymentAccountValidForOffer,
hasMatchingArbitrator,
hasSameProtocolVersion,
isIgnored,
isOfferBanned,
@ -888,7 +875,6 @@ public class OfferBookView extends ActivatableViewAndModel<GridPane, OfferBookVi
if (!myOffer && !isTradable)
button.setOnAction(e -> onShowInfo(offer,
isPaymentAccountValidForOffer,
hasMatchingArbitrator,
hasSameProtocolVersion,
isIgnored,
isOfferBanned,

View file

@ -536,19 +536,6 @@ class OfferBookViewModel extends ActivatableViewModel {
});
}
boolean hasMatchingArbitrator(Offer offer) {
final List<NodeAddress> acceptedArbitratorAddresses = user.getAcceptedArbitratorAddresses();
if (acceptedArbitratorAddresses != null) {
for (NodeAddress offerArbitratorNodeAddress : offer.getArbitratorNodeAddresses()) {
for (NodeAddress acceptedArbitratorNodeAddress : acceptedArbitratorAddresses) {
if (offerArbitratorNodeAddress.equals(acceptedArbitratorNodeAddress))
return true;
}
}
}
return false;
}
boolean isIgnored(Offer offer) {
return preferences.getIgnoreTradersList().stream()
.anyMatch(i -> i.equals(offer.getMakerNodeAddress().getHostNameWithoutPostFix()));

View file

@ -30,9 +30,6 @@ import bisq.desktop.components.InfoTextField;
import bisq.desktop.components.InputTextField;
import bisq.desktop.components.TitledGroupBg;
import bisq.desktop.main.MainView;
import bisq.desktop.main.account.AccountView;
import bisq.desktop.main.account.content.arbitratorselection.ArbitratorSelectionView;
import bisq.desktop.main.account.settings.AccountSettingsView;
import bisq.desktop.main.dao.DaoView;
import bisq.desktop.main.dao.wallet.BsqWalletView;
import bisq.desktop.main.dao.wallet.receive.BsqReceiveView;
@ -393,14 +390,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
});
}
} else {
new Popup<>().headLine(Res.get("popup.warning.noArbitratorSelected.headline"))
.instruction(Res.get("popup.warning.noArbitratorSelected.msg"))
.actionButtonTextWithGoTo("navigation.arbitratorSelection")
.onAction(() -> {
navigation.setReturnPath(navigation.getCurrentPath());
navigation.navigateTo(MainView.class, AccountView.class, AccountSettingsView.class,
ArbitratorSelectionView.class);
}).show();
new Popup<>().warning(Res.get("popup.warning.noArbitratorsAvailable")).show();
}
} else {
showInsufficientBsqFundsForBtcFeePaymentPopup();

View file

@ -19,10 +19,6 @@ package bisq.desktop.main.overlays.windows;
import bisq.desktop.Navigation;
import bisq.desktop.components.BusyAnimation;
import bisq.desktop.main.MainView;
import bisq.desktop.main.account.AccountView;
import bisq.desktop.main.account.content.arbitratorselection.ArbitratorSelectionView;
import bisq.desktop.main.account.settings.AccountSettingsView;
import bisq.desktop.main.overlays.Overlay;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.util.Layout;
@ -398,9 +394,7 @@ public class OfferDetailsWindow extends Overlay<OfferDetailsWindow> {
takeOfferHandlerOptional.get().run();
}
} else {
new Popup<>().warning(Res.get("offerDetailsWindow.warn.noArbitrator")).show();
navigation.navigateTo(MainView.class, AccountView.class, AccountSettingsView.class,
ArbitratorSelectionView.class);
new Popup<>().warning(Res.get("popup.warning.noArbitratorsAvailable")).show();
}
});
}

View file

@ -95,7 +95,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
private ComboBox<TradeCurrency> preferredTradeCurrencyComboBox;
private ComboBox<BaseCurrencyNetwork> selectBaseCurrencyNetworkComboBox;
private CheckBox useAnimationsCheckBox, autoSelectArbitratorsCheckBox, avoidStandbyModeCheckBox,
private CheckBox useAnimationsCheckBox, avoidStandbyModeCheckBox,
showOwnOffersInOfferBook, sortMarketCurrenciesNumericallyCheckBox, useCustomFeeCheckbox;
private int gridRow = 0;
private InputTextField transactionFeeInputTextField, ignoreTradersListInputTextField, referralIdInputTextField;
@ -185,7 +185,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
///////////////////////////////////////////////////////////////////////////////////////////
private void initializeGeneralOptions() {
TitledGroupBg titledGroupBg = addTitledGroupBg(root, gridRow, 10, Res.get("setting.preferences.general"));
TitledGroupBg titledGroupBg = addTitledGroupBg(root, gridRow, 9, Res.get("setting.preferences.general"));
GridPane.setColumnSpan(titledGroupBg, 4);
// selectBaseCurrencyNetwork
@ -287,10 +287,6 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
UserThread.runAfter(() -> deviationInputTextField.setText(formatter.formatPercentagePrice(preferences.getMaxPriceDistanceInPercent())), 100, TimeUnit.MILLISECONDS);
};
// autoSelectArbitrators
autoSelectArbitratorsCheckBox = addLabelCheckBox(root, ++gridRow,
Res.get("setting.preferences.autoSelectArbitrators"), "").second;
// ignoreTraders
ignoreTradersListInputTextField = addLabelInputTextField(root, ++gridRow,
Res.get("setting.preferences.ignorePeers")).second;
@ -680,9 +676,6 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
resetDontShowAgainButton.setOnAction(e -> preferences.resetDontShowAgain());
autoSelectArbitratorsCheckBox.setSelected(preferences.isAutoSelectArbitrators());
autoSelectArbitratorsCheckBox.setOnAction(e -> preferences.setAutoSelectArbitrators(autoSelectArbitratorsCheckBox.isSelected()));
// We use opposite property (useStandbyMode) in preferences to have the default value (false) set as we want it,
// so users who update gets set avoidStandbyMode=true (useStandbyMode=false)
avoidStandbyModeCheckBox.setSelected(!preferences.isUseStandbyMode());
@ -734,7 +727,6 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
// useStickyMarketPriceCheckBox.setOnAction(null);
sortMarketCurrenciesNumericallyCheckBox.setOnAction(null);
showOwnOffersInOfferBook.setOnAction(null);
autoSelectArbitratorsCheckBox.setOnAction(null);
resetDontShowAgainButton.setOnAction(null);
avoidStandbyModeCheckBox.setOnAction(null);
}