Merge pull request #4474 from chimp1984/stop-xmr-proof-service-at-dispute

Stop xmr proof service at dispute
This commit is contained in:
Christoph Atteneder 2020-09-04 10:16:00 +02:00 committed by GitHub
commit d33b40128e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 502 additions and 220 deletions

View File

@ -26,6 +26,7 @@ import bisq.core.setup.CorePersistedDataHost;
import bisq.core.setup.CoreSetup;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
import bisq.core.trade.TradeManager;
import bisq.core.trade.txproof.xmr.XmrTxProofService;
import bisq.network.p2p.P2PService;
@ -164,7 +165,6 @@ public abstract class BisqExecutable implements GracefulShutDownHandler, BisqSet
protected void setupDevEnv() {
DevEnv.setDevMode(config.useDevMode);
DevEnv.setDevMode(config.useDevModeHeader);
DevEnv.setDaoActivated(config.daoActivated);
}
@ -221,6 +221,7 @@ public abstract class BisqExecutable implements GracefulShutDownHandler, BisqSet
try {
injector.getInstance(ArbitratorManager.class).shutDown();
injector.getInstance(TradeManager.class).shutDown();
injector.getInstance(XmrTxProofService.class).shutDown();
injector.getInstance(DaoSetup.class).shutDown();
injector.getInstance(AvoidStandbyModeService.class).shutDown();
injector.getInstance(OpenOfferManager.class).shutDown(() -> {

View File

@ -59,6 +59,7 @@ import bisq.core.support.traderchat.TraderChatManager;
import bisq.core.trade.TradeManager;
import bisq.core.trade.TradeTxException;
import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.core.trade.txproof.xmr.XmrTxProofService;
import bisq.core.user.Preferences;
import bisq.core.user.User;
import bisq.core.util.FormattingUtils;
@ -167,6 +168,7 @@ public class BisqSetup {
private final PrivateNotificationManager privateNotificationManager;
private final FilterManager filterManager;
private final TradeStatisticsManager tradeStatisticsManager;
private final XmrTxProofService xmrTxProofService;
private final ClockWatcher clockWatcher;
private final FeeService feeService;
private final DaoSetup daoSetup;
@ -263,6 +265,7 @@ public class BisqSetup {
PrivateNotificationManager privateNotificationManager,
FilterManager filterManager,
TradeStatisticsManager tradeStatisticsManager,
XmrTxProofService xmrTxProofService,
ClockWatcher clockWatcher,
FeeService feeService,
DaoSetup daoSetup,
@ -308,6 +311,7 @@ public class BisqSetup {
this.privateNotificationManager = privateNotificationManager;
this.filterManager = filterManager;
this.tradeStatisticsManager = tradeStatisticsManager;
this.xmrTxProofService = xmrTxProofService;
this.clockWatcher = clockWatcher;
this.feeService = feeService;
this.daoSetup = daoSetup;
@ -686,6 +690,7 @@ public class BisqSetup {
traderChatManager.onAllServicesInitialized();
tradeManager.onAllServicesInitialized();
xmrTxProofService.onAllServicesInitialized();
if (walletsSetup.downloadPercentageProperty().get() == 1) {
checkForLockedUpFunds();

View File

@ -46,8 +46,6 @@ import bisq.core.trade.messages.PeerPublishedDelayedPayoutTxMessage;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.statistics.ReferralIdService;
import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.core.trade.txproof.AssetTxProofResult;
import bisq.core.trade.txproof.xmr.XmrTxProofService;
import bisq.core.user.User;
import bisq.core.util.Validator;
@ -59,7 +57,6 @@ import bisq.network.p2p.P2PService;
import bisq.network.p2p.SendMailboxMessageListener;
import bisq.common.ClockWatcher;
import bisq.common.UserThread;
import bisq.common.config.Config;
import bisq.common.crypto.KeyRing;
import bisq.common.handlers.ErrorMessageHandler;
@ -111,8 +108,6 @@ import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkArgument;
public class TradeManager implements PersistedDataHost {
private static final Logger log = LoggerFactory.getLogger(TradeManager.class);
@ -132,7 +127,6 @@ public class TradeManager implements PersistedDataHost {
private final ReferralIdService referralIdService;
private final AccountAgeWitnessService accountAgeWitnessService;
@Getter
private final XmrTxProofService xmrTxProofService;
private final ArbitratorManager arbitratorManager;
private final MediatorManager mediatorManager;
private final RefundAgentManager refundAgentManager;
@ -174,7 +168,6 @@ public class TradeManager implements PersistedDataHost {
TradeStatisticsManager tradeStatisticsManager,
ReferralIdService referralIdService,
AccountAgeWitnessService accountAgeWitnessService,
XmrTxProofService xmrTxProofService,
ArbitratorManager arbitratorManager,
MediatorManager mediatorManager,
RefundAgentManager refundAgentManager,
@ -197,7 +190,6 @@ public class TradeManager implements PersistedDataHost {
this.tradeStatisticsManager = tradeStatisticsManager;
this.referralIdService = referralIdService;
this.accountAgeWitnessService = accountAgeWitnessService;
this.xmrTxProofService = xmrTxProofService;
this.arbitratorManager = arbitratorManager;
this.mediatorManager = mediatorManager;
this.refundAgentManager = refundAgentManager;
@ -286,7 +278,7 @@ public class TradeManager implements PersistedDataHost {
}
public void shutDown() {
xmrTxProofService.shutDown();
// Do nothing here
}
private void initPendingTrades() {
@ -329,13 +321,6 @@ public class TradeManager implements PersistedDataHost {
addTradeToFailedTradesList.add(trade);
}
}
if (trade.getState() == Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG) {
// This state can be only appear at a SellerTrade
checkArgument(trade instanceof SellerTrade, "Trade must be instance of SellerTrade");
// We delay a bit as at startup lots of stuff is happening
UserThread.runAfter(() -> maybeStartXmrTxProofServices((SellerTrade) trade), 1);
}
}
);
@ -360,31 +345,10 @@ public class TradeManager implements PersistedDataHost {
pendingTradesInitialized.set(true);
}
public void maybeStartXmrTxProofServices(SellerTrade sellerTrade) {
xmrTxProofService.maybeStartRequests(sellerTrade, tradableList.getList(),
assetTxProofResult -> {
if (assetTxProofResult == AssetTxProofResult.COMPLETED) {
log.info("###########################################################################################");
log.info("We auto-confirm trade {} as our all our services for the tx proof completed successfully", sellerTrade.getShortId());
log.info("###########################################################################################");
autoConfirmFiatPaymentReceived(sellerTrade);
}
},
(errorMessage, throwable) -> {
log.error(errorMessage);
});
}
private void autoConfirmFiatPaymentReceived(SellerTrade sellerTrade) {
onFiatPaymentReceived(sellerTrade,
() -> {
}, errorMessage -> {
});
}
public void onFiatPaymentReceived(SellerTrade sellerTrade,
ResultHandler resultHandler,
ErrorMessageHandler errorMessageHandler) {
public void onUserConfirmedFiatPaymentReceived(SellerTrade sellerTrade,
ResultHandler resultHandler,
ErrorMessageHandler errorMessageHandler) {
sellerTrade.onFiatPaymentReceived(resultHandler, errorMessageHandler);
//TODO move to trade protocol task

View File

@ -17,7 +17,6 @@
package bisq.core.trade.protocol.tasks.seller;
import bisq.core.trade.SellerTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
import bisq.core.trade.protocol.tasks.TradeTask;
@ -27,7 +26,6 @@ import bisq.common.taskrunner.TaskRunner;
import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
@ -62,10 +60,6 @@ public class SellerProcessCounterCurrencyTransferStartedMessage extends TradeTas
trade.setCounterCurrencyExtraData(counterCurrencyExtraData);
}
checkArgument(trade instanceof SellerTrade, "Trade must be instance of SellerTrade");
// We return early in the service if its not XMR. We prefer to not have additional checks outside...
processModel.getTradeManager().maybeStartXmrTxProofServices((SellerTrade) trade);
processModel.removeMailboxMessageAfterProcessing(trade);
trade.setState(Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG);

View File

@ -26,6 +26,7 @@ public enum AssetTxProofResult {
TRADE_LIMIT_EXCEEDED,
INVALID_DATA, // Peer provided invalid data. Might be a scam attempt (e.g. txKey reused)
PAYOUT_TX_ALREADY_PUBLISHED,
DISPUTE_OPENED,
REQUESTS_STARTED(false),
PENDING(false),
@ -44,6 +45,10 @@ public enum AssetTxProofResult {
@Getter
private int numRequiredSuccessResults;
@Getter
private int numConfirmations;
@Getter
private int numRequiredConfirmations;
@Getter
private String details = "";
// If isTerminal is set it means that we stop the service
@Getter
@ -68,6 +73,16 @@ public enum AssetTxProofResult {
return this;
}
public AssetTxProofResult numConfirmations(int numConfirmations) {
this.numConfirmations = numConfirmations;
return this;
}
public AssetTxProofResult numRequiredConfirmations(int numRequiredConfirmations) {
this.numRequiredConfirmations = numRequiredConfirmations;
return this;
}
public AssetTxProofResult details(String details) {
this.details = details;
return this;
@ -78,6 +93,8 @@ public enum AssetTxProofResult {
return "AssetTxProofResult{" +
"\n numSuccessResults=" + numSuccessResults +
",\n requiredSuccessResults=" + numRequiredSuccessResults +
",\n numConfirmations=" + numConfirmations +
",\n numRequiredConfirmations=" + numRequiredConfirmations +
",\n details='" + details + '\'' +
"\n} " + super.toString();
}

View File

@ -17,18 +17,8 @@
package bisq.core.trade.txproof;
import bisq.core.trade.Trade;
import bisq.common.handlers.FaultHandler;
import java.util.List;
import java.util.function.Consumer;
public interface AssetTxProofService {
void maybeStartRequests(Trade trade,
List<Trade> activeTrades,
Consumer<AssetTxProofResult> resultHandler,
FaultHandler faultHandler);
void onAllServicesInitialized();
void shutDown();
}

View File

@ -162,7 +162,7 @@ public class XmrTxProofParser implements AssetTxProofParser<XmrTxProofRequest.Re
if (confirmations < confirmsRequired) {
return XmrTxProofRequest.Result.PENDING.with(XmrTxProofRequest.Detail.PENDING_CONFIRMATIONS.numConfirmations(confirmations));
} else {
return XmrTxProofRequest.Result.SUCCESS;
return XmrTxProofRequest.Result.SUCCESS.with(XmrTxProofRequest.Detail.SUCCESS.numConfirmations(confirmations));
}
} catch (JsonParseException | NullPointerException e) {

View File

@ -85,6 +85,8 @@ class XmrTxProofRequest implements AssetTxProofRequest<XmrTxProofRequest.Result>
TX_NOT_FOUND, // Tx not visible in network yet. Could be also other error
PENDING_CONFIRMATIONS,
SUCCESS,
// Error states
CONNECTION_FAILURE,
API_INVALID,
@ -268,12 +270,10 @@ class XmrTxProofRequest implements AssetTxProofRequest<XmrTxProofRequest.Result>
///////////////////////////////////////////////////////////////////////////////////////////
private String getShortId() {
return Utilities.getShortId(model.getTradeId()) + " @ " +
model.getServiceAddress().substring(0, 6);
return Utilities.getShortId(model.getTradeId()) + " @ " + model.getServiceAddress().substring(0, 6);
}
private boolean isTimeOutReached() {
return System.currentTimeMillis() - firstRequest > MAX_REQUEST_PERIOD;
}
}

View File

@ -18,6 +18,9 @@
package bisq.core.trade.txproof.xmr;
import bisq.core.locale.Res;
import bisq.core.support.dispute.Dispute;
import bisq.core.support.dispute.mediation.MediationManager;
import bisq.core.support.dispute.refund.RefundManager;
import bisq.core.trade.Trade;
import bisq.core.trade.txproof.AssetTxProofHttpClient;
import bisq.core.trade.txproof.AssetTxProofRequestsPerTrade;
@ -30,6 +33,9 @@ import org.bitcoinj.core.Coin;
import javafx.beans.value.ChangeListener;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@ -46,6 +52,8 @@ class XmrTxProofRequestsPerTrade implements AssetTxProofRequestsPerTrade {
@Getter
private final Trade trade;
private final AutoConfirmSettings autoConfirmSettings;
private final MediationManager mediationManager;
private final RefundManager refundManager;
private final AssetTxProofHttpClient httpClient;
private int numRequiredSuccessResults;
@ -54,6 +62,7 @@ class XmrTxProofRequestsPerTrade implements AssetTxProofRequestsPerTrade {
private int numSuccessResults;
private ChangeListener<Trade.State> tradeStateListener;
private AutoConfirmSettings.Listener autoConfirmSettingsListener;
private ListChangeListener<Dispute> mediationListener, refundListener;
///////////////////////////////////////////////////////////////////////////////////////////
@ -62,10 +71,14 @@ class XmrTxProofRequestsPerTrade implements AssetTxProofRequestsPerTrade {
XmrTxProofRequestsPerTrade(AssetTxProofHttpClient httpClient,
Trade trade,
AutoConfirmSettings autoConfirmSettings) {
AutoConfirmSettings autoConfirmSettings,
MediationManager mediationManager,
RefundManager refundManager) {
this.httpClient = httpClient;
this.trade = trade;
this.autoConfirmSettings = autoConfirmSettings;
this.mediationManager = mediationManager;
this.refundManager = refundManager;
}
@ -75,6 +88,49 @@ class XmrTxProofRequestsPerTrade implements AssetTxProofRequestsPerTrade {
@Override
public void requestFromAllServices(Consumer<AssetTxProofResult> resultHandler, FaultHandler faultHandler) {
// isTradeAmountAboveLimit
if (isTradeAmountAboveLimit(trade)) {
callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.TRADE_LIMIT_EXCEEDED);
return;
}
// isPayoutPublished
if (trade.isPayoutPublished()) {
callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.PAYOUT_TX_ALREADY_PUBLISHED);
return;
}
// IsEnabled()
// We will stop all our services if the user changes the enable state in the AutoConfirmSettings
if (!autoConfirmSettings.isEnabled()) {
callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.FEATURE_DISABLED);
return;
}
addSettingsListener(resultHandler);
// TradeState
setupTradeStateListener(resultHandler);
// We checked initially for current trade state so no need to check again here
// Check if mediation dispute and add listener
ObservableList<Dispute> mediationDisputes = mediationManager.getDisputesAsObservableList();
if (isDisputed(mediationDisputes)) {
callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.DISPUTE_OPENED);
return;
}
setupMediationListener(resultHandler, mediationDisputes);
// Check if arbitration dispute and add listener
ObservableList<Dispute> refundDisputes = refundManager.getDisputesAsObservableList();
if (isDisputed(refundDisputes)) {
callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.DISPUTE_OPENED);
return;
}
setupArbitrationListener(resultHandler, refundDisputes);
// All good so we start
callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.REQUESTS_STARTED);
// We set serviceAddresses at request time. If user changes AutoConfirmSettings after request has started
// it will have no impact on serviceAddresses and numRequiredSuccessResults.
// Thought numRequiredConfirmations can be changed during request process and will be read from
@ -82,37 +138,6 @@ class XmrTxProofRequestsPerTrade implements AssetTxProofRequestsPerTrade {
List<String> serviceAddresses = autoConfirmSettings.getServiceAddresses();
numRequiredSuccessResults = serviceAddresses.size();
if (isTradeAmountAboveLimit(trade)) {
callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.TRADE_LIMIT_EXCEEDED);
return;
}
if (trade.isPayoutPublished()) {
callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.PAYOUT_TX_ALREADY_PUBLISHED);
return;
}
// We will stop all our services if the user changes the enable state in the AutoConfirmSettings
autoConfirmSettingsListener = () -> {
if (!autoConfirmSettings.isEnabled()) {
callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.FEATURE_DISABLED);
}
};
autoConfirmSettings.addListener(autoConfirmSettingsListener);
if (!autoConfirmSettings.isEnabled()) {
callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.FEATURE_DISABLED);
return;
}
tradeStateListener = (observable, oldValue, newValue) -> {
if (trade.isPayoutPublished()) {
callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.PAYOUT_TX_ALREADY_PUBLISHED);
}
};
trade.stateProperty().addListener(tradeStateListener);
callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.REQUESTS_STARTED);
for (String serviceAddress : serviceAddresses) {
XmrTxProofModel model = new XmrTxProofModel(trade, serviceAddress, autoConfirmSettings);
XmrTxProofRequest request = new XmrTxProofRequest(httpClient, model);
@ -146,7 +171,12 @@ class XmrTxProofRequestsPerTrade implements AssetTxProofRequestsPerTrade {
// have completed on the service level.
log.info("All {} tx proof requests for trade {} have been successful.",
numRequiredSuccessResults, trade.getShortId());
assetTxProofResult = AssetTxProofResult.COMPLETED;
XmrTxProofRequest.Detail detail = result.getDetail();
assetTxProofResult = AssetTxProofResult.COMPLETED
.numSuccessResults(numSuccessResults)
.numRequiredSuccessResults(numRequiredSuccessResults)
.numConfirmations(detail != null ? detail.getNumConfirmations() : 0)
.numRequiredConfirmations(autoConfirmSettings.getRequiredConfirmations());
}
break;
case FAILED:
@ -174,16 +204,66 @@ class XmrTxProofRequestsPerTrade implements AssetTxProofRequestsPerTrade {
}
}
protected void addSettingsListener(Consumer<AssetTxProofResult> resultHandler) {
autoConfirmSettingsListener = () -> {
if (!autoConfirmSettings.isEnabled()) {
callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.FEATURE_DISABLED);
}
};
autoConfirmSettings.addListener(autoConfirmSettingsListener);
}
protected void setupTradeStateListener(Consumer<AssetTxProofResult> resultHandler) {
tradeStateListener = (observable, oldValue, newValue) -> {
if (trade.isPayoutPublished()) {
callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.PAYOUT_TX_ALREADY_PUBLISHED);
}
};
trade.stateProperty().addListener(tradeStateListener);
}
protected void setupArbitrationListener(Consumer<AssetTxProofResult> resultHandler,
ObservableList<Dispute> refundDisputes) {
refundListener = c -> {
c.next();
if (c.wasAdded() && isDisputed(c.getAddedSubList())) {
callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.DISPUTE_OPENED);
}
};
refundDisputes.addListener(refundListener);
}
protected void setupMediationListener(Consumer<AssetTxProofResult> resultHandler,
ObservableList<Dispute> mediationDisputes) {
mediationListener = c -> {
c.next();
if (c.wasAdded() && isDisputed(c.getAddedSubList())) {
callResultHandlerAndMaybeTerminate(resultHandler, AssetTxProofResult.DISPUTE_OPENED);
}
};
mediationDisputes.addListener(mediationListener);
}
@Override
public void terminate() {
requests.forEach(XmrTxProofRequest::terminate);
requests.clear();
if (tradeStateListener != null) {
trade.stateProperty().removeListener(tradeStateListener);
}
if (autoConfirmSettingsListener != null) {
autoConfirmSettings.removeListener(autoConfirmSettingsListener);
}
if (mediationListener != null) {
mediationManager.getDisputesAsObservableList().removeListener(mediationListener);
}
if (refundListener != null) {
refundManager.getDisputesAsObservableList().removeListener(refundListener);
}
}
@ -217,6 +297,8 @@ class XmrTxProofRequestsPerTrade implements AssetTxProofRequestsPerTrade {
return AssetTxProofResult.PENDING
.numSuccessResults(numSuccessResults)
.numRequiredSuccessResults(numRequiredSuccessResults)
.numConfirmations(detail != null ? detail.getNumConfirmations() : 0)
.numRequiredConfirmations(autoConfirmSettings.getRequiredConfirmations())
.details(detailString);
}
@ -235,4 +317,8 @@ class XmrTxProofRequestsPerTrade implements AssetTxProofRequestsPerTrade {
}
return false;
}
private boolean isDisputed(List<? extends Dispute> disputes) {
return disputes.stream().anyMatch(e -> e.getTradeId().equals(trade.getId()));
}
}

View File

@ -20,9 +20,11 @@ package bisq.core.trade.txproof.xmr;
import bisq.core.btc.setup.WalletsSetup;
import bisq.core.filter.FilterManager;
import bisq.core.locale.Res;
import bisq.core.payment.payload.AssetsAccountPayload;
import bisq.core.support.dispute.mediation.MediationManager;
import bisq.core.support.dispute.refund.RefundManager;
import bisq.core.trade.SellerTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.TradeManager;
import bisq.core.trade.closed.ClosedTradableManager;
import bisq.core.trade.failed.FailedTradesManager;
import bisq.core.trade.txproof.AssetTxProofHttpClient;
@ -31,19 +33,27 @@ import bisq.core.trade.txproof.AssetTxProofService;
import bisq.core.user.AutoConfirmSettings;
import bisq.core.user.Preferences;
import bisq.network.p2p.BootstrapListener;
import bisq.network.p2p.P2PService;
import bisq.common.app.DevEnv;
import bisq.common.handlers.FaultHandler;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.monadic.MonadicBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Stream;
import lombok.extern.slf4j.Slf4j;
@ -59,12 +69,19 @@ import static com.google.common.base.Preconditions.checkNotNull;
public class XmrTxProofService implements AssetTxProofService {
private final FilterManager filterManager;
private final Preferences preferences;
private final TradeManager tradeManager;
private final ClosedTradableManager closedTradableManager;
private final FailedTradesManager failedTradesManager;
private final MediationManager mediationManager;
private final RefundManager refundManager;
private final P2PService p2PService;
private final WalletsSetup walletsSetup;
private final AssetTxProofHttpClient httpClient;
private final Map<String, XmrTxProofRequestsPerTrade> servicesByTradeId = new HashMap<>();
private AutoConfirmSettings autoConfirmSettings;
private Map<String, ChangeListener<Trade.State>> tradeStateListenerMap = new HashMap<>();
private ChangeListener<Number> btcPeersListener, btcBlockListener;
private BootstrapListener bootstrapListener;
///////////////////////////////////////////////////////////////////////////////////////////
@ -75,27 +92,24 @@ public class XmrTxProofService implements AssetTxProofService {
@Inject
public XmrTxProofService(FilterManager filterManager,
Preferences preferences,
TradeManager tradeManager,
ClosedTradableManager closedTradableManager,
FailedTradesManager failedTradesManager,
MediationManager mediationManager,
RefundManager refundManager,
P2PService p2PService,
WalletsSetup walletsSetup,
AssetTxProofHttpClient httpClient) {
this.filterManager = filterManager;
this.preferences = preferences;
this.tradeManager = tradeManager;
this.closedTradableManager = closedTradableManager;
this.failedTradesManager = failedTradesManager;
this.mediationManager = mediationManager;
this.refundManager = refundManager;
this.p2PService = p2PService;
this.walletsSetup = walletsSetup;
this.httpClient = httpClient;
filterManager.filterProperty().addListener((observable, oldValue, newValue) -> {
if (isAutoConfDisabledByFilter()) {
servicesByTradeId.values().stream().map(XmrTxProofRequestsPerTrade::getTrade).forEach(trade ->
trade.setAssetTxProofResult(AssetTxProofResult.FEATURE_DISABLED
.details(Res.get("portfolio.pending.autoConf.state.filterDisabledFeature"))));
shutDown();
}
});
}
@ -104,60 +118,22 @@ public class XmrTxProofService implements AssetTxProofService {
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public void maybeStartRequests(Trade trade,
List<Trade> activeTrades,
Consumer<AssetTxProofResult> resultHandler,
FaultHandler faultHandler) {
if (!isXmrBuyer(trade)) {
return;
}
public void onAllServicesInitialized() {
// As we might trigger the payout tx we want to be sure that we are well connected to the Bitcoin network.
// onAllServicesInitialized is called once we have received the initial data but we want to have our
// hidden service published and upDatedDataResponse received before we start.
MonadicBinding<Boolean> p2pNetworkAndWalletReady = EasyBind.combine(isP2pBootstrapped(), hasSufficientBtcPeers(), isBtcBlockDownloadComplete(),
(isP2pBootstrapped, hasSufficientBtcPeers, isBtcBlockDownloadComplete) -> {
log.info("isP2pBootstrapped={}, hasSufficientBtcPeers={} isBtcBlockDownloadComplete={}",
isP2pBootstrapped, hasSufficientBtcPeers, isBtcBlockDownloadComplete);
return isP2pBootstrapped && hasSufficientBtcPeers && isBtcBlockDownloadComplete;
});
String txId = trade.getCounterCurrencyTxId();
String txHash = trade.getCounterCurrencyExtraData();
if (is32BitHexStringInValid(txId) || is32BitHexStringInValid(txHash)) {
trade.setAssetTxProofResult(AssetTxProofResult.INVALID_DATA.details(Res.get("portfolio.pending.autoConf.state.txKeyOrTxIdInvalid")));
return;
}
if (!networkAndWalletReady()) {
return;
}
Optional<AutoConfirmSettings> optionalAutoConfirmSettings = preferences.findAutoConfirmSettings("XMR");
if (!optionalAutoConfirmSettings.isPresent()) {
// Not expected
log.error("autoConfirmSettings is not present");
return;
}
AutoConfirmSettings autoConfirmSettings = optionalAutoConfirmSettings.get();
if (isAutoConfDisabledByFilter()) {
trade.setAssetTxProofResult(AssetTxProofResult.FEATURE_DISABLED
.details(Res.get("portfolio.pending.autoConf.state.filterDisabledFeature")));
return;
}
if (wasTxKeyReUsed(trade, activeTrades)) {
trade.setAssetTxProofResult(AssetTxProofResult.INVALID_DATA
.details(Res.get("portfolio.pending.autoConf.state.xmr.txKeyReused")));
return;
}
XmrTxProofRequestsPerTrade service = new XmrTxProofRequestsPerTrade(httpClient,
trade,
autoConfirmSettings);
servicesByTradeId.put(trade.getId(), service);
service.requestFromAllServices(
assetTxProofResult -> {
trade.setAssetTxProofResult(assetTxProofResult);
if (assetTxProofResult.isTerminal()) {
servicesByTradeId.remove(trade.getId());
}
resultHandler.accept(assetTxProofResult);
},
faultHandler);
p2pNetworkAndWalletReady.subscribe((observable, oldValue, newValue) -> {
if (newValue) {
onP2pNetworkAndWalletReady();
}
});
}
@Override
@ -167,20 +143,188 @@ public class XmrTxProofService implements AssetTxProofService {
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void onP2pNetworkAndWalletReady() {
if (!preferences.findAutoConfirmSettings("XMR").isPresent()) {
log.error("AutoConfirmSettings is not present");
}
autoConfirmSettings = preferences.findAutoConfirmSettings("XMR").get();
// We register a listener to stop running services. For new trades we check anyway in the trade validation
filterManager.filterProperty().addListener((observable, oldValue, newValue) -> {
if (isAutoConfDisabledByFilter()) {
servicesByTradeId.values().stream().map(XmrTxProofRequestsPerTrade::getTrade).forEach(trade ->
trade.setAssetTxProofResult(AssetTxProofResult.FEATURE_DISABLED
.details(Res.get("portfolio.pending.autoConf.state.filterDisabledFeature"))));
shutDown();
}
});
// We listen on new trades
ObservableList<Trade> tradableList = tradeManager.getTradableList();
tradableList.addListener((ListChangeListener<Trade>) c -> {
c.next();
if (c.wasAdded()) {
processTrades(c.getAddedSubList());
}
});
// Process existing trades
processTrades(tradableList);
}
private void processTrades(List<? extends Trade> trades) {
trades.stream()
.filter(trade -> trade instanceof SellerTrade)
.map(trade -> (SellerTrade) trade)
.filter(this::isXmrTrade)
.filter(trade -> !trade.isFiatReceived()) // Phase name is from the time when it was fiat only. Means counter currency (XMR) received.
.forEach(this::processTradeOrAddListener);
}
// Basic requirements are fulfilled.
// We process further if we are in the expected state or register a listener
private void processTradeOrAddListener(SellerTrade trade) {
if (isExpectedTradeState(trade.getState())) {
startRequestsIfValid(trade);
} else {
// We are expecting SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG in the future, so listen on changes
ChangeListener<Trade.State> tradeStateListener = (observable, oldValue, newValue) -> {
if (isExpectedTradeState(newValue)) {
ChangeListener<Trade.State> listener = tradeStateListenerMap.remove(trade.getId());
if (listener != null) {
trade.stateProperty().removeListener(listener);
}
startRequestsIfValid(trade);
}
};
tradeStateListenerMap.put(trade.getId(), tradeStateListener);
trade.stateProperty().addListener(tradeStateListener);
}
}
private void startRequestsIfValid(SellerTrade trade) {
String txId = trade.getCounterCurrencyTxId();
String txHash = trade.getCounterCurrencyExtraData();
if (is32BitHexStringInValid(txId) || is32BitHexStringInValid(txHash)) {
trade.setAssetTxProofResult(AssetTxProofResult.INVALID_DATA.details(Res.get("portfolio.pending.autoConf.state.txKeyOrTxIdInvalid")));
return;
}
if (isAutoConfDisabledByFilter()) {
trade.setAssetTxProofResult(AssetTxProofResult.FEATURE_DISABLED
.details(Res.get("portfolio.pending.autoConf.state.filterDisabledFeature")));
return;
}
if (wasTxKeyReUsed(trade, tradeManager.getTradableList())) {
trade.setAssetTxProofResult(AssetTxProofResult.INVALID_DATA
.details(Res.get("portfolio.pending.autoConf.state.xmr.txKeyReused")));
return;
}
startRequests(trade);
}
private void startRequests(SellerTrade trade) {
XmrTxProofRequestsPerTrade service = new XmrTxProofRequestsPerTrade(httpClient,
trade,
autoConfirmSettings,
mediationManager,
refundManager);
servicesByTradeId.put(trade.getId(), service);
service.requestFromAllServices(
assetTxProofResult -> {
trade.setAssetTxProofResult(assetTxProofResult);
if (assetTxProofResult == AssetTxProofResult.COMPLETED) {
log.info("###########################################################################################");
log.info("We auto-confirm trade {} as our all our services for the tx proof completed successfully", trade.getShortId());
log.info("###########################################################################################");
trade.onFiatPaymentReceived(() -> {
}, errorMessage -> {
});
}
if (assetTxProofResult.isTerminal()) {
servicesByTradeId.remove(trade.getId());
}
},
(errorMessage, throwable) -> {
log.error(errorMessage);
});
}
///////////////////////////////////////////////////////////////////////////////////////////
// Startup checks
///////////////////////////////////////////////////////////////////////////////////////////
private BooleanProperty isBtcBlockDownloadComplete() {
BooleanProperty result = new SimpleBooleanProperty();
if (walletsSetup.isDownloadComplete()) {
result.set(true);
} else {
btcBlockListener = (observable, oldValue, newValue) -> {
if (walletsSetup.isDownloadComplete()) {
walletsSetup.downloadPercentageProperty().removeListener(btcBlockListener);
result.set(true);
}
};
walletsSetup.downloadPercentageProperty().addListener(btcBlockListener);
}
return result;
}
private BooleanProperty hasSufficientBtcPeers() {
BooleanProperty result = new SimpleBooleanProperty();
if (walletsSetup.hasSufficientPeersForBroadcast()) {
result.set(true);
} else {
btcPeersListener = (observable, oldValue, newValue) -> {
if (walletsSetup.hasSufficientPeersForBroadcast()) {
walletsSetup.numPeersProperty().removeListener(btcPeersListener);
result.set(true);
}
};
walletsSetup.numPeersProperty().addListener(btcPeersListener);
}
return result;
}
private BooleanProperty isP2pBootstrapped() {
BooleanProperty result = new SimpleBooleanProperty();
if (p2PService.isBootstrapped()) {
result.set(true);
} else {
bootstrapListener = new BootstrapListener() {
@Override
public void onUpdatedDataReceived() {
p2PService.removeP2PServiceListener(bootstrapListener);
result.set(true);
}
};
p2PService.addP2PServiceListener(bootstrapListener);
}
return result;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Validation
///////////////////////////////////////////////////////////////////////////////////////////
private boolean isXmrBuyer(Trade trade) {
if (!checkNotNull(trade.getOffer()).getCurrencyCode().equals("XMR")) {
return false;
}
private boolean isXmrTrade(Trade trade) {
return (checkNotNull(trade.getOffer()).getCurrencyCode().equals("XMR"));
}
if (!(trade instanceof SellerTrade)) {
return false;
}
return checkNotNull(trade.getContract()).getSellerPaymentAccountPayload() instanceof AssetsAccountPayload;
private boolean isExpectedTradeState(Trade.State newValue) {
return newValue == Trade.State.SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG;
}
private boolean is32BitHexStringInValid(String hexString) {
@ -192,12 +336,6 @@ public class XmrTxProofService implements AssetTxProofService {
return false;
}
private boolean networkAndWalletReady() {
return p2PService.isBootstrapped() &&
walletsSetup.isDownloadComplete() &&
walletsSetup.hasSufficientPeersForBroadcast();
}
private boolean isAutoConfDisabledByFilter() {
return filterManager.getFilter() != null &&
filterManager.getFilter().isDisableAutoConf();

View File

@ -25,10 +25,13 @@ import org.bitcoinj.core.Coin;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CopyOnWriteArrayList;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Getter
public final class AutoConfirmSettings implements PersistablePayload {
public interface Listener {
@ -42,13 +45,21 @@ public final class AutoConfirmSettings implements PersistablePayload {
private String currencyCode;
private List<Listener> listeners = new CopyOnWriteArrayList<>();
static AutoConfirmSettings getDefaultForXmr(List<String> serviceAddresses) {
return new AutoConfirmSettings(
false,
5,
Coin.COIN.value,
serviceAddresses,
"XMR");
@SuppressWarnings("SameParameterValue")
static Optional<AutoConfirmSettings> getDefault(List<String> serviceAddresses, String currencyCode) {
//noinspection SwitchStatementWithTooFewBranches
switch (currencyCode) {
case "XMR":
return Optional.of(new AutoConfirmSettings(
false,
5,
Coin.COIN.value,
serviceAddresses,
"XMR"));
default:
log.error("No AutoConfirmSettings supported yet for currency {}", currencyCode);
return Optional.empty();
}
}
public AutoConfirmSettings(boolean enabled,

View File

@ -33,7 +33,6 @@ import bisq.core.setup.CoreNetworkCapabilities;
import bisq.network.p2p.network.BridgeAddressProvider;
import bisq.common.app.DevEnv;
import bisq.common.config.BaseCurrencyNetwork;
import bisq.common.config.Config;
import bisq.common.proto.persistable.PersistedDataHost;
@ -124,16 +123,12 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
new BlockChainExplorer("bsq.bisq.cc (@m52go)", "https://bsq.bisq.cc/tx.html?tx=", "https://bsq.bisq.cc/Address.html?addr=")
));
// list of XMR proof providers : this list will be used if no preference has been set
public static final List<String> getDefaultXmrProofProviders() {
if (DevEnv.isDevMode()) {
return new ArrayList<>(Arrays.asList("78.47.61.90:8081"));
} else {
// TODO we need at least 2 for release
return new ArrayList<>(Arrays.asList(
"monero3bec7m26vx6si6qo7q7imlaoz45ot5m2b5z2ppgoooo6jx2rqd.onion"));
}
}
//TODO add a second before release
private static final ArrayList<String> XMR_TX_PROOF_SERVICES_CLEAR_NET = new ArrayList<>(Arrays.asList(
"78.47.61.90:8081"));
//TODO add a second before release
private static final ArrayList<String> XMR_TX_PROOF_SERVICES = new ArrayList<>(Arrays.asList(
"monero3bec7m26vx6si6qo7q7imlaoz45ot5m2b5z2ppgoooo6jx2rqd.onion"));
public static final boolean USE_SYMMETRIC_SECURITY_DEPOSIT = true;
@ -321,7 +316,11 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
}
if (prefPayload.getAutoConfirmSettingsList().isEmpty()) {
getAutoConfirmSettingsList().add(AutoConfirmSettings.getDefaultForXmr(getDefaultXmrProofProviders()));
List<String> defaultXmrTxProofServices = getDefaultXmrTxProofServices();
AutoConfirmSettings.getDefault(defaultXmrTxProofServices, "XMR")
.ifPresent(xmrAutoConfirmSettings -> {
getAutoConfirmSettingsList().add(xmrAutoConfirmSettings);
});
}
// We set the capability in CoreNetworkCapabilities if the program argument is set.
@ -332,7 +331,6 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
persist();
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
@ -875,6 +873,14 @@ public final class Preferences implements PersistedDataHost, BridgeAddressProvid
}
}
public List<String> getDefaultXmrTxProofServices() {
if (config.useLocalhostForP2P) {
return XMR_TX_PROOF_SERVICES_CLEAR_NET;
} else {
return XMR_TX_PROOF_SERVICES;
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private

View File

@ -52,9 +52,7 @@ import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAs
public final class PreferencesPayload implements UserThreadMappedPersistableEnvelope {
private String userLanguage;
private Country userCountry;
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
private List<FiatCurrency> fiatCurrencies = new ArrayList<>();
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
private List<CryptoCurrency> cryptoCurrencies = new ArrayList<>();
private BlockChainExplorer blockChainExplorerMainNet;
private BlockChainExplorer blockChainExplorerTestNet;

View File

@ -572,9 +572,9 @@ portfolio.pending.step5.completed=Completed
portfolio.pending.step3_seller.autoConf.status.label=Auto-confirm status
portfolio.pending.autoConf=Auto-confirmed
portfolio.pending.autoConf.blocks=XMR confirmations: {0} / Required: {1}
portfolio.pending.autoConf.state.xmr.txKeyReused=Transaction key re-used. Please open a dispute.
portfolio.pending.autoConf.state.confirmations=Confirmations: {0}/{1}
portfolio.pending.autoConf.state.confirmations=XMR confirmations: {0}/{1}
portfolio.pending.autoConf.state.txNotFound=Transaction not seen in mem-pool yet
portfolio.pending.autoConf.state.txKeyOrTxIdInvalid=No valid transaction ID / transaction key
portfolio.pending.autoConf.state.filterDisabledFeature=Disabled by developers.
@ -588,9 +588,11 @@ portfolio.pending.autoConf.state.INVALID_DATA=Peer provided invalid data. {0}
# suppress inspection "UnusedProperty"
portfolio.pending.autoConf.state.PAYOUT_TX_ALREADY_PUBLISHED=Payout transaction was already published.
# suppress inspection "UnusedProperty"
portfolio.pending.autoConf.state.REQUESTS_STARTED=Proof requests started
portfolio.pending.autoConf.state.DISPUTE_OPENED=Dispute was opened. Auto-confirm is deactivated for that trade.
# suppress inspection "UnusedProperty"
portfolio.pending.autoConf.state.PENDING=Service results: {0}/{1}; {2}
portfolio.pending.autoConf.state.REQUESTS_STARTED=Transaction proof requests started
# suppress inspection "UnusedProperty"
portfolio.pending.autoConf.state.PENDING=Success results: {0}/{1}; {2}
# suppress inspection "UnusedProperty"
portfolio.pending.autoConf.state.COMPLETED=Proof at all services succeeded
# suppress inspection "UnusedProperty"

View File

@ -436,8 +436,10 @@ tree-table-view:focused {
-fx-pref-width: 30;
}
.jfx-badge.autoconf .badge-pane {
-fx-pref-width: 100;
.jfx-badge.auto-conf .badge-pane {
-fx-background-color: -xmr-orange;
-fx-pref-width: -1;
-fx-padding: -1 10 0 10;
}
.jfx-badge .badge-pane .label {
@ -856,6 +858,12 @@ textfield */
-fx-max-height: 20;
}
#xmr-confidence {
-fx-progress-color: -xmr-orange;
-fx-max-width: 20;
-fx-max-height: 20;
}
.hyperlink,
.hyperlink.force-underline .text,
.hyperlink:hover,

View File

@ -194,7 +194,7 @@ public class PendingTradesDataModel extends ActivatableDataModel {
public void onFiatPaymentReceived(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
checkNotNull(getTrade(), "trade must not be null");
checkArgument(getTrade() instanceof SellerTrade, "Trade must be instance of SellerTrade");
tradeManager.onFiatPaymentReceived((SellerTrade) getTrade(), resultHandler, errorMessageHandler);
tradeManager.onUserConfirmedFiatPaymentReceived((SellerTrade) getTrade(), resultHandler, errorMessageHandler);
}
public void onWithdrawRequest(String toAddress,

View File

@ -112,7 +112,7 @@ public class BuyerStep4View extends TradeStepView {
JFXBadge autoConfBadge = new JFXBadge(new Label(""), Pos.BASELINE_RIGHT);
autoConfBadge.setText(Res.get("portfolio.pending.autoConf"));
autoConfBadge.getStyleClass().add("autoconf");
autoConfBadge.getStyleClass().add("auto-conf");
HBox hBox2 = new HBox(1, completedTradeLabel, autoConfBadge);
GridPane.setMargin(hBox2, new Insets(18, -10, -12, -10));

View File

@ -18,8 +18,10 @@
package bisq.desktop.main.portfolio.pendingtrades.steps.seller;
import bisq.desktop.components.BusyAnimation;
import bisq.desktop.components.InfoTextField;
import bisq.desktop.components.TextFieldWithCopyIcon;
import bisq.desktop.components.TitledGroupBg;
import bisq.desktop.components.indicator.TxConfidenceIndicator;
import bisq.desktop.main.overlays.popups.Popup;
import bisq.desktop.main.portfolio.pendingtrades.PendingTradesViewModel;
import bisq.desktop.main.portfolio.pendingtrades.steps.TradeStepView;
@ -49,6 +51,7 @@ import bisq.core.user.DontShowAgainLookup;
import bisq.common.Timer;
import bisq.common.UserThread;
import bisq.common.app.DevEnv;
import bisq.common.util.Tuple2;
import bisq.common.util.Tuple4;
import javafx.scene.control.Button;
@ -57,6 +60,9 @@ import javafx.scene.control.Tooltip;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.geometry.Insets;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
@ -65,20 +71,20 @@ import javafx.beans.value.ChangeListener;
import java.util.Optional;
import static bisq.desktop.util.FormBuilder.addButtonBusyAnimationLabelAfterGroup;
import static bisq.desktop.util.FormBuilder.addCompactTopLabelTextFieldWithCopyIcon;
import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
import static bisq.desktop.util.FormBuilder.addTopLabelTextFieldWithCopyIcon;
import javax.annotation.Nullable;
import static bisq.desktop.util.FormBuilder.*;
public class SellerStep3View extends TradeStepView {
private final ChangeListener<Number> proofResultListener;
private Button confirmButton;
private Label statusLabel;
private BusyAnimation busyAnimation;
private Subscription tradeStatePropertySubscription;
private Timer timeoutTimer;
private TextFieldWithCopyIcon assetTxProofResultField;
private final ChangeListener<Number> proofResultListener;
private InfoTextField assetTxProofResultField;
private TxConfidenceIndicator assetTxConfidenceIndicator;
///////////////////////////////////////////////////////////////////////////////////////////
@ -230,9 +236,28 @@ public class SellerStep3View extends TradeStepView {
}
if (isBlockChain && trade.getOffer().getCurrencyCode().equals("XMR")) {
assetTxProofResultField = addTopLabelTextFieldWithCopyIcon(gridPane, gridRow, 1,
Res.get("portfolio.pending.step3_seller.autoConf.status.label"),
"", Layout.COMPACT_FIRST_ROW_AND_GROUP_DISTANCE).second;
assetTxProofResultField = new InfoTextField();
Tuple2<Label, VBox> topLabelWithVBox = getTopLabelWithVBox(Res.get("portfolio.pending.step3_seller.autoConf.status.label"), assetTxProofResultField);
VBox vBox = topLabelWithVBox.second;
assetTxConfidenceIndicator = new TxConfidenceIndicator();
assetTxConfidenceIndicator.setId("xmr-confidence");
assetTxConfidenceIndicator.setProgress(0);
assetTxConfidenceIndicator.setTooltip(new Tooltip());
assetTxProofResultField.setContentForInfoPopOver(createPopoverLabel(Res.get("setting.info.msg")));
HBox.setMargin(assetTxConfidenceIndicator, new Insets(Layout.FLOATING_LABEL_DISTANCE, 0, 0, 0));
HBox hBox = new HBox();
HBox.setHgrow(vBox, Priority.ALWAYS);
hBox.setSpacing(10);
hBox.getChildren().addAll(vBox, assetTxConfidenceIndicator);
GridPane.setRowIndex(hBox, gridRow);
GridPane.setColumnIndex(hBox, 1);
GridPane.setMargin(hBox, new Insets(Layout.COMPACT_FIRST_ROW_AND_GROUP_DISTANCE + Layout.FLOATING_LABEL_DISTANCE, 0, 0, 0));
gridPane.getChildren().add(hBox);
}
TextFieldWithCopyIcon myPaymentDetailsTextField = addCompactTopLabelTextFieldWithCopyIcon(gridPane, ++gridRow,
@ -274,7 +299,6 @@ public class SellerStep3View extends TradeStepView {
// Info
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected String getInfoText() {
String currencyCode = model.dataModel.getCurrencyCode();
@ -443,10 +467,44 @@ public class SellerStep3View extends TradeStepView {
}
}
private void applyAssetTxProofResult(AssetTxProofResult result) {
private void applyAssetTxProofResult(@Nullable AssetTxProofResult result) {
String txt = GUIUtil.getProofResultAsString(result);
assetTxProofResultField.setText(txt);
assetTxProofResultField.setTooltip(new Tooltip(txt));
if (result == null) {
assetTxConfidenceIndicator.setProgress(0);
return;
}
switch (result) {
case PENDING:
case COMPLETED:
if (result.getNumRequiredConfirmations() > 0) {
int numRequiredConfirmations = result.getNumRequiredConfirmations();
int numConfirmations = result.getNumConfirmations();
if (numConfirmations == 0) {
assetTxConfidenceIndicator.setProgress(-1);
} else {
double progress = Math.min(1, (double) numConfirmations / (double) numRequiredConfirmations);
assetTxConfidenceIndicator.setProgress(progress);
assetTxConfidenceIndicator.getTooltip().setText(
Res.get("portfolio.pending.autoConf.blocks",
numConfirmations, numRequiredConfirmations));
}
}
break;
default:
// Set invisible by default
assetTxConfidenceIndicator.setProgress(0);
break;
}
}
private Label createPopoverLabel(String text) {
Label label = new Label(text);
label.setPrefWidth(600);
label.setWrapText(true);
label.setPadding(new Insets(10));
return label;
}
@Override

View File

@ -661,7 +661,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
autoConfirmXmrToggle = addSlideToggleButton(autoConfirmGridPane, localRowIndex, Res.get("setting.preferences.autoConfirmEnabled"), Layout.FIRST_ROW_DISTANCE);
autoConfRequiredConfirmationsTf = addInputTextField(autoConfirmGridPane, ++localRowIndex, Res.get("setting.preferences.autoConfirmRequiredConfirmations"));
autoConfRequiredConfirmationsTf.setValidator(new IntegerValidator(0, DevEnv.isDevMode() ? 100000000 : 1000));
autoConfRequiredConfirmationsTf.setValidator(new IntegerValidator(1, DevEnv.isDevMode() ? 100000000 : 1000));
autoConfTradeLimitTf = addInputTextField(autoConfirmGridPane, ++localRowIndex, Res.get("setting.preferences.autoConfirmMaxTradeSize"));
autoConfTradeLimitTf.setValidator(new BtcValidator(formatter));
@ -677,7 +677,7 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
List<String> serviceAddresses = Arrays.asList(StringUtils.deleteWhitespace(newValue).split(","));
// revert to default service providers when user empties the list
if (serviceAddresses.size() == 1 && serviceAddresses.get(0).isEmpty()) {
serviceAddresses = Preferences.getDefaultXmrProofProviders();
serviceAddresses = preferences.getDefaultXmrTxProofServices();
}
preferences.setAutoConfServiceAddresses("XMR", serviceAddresses);
}

View File

@ -106,6 +106,9 @@
/* dao chart colors */
-bs-chart-dao-line1: -bs-color-green-3;
-bs-chart-dao-line2: -bs-color-blue-5;
/* Monero orange color code */
-xmr-orange: #f26822;
}
.warning-box {

View File

@ -1176,6 +1176,7 @@ public class GUIUtil {
case INVALID_DATA:
return Res.get(key, result.getDetails());
case PAYOUT_TX_ALREADY_PUBLISHED:
case DISPUTE_OPENED:
case REQUESTS_STARTED:
return Res.get(key);
case PENDING: