P2P status indicator with update prompt
Co-authored-by: jmacxx <47253594+jmacxx@users.noreply.github.com> Co-authored-by: Christoph Atteneder <christoph.atteneder@gmail.com>
This commit is contained in:
parent
a2929035bc
commit
bd70b935e4
@ -22,6 +22,11 @@ import bisq.network.p2p.NodeAddress;
|
|||||||
import bisq.network.p2p.P2PService;
|
import bisq.network.p2p.P2PService;
|
||||||
import bisq.network.p2p.SendMailboxMessageListener;
|
import bisq.network.p2p.SendMailboxMessageListener;
|
||||||
import bisq.network.p2p.mailbox.MailboxMessageService;
|
import bisq.network.p2p.mailbox.MailboxMessageService;
|
||||||
|
import bisq.network.p2p.network.Connection;
|
||||||
|
import bisq.network.p2p.network.MessageListener;
|
||||||
|
import bisq.network.p2p.network.NetworkNode;
|
||||||
|
import bisq.network.p2p.peers.keepalive.messages.Ping;
|
||||||
|
import bisq.network.p2p.peers.keepalive.messages.Pong;
|
||||||
|
|
||||||
import bisq.common.app.DevEnv;
|
import bisq.common.app.DevEnv;
|
||||||
import bisq.common.config.Config;
|
import bisq.common.config.Config;
|
||||||
@ -36,6 +41,10 @@ import javax.inject.Inject;
|
|||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
|
||||||
import com.google.common.base.Charsets;
|
import com.google.common.base.Charsets;
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import com.google.common.util.concurrent.Futures;
|
||||||
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
|
import com.google.common.util.concurrent.SettableFuture;
|
||||||
|
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||||
@ -45,16 +54,20 @@ import java.security.SignatureException;
|
|||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
import static org.bitcoinj.core.Utils.HEX;
|
import static org.bitcoinj.core.Utils.HEX;
|
||||||
|
|
||||||
public class PrivateNotificationManager {
|
public class PrivateNotificationManager implements MessageListener {
|
||||||
private static final Logger log = LoggerFactory.getLogger(PrivateNotificationManager.class);
|
private static final Logger log = LoggerFactory.getLogger(PrivateNotificationManager.class);
|
||||||
|
|
||||||
private final P2PService p2PService;
|
private final P2PService p2PService;
|
||||||
@ -68,6 +81,8 @@ public class PrivateNotificationManager {
|
|||||||
private ECKey privateNotificationSigningKey;
|
private ECKey privateNotificationSigningKey;
|
||||||
@Nullable
|
@Nullable
|
||||||
private PrivateNotificationMessage privateNotificationMessage;
|
private PrivateNotificationMessage privateNotificationMessage;
|
||||||
|
private final NetworkNode networkNode;
|
||||||
|
private Consumer<String> pingResponseHandler = null;
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
@ -76,11 +91,13 @@ public class PrivateNotificationManager {
|
|||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public PrivateNotificationManager(P2PService p2PService,
|
public PrivateNotificationManager(P2PService p2PService,
|
||||||
|
NetworkNode networkNode,
|
||||||
MailboxMessageService mailboxMessageService,
|
MailboxMessageService mailboxMessageService,
|
||||||
KeyRing keyRing,
|
KeyRing keyRing,
|
||||||
@Named(Config.IGNORE_DEV_MSG) boolean ignoreDevMsg,
|
@Named(Config.IGNORE_DEV_MSG) boolean ignoreDevMsg,
|
||||||
@Named(Config.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) {
|
@Named(Config.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) {
|
||||||
this.p2PService = p2PService;
|
this.p2PService = p2PService;
|
||||||
|
this.networkNode = networkNode;
|
||||||
this.mailboxMessageService = mailboxMessageService;
|
this.mailboxMessageService = mailboxMessageService;
|
||||||
this.keyRing = keyRing;
|
this.keyRing = keyRing;
|
||||||
|
|
||||||
@ -173,5 +190,38 @@ public class PrivateNotificationManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void sendPing(NodeAddress peersNodeAddress, Consumer<String> resultHandler) {
|
||||||
|
Ping ping = new Ping(new Random().nextInt(), 0);
|
||||||
|
log.info("Send Ping to peer {}, nonce={}", peersNodeAddress, ping.getNonce());
|
||||||
|
SettableFuture<Connection> future = networkNode.sendMessage(peersNodeAddress, ping);
|
||||||
|
Futures.addCallback(future, new FutureCallback<>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(Connection connection) {
|
||||||
|
connection.addMessageListener(PrivateNotificationManager.this);
|
||||||
|
pingResponseHandler = resultHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NotNull Throwable throwable) {
|
||||||
|
String errorMessage = "Sending ping to " + peersNodeAddress.getHostNameForDisplay() +
|
||||||
|
" failed. That is expected if the peer is offline.\n\tping=" + ping +
|
||||||
|
".\n\tException=" + throwable.getMessage();
|
||||||
|
log.info(errorMessage);
|
||||||
|
resultHandler.accept(errorMessage);
|
||||||
|
}
|
||||||
|
}, MoreExecutors.directExecutor());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMessage(NetworkEnvelope networkEnvelope, Connection connection) {
|
||||||
|
if (networkEnvelope instanceof Pong) {
|
||||||
|
Pong pong = (Pong) networkEnvelope;
|
||||||
|
String key = connection.getPeersNodeAddressOptional().get().getFullAddress();
|
||||||
|
log.info("Received Pong! {} from {}", pong, key);
|
||||||
|
connection.removeMessageListener(this);
|
||||||
|
if (pingResponseHandler != null) {
|
||||||
|
pingResponseHandler.accept("SUCCESS");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,7 +96,7 @@ public class HavenoHeadlessApp implements HeadlessApp {
|
|||||||
bisqSetup.setQubesOSInfoHandler(() -> log.info("setQubesOSInfoHandler"));
|
bisqSetup.setQubesOSInfoHandler(() -> log.info("setQubesOSInfoHandler"));
|
||||||
bisqSetup.setDownGradePreventionHandler(lastVersion -> log.info("Downgrade from version {} to version {} is not supported",
|
bisqSetup.setDownGradePreventionHandler(lastVersion -> log.info("Downgrade from version {} to version {} is not supported",
|
||||||
lastVersion, Version.VERSION));
|
lastVersion, Version.VERSION));
|
||||||
|
bisqSetup.setTorAddressUpgradeHandler(() -> log.info("setTorAddressUpgradeHandler"));
|
||||||
corruptedStorageFileHandler.getFiles().ifPresent(files -> log.warn("getCorruptedDatabaseFiles. files={}", files));
|
corruptedStorageFileHandler.getFiles().ifPresent(files -> log.warn("getCorruptedDatabaseFiles. files={}", files));
|
||||||
tradeManager.setTakeOfferRequestErrorMessageHandler(errorMessage -> log.error("Error taking offer: " + errorMessage));
|
tradeManager.setTakeOfferRequestErrorMessageHandler(errorMessage -> log.error("Error taking offer: " + errorMessage));
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import bisq.core.account.sign.SignedWitnessStorageService;
|
|||||||
import bisq.core.account.witness.AccountAgeWitnessService;
|
import bisq.core.account.witness.AccountAgeWitnessService;
|
||||||
import bisq.core.alert.Alert;
|
import bisq.core.alert.Alert;
|
||||||
import bisq.core.alert.AlertManager;
|
import bisq.core.alert.AlertManager;
|
||||||
|
import bisq.core.alert.PrivateNotificationManager;
|
||||||
import bisq.core.alert.PrivateNotificationPayload;
|
import bisq.core.alert.PrivateNotificationPayload;
|
||||||
import bisq.core.api.CoreMoneroNodeService;
|
import bisq.core.api.CoreMoneroNodeService;
|
||||||
import bisq.core.btc.model.AddressEntry;
|
import bisq.core.btc.model.AddressEntry;
|
||||||
@ -37,6 +38,10 @@ import bisq.core.payment.AmazonGiftCardAccount;
|
|||||||
import bisq.core.payment.PaymentAccount;
|
import bisq.core.payment.PaymentAccount;
|
||||||
import bisq.core.payment.RevolutAccount;
|
import bisq.core.payment.RevolutAccount;
|
||||||
import bisq.core.payment.payload.PaymentMethod;
|
import bisq.core.payment.payload.PaymentMethod;
|
||||||
|
import bisq.core.support.dispute.Dispute;
|
||||||
|
import bisq.core.support.dispute.arbitration.ArbitrationManager;
|
||||||
|
import bisq.core.support.dispute.mediation.MediationManager;
|
||||||
|
import bisq.core.support.dispute.refund.RefundManager;
|
||||||
import bisq.core.trade.TradeManager;
|
import bisq.core.trade.TradeManager;
|
||||||
import bisq.core.trade.TradeTxException;
|
import bisq.core.trade.TradeTxException;
|
||||||
import bisq.core.user.Preferences;
|
import bisq.core.user.Preferences;
|
||||||
@ -45,8 +50,10 @@ import bisq.core.util.FormattingUtils;
|
|||||||
import bisq.core.util.coin.CoinFormatter;
|
import bisq.core.util.coin.CoinFormatter;
|
||||||
|
|
||||||
import bisq.network.Socks5ProxyProvider;
|
import bisq.network.Socks5ProxyProvider;
|
||||||
|
import bisq.network.p2p.NodeAddress;
|
||||||
import bisq.network.p2p.P2PService;
|
import bisq.network.p2p.P2PService;
|
||||||
import bisq.network.p2p.storage.payload.PersistableNetworkPayload;
|
import bisq.network.p2p.storage.payload.PersistableNetworkPayload;
|
||||||
|
import bisq.network.utils.Utils;
|
||||||
|
|
||||||
import bisq.common.Timer;
|
import bisq.common.Timer;
|
||||||
import bisq.common.UserThread;
|
import bisq.common.UserThread;
|
||||||
@ -86,6 +93,8 @@ import java.io.IOException;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Random;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@ -106,19 +115,6 @@ public class HavenoSetup {
|
|||||||
private static final String VERSION_FILE_NAME = "version";
|
private static final String VERSION_FILE_NAME = "version";
|
||||||
private static final String RESYNC_SPV_FILE_NAME = "resyncSpv";
|
private static final String RESYNC_SPV_FILE_NAME = "resyncSpv";
|
||||||
|
|
||||||
public interface HavenoSetupListener {
|
|
||||||
default void onInitP2pNetwork() {
|
|
||||||
}
|
|
||||||
|
|
||||||
default void onInitWallet() {
|
|
||||||
}
|
|
||||||
|
|
||||||
default void onRequestWalletPassword() {
|
|
||||||
}
|
|
||||||
|
|
||||||
void onSetupComplete();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final long STARTUP_TIMEOUT_MINUTES = 4;
|
private static final long STARTUP_TIMEOUT_MINUTES = 4;
|
||||||
|
|
||||||
private final DomainInitialisation domainInitialisation;
|
private final DomainInitialisation domainInitialisation;
|
||||||
@ -129,6 +125,7 @@ public class HavenoSetup {
|
|||||||
private final BtcWalletService btcWalletService;
|
private final BtcWalletService btcWalletService;
|
||||||
private final XmrWalletService xmrWalletService;
|
private final XmrWalletService xmrWalletService;
|
||||||
private final P2PService p2PService;
|
private final P2PService p2PService;
|
||||||
|
private final PrivateNotificationManager privateNotificationManager;
|
||||||
private final SignedWitnessStorageService signedWitnessStorageService;
|
private final SignedWitnessStorageService signedWitnessStorageService;
|
||||||
private final TradeManager tradeManager;
|
private final TradeManager tradeManager;
|
||||||
private final OpenOfferManager openOfferManager;
|
private final OpenOfferManager openOfferManager;
|
||||||
@ -141,7 +138,9 @@ public class HavenoSetup {
|
|||||||
private final CoinFormatter formatter;
|
private final CoinFormatter formatter;
|
||||||
private final LocalBitcoinNode localBitcoinNode;
|
private final LocalBitcoinNode localBitcoinNode;
|
||||||
private final AppStartupState appStartupState;
|
private final AppStartupState appStartupState;
|
||||||
|
private final MediationManager mediationManager;
|
||||||
|
private final RefundManager refundManager;
|
||||||
|
private final ArbitrationManager arbitrationManager;
|
||||||
@Setter
|
@Setter
|
||||||
@Nullable
|
@Nullable
|
||||||
private Consumer<Runnable> displayTacHandler;
|
private Consumer<Runnable> displayTacHandler;
|
||||||
@ -188,8 +187,10 @@ public class HavenoSetup {
|
|||||||
private Runnable qubesOSInfoHandler;
|
private Runnable qubesOSInfoHandler;
|
||||||
@Setter
|
@Setter
|
||||||
@Nullable
|
@Nullable
|
||||||
|
private Runnable torAddressUpgradeHandler;
|
||||||
|
@Setter
|
||||||
|
@Nullable
|
||||||
private Consumer<String> downGradePreventionHandler;
|
private Consumer<String> downGradePreventionHandler;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
final BooleanProperty newVersionAvailableProperty = new SimpleBooleanProperty(false);
|
final BooleanProperty newVersionAvailableProperty = new SimpleBooleanProperty(false);
|
||||||
private BooleanProperty p2pNetworkReady;
|
private BooleanProperty p2pNetworkReady;
|
||||||
@ -199,6 +200,19 @@ public class HavenoSetup {
|
|||||||
private MonadicBinding<Boolean> p2pNetworkAndWalletInitialized;
|
private MonadicBinding<Boolean> p2pNetworkAndWalletInitialized;
|
||||||
private final List<HavenoSetupListener> havenoSetupListeners = new ArrayList<>();
|
private final List<HavenoSetupListener> havenoSetupListeners = new ArrayList<>();
|
||||||
|
|
||||||
|
public interface HavenoSetupListener {
|
||||||
|
default void onInitP2pNetwork() {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void onInitWallet() {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void onRequestWalletPassword() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void onSetupComplete();
|
||||||
|
}
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public HavenoSetup(DomainInitialisation domainInitialisation,
|
public HavenoSetup(DomainInitialisation domainInitialisation,
|
||||||
P2PNetworkSetup p2PNetworkSetup,
|
P2PNetworkSetup p2PNetworkSetup,
|
||||||
@ -208,6 +222,7 @@ public class HavenoSetup {
|
|||||||
XmrWalletService xmrWalletService,
|
XmrWalletService xmrWalletService,
|
||||||
BtcWalletService btcWalletService,
|
BtcWalletService btcWalletService,
|
||||||
P2PService p2PService,
|
P2PService p2PService,
|
||||||
|
PrivateNotificationManager privateNotificationManager,
|
||||||
SignedWitnessStorageService signedWitnessStorageService,
|
SignedWitnessStorageService signedWitnessStorageService,
|
||||||
TradeManager tradeManager,
|
TradeManager tradeManager,
|
||||||
OpenOfferManager openOfferManager,
|
OpenOfferManager openOfferManager,
|
||||||
@ -220,7 +235,10 @@ public class HavenoSetup {
|
|||||||
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter,
|
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter,
|
||||||
LocalBitcoinNode localBitcoinNode,
|
LocalBitcoinNode localBitcoinNode,
|
||||||
AppStartupState appStartupState,
|
AppStartupState appStartupState,
|
||||||
Socks5ProxyProvider socks5ProxyProvider) {
|
Socks5ProxyProvider socks5ProxyProvider,
|
||||||
|
MediationManager mediationManager,
|
||||||
|
RefundManager refundManager,
|
||||||
|
ArbitrationManager arbitrationManager) {
|
||||||
this.domainInitialisation = domainInitialisation;
|
this.domainInitialisation = domainInitialisation;
|
||||||
this.p2PNetworkSetup = p2PNetworkSetup;
|
this.p2PNetworkSetup = p2PNetworkSetup;
|
||||||
this.walletAppSetup = walletAppSetup;
|
this.walletAppSetup = walletAppSetup;
|
||||||
@ -229,6 +247,7 @@ public class HavenoSetup {
|
|||||||
this.xmrWalletService = xmrWalletService;
|
this.xmrWalletService = xmrWalletService;
|
||||||
this.btcWalletService = btcWalletService;
|
this.btcWalletService = btcWalletService;
|
||||||
this.p2PService = p2PService;
|
this.p2PService = p2PService;
|
||||||
|
this.privateNotificationManager = privateNotificationManager;
|
||||||
this.signedWitnessStorageService = signedWitnessStorageService;
|
this.signedWitnessStorageService = signedWitnessStorageService;
|
||||||
this.tradeManager = tradeManager;
|
this.tradeManager = tradeManager;
|
||||||
this.openOfferManager = openOfferManager;
|
this.openOfferManager = openOfferManager;
|
||||||
@ -241,6 +260,9 @@ public class HavenoSetup {
|
|||||||
this.formatter = formatter;
|
this.formatter = formatter;
|
||||||
this.localBitcoinNode = localBitcoinNode;
|
this.localBitcoinNode = localBitcoinNode;
|
||||||
this.appStartupState = appStartupState;
|
this.appStartupState = appStartupState;
|
||||||
|
this.mediationManager = mediationManager;
|
||||||
|
this.refundManager = refundManager;
|
||||||
|
this.arbitrationManager = arbitrationManager;
|
||||||
|
|
||||||
MemPoolSpaceTxBroadcaster.init(socks5ProxyProvider, preferences, localBitcoinNode);
|
MemPoolSpaceTxBroadcaster.init(socks5ProxyProvider, preferences, localBitcoinNode);
|
||||||
}
|
}
|
||||||
@ -313,6 +335,8 @@ public class HavenoSetup {
|
|||||||
maybeShowSecurityRecommendation();
|
maybeShowSecurityRecommendation();
|
||||||
maybeShowLocalhostRunningInfo();
|
maybeShowLocalhostRunningInfo();
|
||||||
maybeShowAccountSigningStateInfo();
|
maybeShowAccountSigningStateInfo();
|
||||||
|
maybeShowTorAddressUpgradeInformation();
|
||||||
|
checkInboundConnections();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -663,6 +687,37 @@ public class HavenoSetup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if we have inbound connections. If not, try to ping ourselves.
|
||||||
|
* If Haveno cannot connect to its own onion address through Tor, display
|
||||||
|
* an informative message to let the user know to configure their firewall else
|
||||||
|
* their offers will not be reachable.
|
||||||
|
* Repeat this test hourly.
|
||||||
|
*/
|
||||||
|
private void checkInboundConnections() {
|
||||||
|
NodeAddress onionAddress = p2PService.getNetworkNode().nodeAddressProperty().get();
|
||||||
|
if (onionAddress == null || !onionAddress.getFullAddress().contains("onion")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p2PService.getNetworkNode().upTime() > TimeUnit.HOURS.toMillis(1) &&
|
||||||
|
p2PService.getNetworkNode().getInboundConnectionCount() == 0) {
|
||||||
|
// we've been online a while and did not find any inbound connections; lets try the self-ping check
|
||||||
|
log.info("no recent inbound connections found, starting the self-ping test");
|
||||||
|
privateNotificationManager.sendPing(onionAddress, stringResult -> {
|
||||||
|
log.info(stringResult);
|
||||||
|
if (stringResult.contains("failed")) {
|
||||||
|
getP2PNetworkStatusIconId().set("flashing:image-yellow_circle");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// schedule another inbound connection check for later
|
||||||
|
int nextCheckInMinutes = 30 + new Random().nextInt(30);
|
||||||
|
log.debug("next inbound connections check in {} minutes", nextCheckInMinutes);
|
||||||
|
UserThread.runAfter(this::checkInboundConnections, nextCheckInMinutes, TimeUnit.MINUTES);
|
||||||
|
}
|
||||||
|
|
||||||
private void maybeShowSecurityRecommendation() {
|
private void maybeShowSecurityRecommendation() {
|
||||||
if (user.getPaymentAccountsAsObservable() == null) return;
|
if (user.getPaymentAccountsAsObservable() == null) return;
|
||||||
String key = "remindPasswordAndBackup";
|
String key = "remindPasswordAndBackup";
|
||||||
@ -733,6 +788,30 @@ public class HavenoSetup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void maybeShowTorAddressUpgradeInformation() {
|
||||||
|
if (Config.baseCurrencyNetwork().isTestnet() ||
|
||||||
|
Utils.isV3Address(Objects.requireNonNull(p2PService.getNetworkNode().getNodeAddress()).getHostName())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
maybeRunTorNodeAddressUpgradeHandler();
|
||||||
|
|
||||||
|
tradeManager.getNumPendingTrades().addListener((observable, oldValue, newValue) -> {
|
||||||
|
long numPendingTrades = (long) newValue;
|
||||||
|
if (numPendingTrades == 0) {
|
||||||
|
maybeRunTorNodeAddressUpgradeHandler();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeRunTorNodeAddressUpgradeHandler() {
|
||||||
|
if (mediationManager.getDisputesAsObservableList().stream().allMatch(Dispute::isClosed) &&
|
||||||
|
refundManager.getDisputesAsObservableList().stream().allMatch(Dispute::isClosed) &&
|
||||||
|
arbitrationManager.getDisputesAsObservableList().stream().allMatch(Dispute::isClosed) &&
|
||||||
|
tradeManager.getNumPendingTrades().isEqualTo(0).get()) {
|
||||||
|
Objects.requireNonNull(torAddressUpgradeHandler).run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Getters
|
// Getters
|
||||||
@ -776,6 +855,10 @@ public class HavenoSetup {
|
|||||||
return p2PNetworkSetup.getP2PNetworkIconId();
|
return p2PNetworkSetup.getP2PNetworkIconId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public StringProperty getP2PNetworkStatusIconId() {
|
||||||
|
return p2PNetworkSetup.getP2PNetworkStatusIconId();
|
||||||
|
}
|
||||||
|
|
||||||
public BooleanProperty getUpdatedDataReceived() {
|
public BooleanProperty getUpdatedDataReceived() {
|
||||||
return p2PNetworkSetup.getUpdatedDataReceived();
|
return p2PNetworkSetup.getUpdatedDataReceived();
|
||||||
}
|
}
|
||||||
|
@ -63,6 +63,8 @@ public class P2PNetworkSetup {
|
|||||||
@Getter
|
@Getter
|
||||||
final StringProperty p2PNetworkIconId = new SimpleStringProperty();
|
final StringProperty p2PNetworkIconId = new SimpleStringProperty();
|
||||||
@Getter
|
@Getter
|
||||||
|
final StringProperty p2PNetworkStatusIconId = new SimpleStringProperty();
|
||||||
|
@Getter
|
||||||
final BooleanProperty splashP2PNetworkAnimationVisible = new SimpleBooleanProperty(true);
|
final BooleanProperty splashP2PNetworkAnimationVisible = new SimpleBooleanProperty(true);
|
||||||
@Getter
|
@Getter
|
||||||
final StringProperty p2pNetworkLabelId = new SimpleStringProperty("footer-pane");
|
final StringProperty p2pNetworkLabelId = new SimpleStringProperty("footer-pane");
|
||||||
@ -118,10 +120,12 @@ public class P2PNetworkSetup {
|
|||||||
p2PService.getNetworkNode().addConnectionListener(new ConnectionListener() {
|
p2PService.getNetworkNode().addConnectionListener(new ConnectionListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onConnection(Connection connection) {
|
public void onConnection(Connection connection) {
|
||||||
|
updateNetworkStatusIndicator();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDisconnect(CloseConnectionReason closeConnectionReason, Connection connection) {
|
public void onDisconnect(CloseConnectionReason closeConnectionReason, Connection connection) {
|
||||||
|
updateNetworkStatusIndicator();
|
||||||
// We only check at seed nodes as they are running the latest version
|
// We only check at seed nodes as they are running the latest version
|
||||||
// Other disconnects might be caused by peers running an older version
|
// Other disconnects might be caused by peers running an older version
|
||||||
if (connection.getConnectionState().isSeedNode() &&
|
if (connection.getConnectionState().isSeedNode() &&
|
||||||
@ -225,4 +229,14 @@ public class P2PNetworkSetup {
|
|||||||
public void setSplashP2PNetworkAnimationVisible(boolean value) {
|
public void setSplashP2PNetworkAnimationVisible(boolean value) {
|
||||||
splashP2PNetworkAnimationVisible.set(value);
|
splashP2PNetworkAnimationVisible.set(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateNetworkStatusIndicator() {
|
||||||
|
if (p2PService.getNetworkNode().getInboundConnectionCount() > 0) {
|
||||||
|
p2PNetworkStatusIconId.set("image-green_circle");
|
||||||
|
} else if (p2PService.getNetworkNode().getOutboundConnectionCount() > 0) {
|
||||||
|
p2PNetworkStatusIconId.set("image-yellow_circle");
|
||||||
|
} else {
|
||||||
|
p2PNetworkStatusIconId.set("image-alert-round");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -287,6 +287,7 @@ mainView.walletServiceErrorMsg.rejectedTxException=A transaction was rejected fr
|
|||||||
mainView.networkWarning.allConnectionsLost=You lost the connection to all {0} network peers.\nMaybe you lost your internet connection or your computer was in standby mode.
|
mainView.networkWarning.allConnectionsLost=You lost the connection to all {0} network peers.\nMaybe you lost your internet connection or your computer was in standby mode.
|
||||||
mainView.networkWarning.localhostBitcoinLost=You lost the connection to the localhost Monero node.\nPlease restart the Haveno application to connect to other Monero nodes or restart the localhost Monero node.
|
mainView.networkWarning.localhostBitcoinLost=You lost the connection to the localhost Monero node.\nPlease restart the Haveno application to connect to other Monero nodes or restart the localhost Monero node.
|
||||||
mainView.version.update=(Update available)
|
mainView.version.update=(Update available)
|
||||||
|
mainView.status.connections=Inbound connections: {0}\nOutbound connections: {1}
|
||||||
|
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
@ -2136,6 +2137,12 @@ popup.info.shutDownWithTradeInit={0}\n\
|
|||||||
This trade has not finished initializing; shutting down now will probably make it corrupted. Please wait a minute and try again.
|
This trade has not finished initializing; shutting down now will probably make it corrupted. Please wait a minute and try again.
|
||||||
popup.info.qubesOSSetupInfo=It appears you are running Bisq on Qubes OS. \n\n\
|
popup.info.qubesOSSetupInfo=It appears you are running Bisq on Qubes OS. \n\n\
|
||||||
Please make sure your Bisq qube is setup according to our Setup Guide at [HYPERLINK:https://bisq.wiki/Running_Bisq_on_Qubes].
|
Please make sure your Bisq qube is setup according to our Setup Guide at [HYPERLINK:https://bisq.wiki/Running_Bisq_on_Qubes].
|
||||||
|
popup.info.p2pStatusIndicator.red={0}\n\n\
|
||||||
|
Your node has no connection to the P2P network. Haveno cannot operate in this state.
|
||||||
|
popup.info.p2pStatusIndicator.yellow={0}\n\n\
|
||||||
|
Your node has no inbound Tor connections. Haveno will function ok, but if this state persists for several hours it may be an indication of connectivity problems.
|
||||||
|
popup.info.p2pStatusIndicator.green={0}\n\n\
|
||||||
|
Good news, your P2P connection state looks healthy!
|
||||||
popup.info.firewallSetupInfo=It appears this machine blocks incoming Tor connections. \
|
popup.info.firewallSetupInfo=It appears this machine blocks incoming Tor connections. \
|
||||||
This can happen in VM environments such as Qubes/VirtualBox/Whonix. \n\n\
|
This can happen in VM environments such as Qubes/VirtualBox/Whonix. \n\n\
|
||||||
Please set up your environment to accept incoming Tor connections, otherwise no-one will be able to take your offers.
|
Please set up your environment to accept incoming Tor connections, otherwise no-one will be able to take your offers.
|
||||||
@ -2196,13 +2203,17 @@ popup.accountSigning.unsignedPubKeys.signed=Pubkeys were signed
|
|||||||
popup.accountSigning.unsignedPubKeys.result.signed=Signed pubkeys
|
popup.accountSigning.unsignedPubKeys.result.signed=Signed pubkeys
|
||||||
popup.accountSigning.unsignedPubKeys.result.failed=Failed to sign
|
popup.accountSigning.unsignedPubKeys.result.failed=Failed to sign
|
||||||
|
|
||||||
|
popup.info.torMigration.msg=Your Haveno node is probably using a deprecated Tor v2 address. \
|
||||||
|
Please switch your Haveno node to a Tor v3 address. \
|
||||||
|
Make sure to back up your data directory beforehand.
|
||||||
|
|
||||||
####################################################################
|
####################################################################
|
||||||
# Notifications
|
# Notifications
|
||||||
####################################################################
|
####################################################################
|
||||||
|
|
||||||
notification.trade.headline=Notification for trade with ID {0}
|
notification.trade.headline=Notification for trade with ID {0}
|
||||||
notification.ticket.headline=Support ticket for trade with ID {0}
|
notification.ticket.headline=Support ticket for trade with ID {0}
|
||||||
notification.trade.completed=The trade is now completed and you can withdraw your funds.
|
notification.trade.completed=The trade is now completed, and you can withdraw your funds.
|
||||||
notification.trade.accepted=Your offer has been accepted by a XMR {0}.
|
notification.trade.accepted=Your offer has been accepted by a XMR {0}.
|
||||||
notification.trade.unlocked=Your trade has been confirmed.\nYou can start the payment now.
|
notification.trade.unlocked=Your trade has been confirmed.\nYou can start the payment now.
|
||||||
notification.trade.paymentStarted=The XMR buyer has started the payment.
|
notification.trade.paymentStarted=The XMR buyer has started the payment.
|
||||||
@ -2232,7 +2243,7 @@ systemTray.tooltip=Haveno: A decentralized bitcoin exchange network
|
|||||||
####################################################################
|
####################################################################
|
||||||
|
|
||||||
guiUtil.miningFeeInfo=Please be sure that the mining fee used by your external wallet is \
|
guiUtil.miningFeeInfo=Please be sure that the mining fee used by your external wallet is \
|
||||||
at least {0} satoshis/vbyte. Otherwise the trade transactions may not be confirmed in time and the trade will end up in a dispute.
|
at least {0} satoshis/vbyte. Otherwise, the trade transactions may not be confirmed in time and the trade will end up in a dispute.
|
||||||
|
|
||||||
guiUtil.accountExport.savedToPath=Trading accounts saved to path:\n{0}
|
guiUtil.accountExport.savedToPath=Trading accounts saved to path:\n{0}
|
||||||
guiUtil.accountExport.noAccountSetup=You don't have trading accounts set up for exporting.
|
guiUtil.accountExport.noAccountSetup=You don't have trading accounts set up for exporting.
|
||||||
@ -2447,7 +2458,7 @@ payment.altcoin.address=Cryptocurrency address
|
|||||||
payment.altcoin.tradeInstantCheckbox=Trade instant (within 1 hour) with this Cryptocurrency
|
payment.altcoin.tradeInstantCheckbox=Trade instant (within 1 hour) with this Cryptocurrency
|
||||||
payment.altcoin.tradeInstant.popup=For instant trading it is required that both trading peers are online to be able \
|
payment.altcoin.tradeInstant.popup=For instant trading it is required that both trading peers are online to be able \
|
||||||
to complete the trade in less than 1 hour.\n\n\
|
to complete the trade in less than 1 hour.\n\n\
|
||||||
If you have offers open and you are not available please disable \
|
If you have offers open, and you are not available please disable \
|
||||||
those offers under the 'Portfolio' screen.
|
those offers under the 'Portfolio' screen.
|
||||||
payment.altcoin=Cryptocurrency
|
payment.altcoin=Cryptocurrency
|
||||||
payment.select.altcoin=Select or search Cryptocurrency
|
payment.select.altcoin=Select or search Cryptocurrency
|
||||||
@ -2563,7 +2574,7 @@ payment.westernUnion.info=When using Western Union the XMR buyer has to send the
|
|||||||
payment.halCash.info=When using HalCash the XMR buyer needs to send the XMR seller the HalCash code via a text message from their mobile phone.\n\n\
|
payment.halCash.info=When using HalCash the XMR buyer needs to send the XMR seller the HalCash code via a text message from their mobile phone.\n\n\
|
||||||
Please make sure to not exceed the maximum amount your bank allows you to send with HalCash. \
|
Please make sure to not exceed the maximum amount your bank allows you to send with HalCash. \
|
||||||
The min. amount per withdrawal is 10 EUR and the max. amount is 600 EUR. For repeated withdrawals it is \
|
The min. amount per withdrawal is 10 EUR and the max. amount is 600 EUR. For repeated withdrawals it is \
|
||||||
3000 EUR per receiver per day and 6000 EUR per receiver per month. Please cross check those limits with your \
|
3000 EUR per receiver per day and 6000 EUR per receiver per month. Please cross-check those limits with your \
|
||||||
bank to be sure they use the same limits as stated here.\n\n\
|
bank to be sure they use the same limits as stated here.\n\n\
|
||||||
The withdrawal amount must be a multiple of 10 EUR as you cannot withdraw other amounts from an ATM. The \
|
The withdrawal amount must be a multiple of 10 EUR as you cannot withdraw other amounts from an ATM. The \
|
||||||
UI in the create-offer and take-offer screen will adjust the XMR amount so that the EUR amount is correct. You cannot use market \
|
UI in the create-offer and take-offer screen will adjust the XMR amount so that the EUR amount is correct. You cannot use market \
|
||||||
@ -2645,9 +2656,9 @@ Please be aware there is a maximum of Rs. 200,000 that can be sent per transacti
|
|||||||
Some banks have different limits for their customers.
|
Some banks have different limits for their customers.
|
||||||
payment.imps.info.buyer=Please send payment only to the account details provided in Bisq.\n\n\
|
payment.imps.info.buyer=Please send payment only to the account details provided in Bisq.\n\n\
|
||||||
The maximum trade size is Rs. 200,000 per transaction.\n\n\
|
The maximum trade size is Rs. 200,000 per transaction.\n\n\
|
||||||
If your trade is over Rs. 200,000 you will have to make multiple transfers. However be aware their is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\n\
|
If your trade is over Rs. 200,000 you will have to make multiple transfers. However, be aware there is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\n\
|
||||||
Please note some banks have different limits for their customers.
|
Please note some banks have different limits for their customers.
|
||||||
payment.imps.info.seller=If you intend to receive over Rs. 200,000 per trade you should expect the buyer to have to make multiple transfers. However be aware there is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\n\
|
payment.imps.info.seller=If you intend to receive over Rs. 200,000 per trade you should expect the buyer to have to make multiple transfers. However, be aware there is a maximum limit of Rs. 1,000,000 that can be sent per day.\n\n\
|
||||||
Please note some banks have different limits for their customers.
|
Please note some banks have different limits for their customers.
|
||||||
|
|
||||||
payment.neft.info.account=Please make sure to include your:\n\n\
|
payment.neft.info.account=Please make sure to include your:\n\n\
|
||||||
@ -2865,7 +2876,7 @@ payment.strike.info.seller=Please make sure your payment is received from the BT
|
|||||||
The maximum trade size is $1,000 per payment.\n\n\
|
The maximum trade size is $1,000 per payment.\n\n\
|
||||||
If you trade over the above limits your trade might be cancelled and there could be a penalty.
|
If you trade over the above limits your trade might be cancelled and there could be a penalty.
|
||||||
|
|
||||||
payment.transferwiseUsd.info.account=Due US banking regulation, sending and receiving USD payments has more restrictions \
|
payment.transferwiseUsd.info.account=Due to US banking regulation, sending and receiving USD payments has more restrictions \
|
||||||
than most other currencies. For this reason USD was not added to Bisq TransferWise payment method.\n\n\
|
than most other currencies. For this reason USD was not added to Bisq TransferWise payment method.\n\n\
|
||||||
The TransferWise-USD payment method allows Bisq users to trade in USD.\n\n\
|
The TransferWise-USD payment method allows Bisq users to trade in USD.\n\n\
|
||||||
Anyone with a Wise, formally TransferWise account, can add TransferWise-USD as a payment method in Bisq. This will \
|
Anyone with a Wise, formally TransferWise account, can add TransferWise-USD as a payment method in Bisq. This will \
|
||||||
@ -3246,7 +3257,7 @@ validation.iban.checkSumInvalid=IBAN checksum is invalid
|
|||||||
validation.iban.invalidLength=Number must have a length of 15 to 34 chars.
|
validation.iban.invalidLength=Number must have a length of 15 to 34 chars.
|
||||||
validation.iban.sepaNotSupported=SEPA is not supported in this country
|
validation.iban.sepaNotSupported=SEPA is not supported in this country
|
||||||
validation.interacETransfer.invalidAreaCode=Non-Canadian area code
|
validation.interacETransfer.invalidAreaCode=Non-Canadian area code
|
||||||
validation.interacETransfer.invalidPhone=Please enter a valid 11 digit phone number (ex: 1-123-456-7890) or an email address
|
validation.interacETransfer.invalidPhone=Please enter a valid 11-digit phone number (ex: 1-123-456-7890) or an email address
|
||||||
validation.interacETransfer.invalidQuestion=Must contain only letters, numbers, spaces and/or the symbols ' _ , . ? -
|
validation.interacETransfer.invalidQuestion=Must contain only letters, numbers, spaces and/or the symbols ' _ , . ? -
|
||||||
validation.interacETransfer.invalidAnswer=Must be one word and contain only letters, numbers, and/or the symbol -
|
validation.interacETransfer.invalidAnswer=Must be one word and contain only letters, numbers, and/or the symbol -
|
||||||
validation.inputTooLarge=Input must not be larger than {0}
|
validation.inputTooLarge=Input must not be larger than {0}
|
||||||
@ -3263,7 +3274,7 @@ validation.mustBeDifferent=Your input must be different from the current value
|
|||||||
validation.cannotBeChanged=Parameter cannot be changed
|
validation.cannotBeChanged=Parameter cannot be changed
|
||||||
validation.numberFormatException=Number format exception {0}
|
validation.numberFormatException=Number format exception {0}
|
||||||
validation.mustNotBeNegative=Input must not be negative
|
validation.mustNotBeNegative=Input must not be negative
|
||||||
validation.phone.missingCountryCode=Need two letter country code to validate phone number
|
validation.phone.missingCountryCode=Need two-letter country code to validate phone number
|
||||||
validation.phone.invalidCharacters=Phone number {0} contains invalid characters
|
validation.phone.invalidCharacters=Phone number {0} contains invalid characters
|
||||||
validation.phone.insufficientDigits=There are not enough digits in {0} to be a valid phone number
|
validation.phone.insufficientDigits=There are not enough digits in {0} to be a valid phone number
|
||||||
validation.phone.tooManyDigits=There are too many digits in {0} to be a valid phone number
|
validation.phone.tooManyDigits=There are too many digits in {0} to be a valid phone number
|
||||||
|
@ -21,6 +21,10 @@
|
|||||||
-fx-image: url("../../images/green_circle.png");
|
-fx-image: url("../../images/green_circle.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#image-yellow_circle {
|
||||||
|
-fx-image: url("../../images/yellow_circle.png");
|
||||||
|
}
|
||||||
|
|
||||||
#image-blue_circle {
|
#image-blue_circle {
|
||||||
-fx-image: url("../../images/blue_circle.png");
|
-fx-image: url("../../images/blue_circle.png");
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,7 @@ import bisq.desktop.main.market.offerbook.OfferBookChartView;
|
|||||||
import bisq.desktop.main.offer.BuyOfferView;
|
import bisq.desktop.main.offer.BuyOfferView;
|
||||||
import bisq.desktop.main.offer.SellOfferView;
|
import bisq.desktop.main.offer.SellOfferView;
|
||||||
import bisq.desktop.main.overlays.popups.Popup;
|
import bisq.desktop.main.overlays.popups.Popup;
|
||||||
|
import bisq.desktop.main.overlays.windows.TorNetworkSettingsWindow;
|
||||||
import bisq.desktop.main.portfolio.PortfolioView;
|
import bisq.desktop.main.portfolio.PortfolioView;
|
||||||
import bisq.desktop.main.settings.SettingsView;
|
import bisq.desktop.main.settings.SettingsView;
|
||||||
import bisq.desktop.main.shared.PriceFeedComboBoxItem;
|
import bisq.desktop.main.shared.PriceFeedComboBoxItem;
|
||||||
@ -58,6 +59,10 @@ import com.jfoenix.controls.JFXBadge;
|
|||||||
import com.jfoenix.controls.JFXComboBox;
|
import com.jfoenix.controls.JFXComboBox;
|
||||||
import com.jfoenix.controls.JFXProgressBar;
|
import com.jfoenix.controls.JFXProgressBar;
|
||||||
|
|
||||||
|
import javafx.animation.Animation;
|
||||||
|
import javafx.animation.KeyFrame;
|
||||||
|
import javafx.animation.Timeline;
|
||||||
|
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.ComboBox;
|
import javafx.scene.control.ComboBox;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
@ -91,6 +96,8 @@ import javafx.beans.property.SimpleStringProperty;
|
|||||||
import javafx.beans.property.StringProperty;
|
import javafx.beans.property.StringProperty;
|
||||||
import javafx.beans.value.ChangeListener;
|
import javafx.beans.value.ChangeListener;
|
||||||
|
|
||||||
|
import javafx.util.Duration;
|
||||||
|
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
|
|
||||||
@ -114,6 +121,22 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
|
|||||||
private final static int SHOW_TOR_SETTINGS_DELAY_SEC = 90;
|
private final static int SHOW_TOR_SETTINGS_DELAY_SEC = 90;
|
||||||
@Setter
|
@Setter
|
||||||
private Runnable onApplicationStartedHandler;
|
private Runnable onApplicationStartedHandler;
|
||||||
|
private static Transitions transitions;
|
||||||
|
private static StackPane rootContainer;
|
||||||
|
private final ViewLoader viewLoader;
|
||||||
|
private final Navigation navigation;
|
||||||
|
private final ToggleGroup navButtons = new ToggleGroup();
|
||||||
|
private ChangeListener<String> walletServiceErrorMsgListener;
|
||||||
|
private ChangeListener<String> btcSyncIconIdListener;
|
||||||
|
private ChangeListener<String> splashP2PNetworkErrorMsgListener;
|
||||||
|
private ChangeListener<String> splashP2PNetworkIconIdListener;
|
||||||
|
private ChangeListener<Boolean> splashP2PNetworkVisibleListener;
|
||||||
|
private BusyAnimation splashP2PNetworkBusyAnimation;
|
||||||
|
private Label splashP2PNetworkLabel;
|
||||||
|
private ProgressBar btcSyncIndicator, p2pNetworkProgressBar;
|
||||||
|
private Label btcSplashInfo;
|
||||||
|
private Popup p2PNetworkWarnMsgPopup, btcNetworkWarnMsgPopup;
|
||||||
|
private final TorNetworkSettingsWindow torNetworkSettingsWindow;
|
||||||
|
|
||||||
public static StackPane getRootContainer() {
|
public static StackPane getRootContainer() {
|
||||||
return MainView.rootContainer;
|
return MainView.rootContainer;
|
||||||
@ -135,34 +158,17 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
|
|||||||
transitions.removeEffect(MainView.rootContainer);
|
transitions.removeEffect(MainView.rootContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Transitions transitions;
|
|
||||||
private static StackPane rootContainer;
|
|
||||||
|
|
||||||
|
|
||||||
private final ViewLoader viewLoader;
|
|
||||||
private final Navigation navigation;
|
|
||||||
|
|
||||||
private final ToggleGroup navButtons = new ToggleGroup();
|
|
||||||
private ChangeListener<String> walletServiceErrorMsgListener;
|
|
||||||
private ChangeListener<String> btcSyncIconIdListener;
|
|
||||||
private ChangeListener<String> splashP2PNetworkErrorMsgListener;
|
|
||||||
private ChangeListener<String> splashP2PNetworkIconIdListener;
|
|
||||||
private ChangeListener<Boolean> splashP2PNetworkVisibleListener;
|
|
||||||
private BusyAnimation splashP2PNetworkBusyAnimation;
|
|
||||||
private Label splashP2PNetworkLabel;
|
|
||||||
private ProgressBar btcSyncIndicator, p2pNetworkProgressBar;
|
|
||||||
private Label btcSplashInfo;
|
|
||||||
private Popup p2PNetworkWarnMsgPopup, btcNetworkWarnMsgPopup;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public MainView(MainViewModel model,
|
public MainView(MainViewModel model,
|
||||||
CachingViewLoader viewLoader,
|
CachingViewLoader viewLoader,
|
||||||
Navigation navigation,
|
Navigation navigation,
|
||||||
Transitions transitions) {
|
Transitions transitions,
|
||||||
|
TorNetworkSettingsWindow torNetworkSettingsWindow) {
|
||||||
super(model);
|
super(model);
|
||||||
this.viewLoader = viewLoader;
|
this.viewLoader = viewLoader;
|
||||||
this.navigation = navigation;
|
this.navigation = navigation;
|
||||||
MainView.transitions = transitions;
|
MainView.transitions = transitions;
|
||||||
|
this.torNetworkSettingsWindow = torNetworkSettingsWindow;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -596,6 +602,9 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
|
|||||||
splashP2PNetworkIcon.setVisible(false);
|
splashP2PNetworkIcon.setVisible(false);
|
||||||
splashP2PNetworkIcon.setManaged(false);
|
splashP2PNetworkIcon.setManaged(false);
|
||||||
HBox.setMargin(splashP2PNetworkIcon, new Insets(0, 0, 5, 0));
|
HBox.setMargin(splashP2PNetworkIcon, new Insets(0, 0, 5, 0));
|
||||||
|
splashP2PNetworkIcon.setOnMouseClicked(e -> {
|
||||||
|
torNetworkSettingsWindow.show();
|
||||||
|
});
|
||||||
|
|
||||||
Timer showTorNetworkSettingsTimer = UserThread.runAfter(() -> {
|
Timer showTorNetworkSettingsTimer = UserThread.runAfter(() -> {
|
||||||
showTorNetworkSettingsButton.setVisible(true);
|
showTorNetworkSettingsButton.setVisible(true);
|
||||||
@ -737,6 +746,40 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
|
|||||||
p2PNetworkWarnMsgPopup.hide();
|
p2PNetworkWarnMsgPopup.hide();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
p2PNetworkIcon.setOnMouseClicked(e -> {
|
||||||
|
torNetworkSettingsWindow.show();
|
||||||
|
});
|
||||||
|
|
||||||
|
ImageView p2PNetworkStatusIcon = new ImageView();
|
||||||
|
setRightAnchor(p2PNetworkStatusIcon, 30d);
|
||||||
|
setBottomAnchor(p2PNetworkStatusIcon, 7d);
|
||||||
|
Tooltip p2pNetworkStatusToolTip = new Tooltip();
|
||||||
|
Tooltip.install(p2PNetworkStatusIcon, p2pNetworkStatusToolTip);
|
||||||
|
p2PNetworkStatusIcon.setOnMouseEntered(e -> p2pNetworkStatusToolTip.setText(model.getP2pConnectionSummary()));
|
||||||
|
Timeline flasher = new Timeline(
|
||||||
|
new KeyFrame(Duration.seconds(0.5), e -> p2PNetworkStatusIcon.setOpacity(0.2)),
|
||||||
|
new KeyFrame(Duration.seconds(1.0), e -> p2PNetworkStatusIcon.setOpacity(1))
|
||||||
|
);
|
||||||
|
flasher.setCycleCount(Animation.INDEFINITE);
|
||||||
|
model.getP2PNetworkStatusIconId().addListener((ov, oldValue, newValue) -> {
|
||||||
|
if (newValue.equalsIgnoreCase("flashing:image-yellow_circle")) {
|
||||||
|
p2PNetworkStatusIcon.setId("image-yellow_circle");
|
||||||
|
flasher.play();
|
||||||
|
} else {
|
||||||
|
p2PNetworkStatusIcon.setId(newValue);
|
||||||
|
flasher.stop();
|
||||||
|
p2PNetworkStatusIcon.setOpacity(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
p2PNetworkStatusIcon.setOnMouseClicked(e -> {
|
||||||
|
if (p2PNetworkStatusIcon.getId().equalsIgnoreCase("image-alert-round")) {
|
||||||
|
new Popup().warning(Res.get("popup.info.p2pStatusIndicator.red", model.getP2pConnectionSummary())).show();
|
||||||
|
} else if (p2PNetworkStatusIcon.getId().equalsIgnoreCase("image-yellow_circle")) {
|
||||||
|
new Popup().information(Res.get("popup.info.p2pStatusIndicator.yellow", model.getP2pConnectionSummary())).show();
|
||||||
|
} else {
|
||||||
|
new Popup().information(Res.get("popup.info.p2pStatusIndicator.green", model.getP2pConnectionSummary())).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
model.getUpdatedDataReceived().addListener((observable, oldValue, newValue) -> {
|
model.getUpdatedDataReceived().addListener((observable, oldValue, newValue) -> {
|
||||||
UserThread.execute(() -> {
|
UserThread.execute(() -> {
|
||||||
@ -752,10 +795,10 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
|
|||||||
VBox vBox = new VBox();
|
VBox vBox = new VBox();
|
||||||
vBox.setAlignment(Pos.CENTER_RIGHT);
|
vBox.setAlignment(Pos.CENTER_RIGHT);
|
||||||
vBox.getChildren().addAll(p2PNetworkLabel, p2pNetworkProgressBar);
|
vBox.getChildren().addAll(p2PNetworkLabel, p2pNetworkProgressBar);
|
||||||
setRightAnchor(vBox, 33d);
|
setRightAnchor(vBox, 53d);
|
||||||
setBottomAnchor(vBox, 5d);
|
setBottomAnchor(vBox, 5d);
|
||||||
|
|
||||||
return new AnchorPane(separator, btcInfoLabel, versionBox, vBox, p2PNetworkIcon) {{
|
return new AnchorPane(separator, btcInfoLabel, versionBox, vBox, p2PNetworkStatusIcon, p2PNetworkIcon) {{
|
||||||
setId("footer-pane");
|
setId("footer-pane");
|
||||||
setMinHeight(30);
|
setMinHeight(30);
|
||||||
setMaxHeight(30);
|
setMaxHeight(30);
|
||||||
|
@ -17,9 +17,12 @@
|
|||||||
|
|
||||||
package bisq.desktop.main;
|
package bisq.desktop.main;
|
||||||
|
|
||||||
|
import bisq.desktop.Navigation;
|
||||||
import bisq.desktop.app.HavenoApp;
|
import bisq.desktop.app.HavenoApp;
|
||||||
import bisq.desktop.common.model.ViewModel;
|
import bisq.desktop.common.model.ViewModel;
|
||||||
import bisq.desktop.components.TxIdTextField;
|
import bisq.desktop.components.TxIdTextField;
|
||||||
|
import bisq.desktop.main.account.AccountView;
|
||||||
|
import bisq.desktop.main.account.content.backup.BackupView;
|
||||||
import bisq.desktop.main.overlays.Overlay;
|
import bisq.desktop.main.overlays.Overlay;
|
||||||
import bisq.desktop.main.overlays.notifications.NotificationCenter;
|
import bisq.desktop.main.overlays.notifications.NotificationCenter;
|
||||||
import bisq.desktop.main.overlays.popups.Popup;
|
import bisq.desktop.main.overlays.popups.Popup;
|
||||||
@ -133,6 +136,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
|
|||||||
@Getter
|
@Getter
|
||||||
private final TorNetworkSettingsWindow torNetworkSettingsWindow;
|
private final TorNetworkSettingsWindow torNetworkSettingsWindow;
|
||||||
private final CorruptedStorageFileHandler corruptedStorageFileHandler;
|
private final CorruptedStorageFileHandler corruptedStorageFileHandler;
|
||||||
|
private final Navigation navigation;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private final BooleanProperty showAppScreen = new SimpleBooleanProperty();
|
private final BooleanProperty showAppScreen = new SimpleBooleanProperty();
|
||||||
@ -175,7 +179,8 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
|
|||||||
LocalBitcoinNode localBitcoinNode,
|
LocalBitcoinNode localBitcoinNode,
|
||||||
AccountAgeWitnessService accountAgeWitnessService,
|
AccountAgeWitnessService accountAgeWitnessService,
|
||||||
TorNetworkSettingsWindow torNetworkSettingsWindow,
|
TorNetworkSettingsWindow torNetworkSettingsWindow,
|
||||||
CorruptedStorageFileHandler corruptedStorageFileHandler) {
|
CorruptedStorageFileHandler corruptedStorageFileHandler,
|
||||||
|
Navigation navigation) {
|
||||||
this.bisqSetup = bisqSetup;
|
this.bisqSetup = bisqSetup;
|
||||||
this.connectionService = connectionService;
|
this.connectionService = connectionService;
|
||||||
this.user = user;
|
this.user = user;
|
||||||
@ -199,6 +204,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
|
|||||||
this.accountAgeWitnessService = accountAgeWitnessService;
|
this.accountAgeWitnessService = accountAgeWitnessService;
|
||||||
this.torNetworkSettingsWindow = torNetworkSettingsWindow;
|
this.torNetworkSettingsWindow = torNetworkSettingsWindow;
|
||||||
this.corruptedStorageFileHandler = corruptedStorageFileHandler;
|
this.corruptedStorageFileHandler = corruptedStorageFileHandler;
|
||||||
|
this.navigation = navigation;
|
||||||
|
|
||||||
TxIdTextField.setPreferences(preferences);
|
TxIdTextField.setPreferences(preferences);
|
||||||
|
|
||||||
@ -411,6 +417,12 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
|
|||||||
.show();
|
.show();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
bisqSetup.setTorAddressUpgradeHandler(() -> new Popup().information(Res.get("popup.info.torMigration.msg"))
|
||||||
|
.actionButtonTextWithGoTo("navigation.account.backup")
|
||||||
|
.onAction(() -> {
|
||||||
|
navigation.setReturnPath(navigation.getCurrentPath());
|
||||||
|
navigation.navigateTo(MainView.class, AccountView.class, BackupView.class);
|
||||||
|
}).show());
|
||||||
|
|
||||||
corruptedStorageFileHandler.getFiles().ifPresent(files -> new Popup()
|
corruptedStorageFileHandler.getFiles().ifPresent(files -> new Popup()
|
||||||
.warning(Res.get("popup.warning.incompatibleDB", files.toString(), config.appDataDir))
|
.warning(Res.get("popup.warning.incompatibleDB", files.toString(), config.appDataDir))
|
||||||
@ -704,6 +716,10 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
|
|||||||
return bisqSetup.getP2PNetworkIconId();
|
return bisqSetup.getP2PNetworkIconId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StringProperty getP2PNetworkStatusIconId() {
|
||||||
|
return bisqSetup.getP2PNetworkStatusIconId();
|
||||||
|
}
|
||||||
|
|
||||||
BooleanProperty getUpdatedDataReceived() {
|
BooleanProperty getUpdatedDataReceived() {
|
||||||
return bisqSetup.getUpdatedDataReceived();
|
return bisqSetup.getUpdatedDataReceived();
|
||||||
}
|
}
|
||||||
@ -767,4 +783,10 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
|
|||||||
overlay.show();
|
overlay.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getP2pConnectionSummary() {
|
||||||
|
return Res.get("mainView.status.connections",
|
||||||
|
p2PService.getNetworkNode().getInboundConnectionCount(),
|
||||||
|
p2PService.getNetworkNode().getOutboundConnectionCount());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -123,7 +123,7 @@ public class TorNetworkSettingsWindow extends Overlay<TorNetworkSettingsWindow>
|
|||||||
headLine = Res.get("torNetworkSettingWindow.header");
|
headLine = Res.get("torNetworkSettingWindow.header");
|
||||||
|
|
||||||
width = 1068;
|
width = 1068;
|
||||||
|
rowIndex = 0;
|
||||||
createGridPane();
|
createGridPane();
|
||||||
gridPane.getColumnConstraints().get(0).setHalignment(HPos.LEFT);
|
gridPane.getColumnConstraints().get(0).setHalignment(HPos.LEFT);
|
||||||
|
|
||||||
|
BIN
desktop/src/main/resources/images/yellow_circle.png
Normal file
BIN
desktop/src/main/resources/images/yellow_circle.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 510 B |
@ -79,6 +79,17 @@ public final class NodeAddress implements PersistablePayload, NetworkPayload, Us
|
|||||||
return hostName.replace(".onion", "");
|
return hostName.replace(".onion", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tor v3 onions are too long to display for example in a table grid, so this convenience method
|
||||||
|
// produces a display-friendly format which includes [first 7]..[last 7] characters.
|
||||||
|
// tor v2 and localhost will be displayed in full, as they are 16 chars or fewer.
|
||||||
|
public String getHostNameForDisplay() {
|
||||||
|
String work = getHostNameWithoutPostFix();
|
||||||
|
if (work.length() > 16) {
|
||||||
|
return work.substring(0, 7) + ".." + work.substring(work.length() - 7);
|
||||||
|
}
|
||||||
|
return work;
|
||||||
|
}
|
||||||
|
|
||||||
// We use just a few chars from the full address to blur the potential receiver for sent network_messages
|
// We use just a few chars from the full address to blur the potential receiver for sent network_messages
|
||||||
public byte[] getAddressPrefixHash() {
|
public byte[] getAddressPrefixHash() {
|
||||||
if (addressPrefixHash == null)
|
if (addressPrefixHash == null)
|
||||||
|
@ -39,12 +39,12 @@ import javafx.beans.property.ObjectProperty;
|
|||||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
|
||||||
import java.net.ConnectException;
|
|
||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@ -127,10 +127,10 @@ public abstract class NetworkNode implements MessageListener {
|
|||||||
Thread.currentThread().setName("NetworkNode:SendMessage-to-" + peersNodeAddress.getFullAddress());
|
Thread.currentThread().setName("NetworkNode:SendMessage-to-" + peersNodeAddress.getFullAddress());
|
||||||
|
|
||||||
if (peersNodeAddress.equals(getNodeAddress())) {
|
if (peersNodeAddress.equals(getNodeAddress())) {
|
||||||
throw new ConnectException("We do not send a message to ourselves");
|
log.warn("We are sending a message to ourselves");
|
||||||
}
|
}
|
||||||
|
|
||||||
OutboundConnection outboundConnection = null;
|
OutboundConnection outboundConnection;
|
||||||
try {
|
try {
|
||||||
// can take a while when using tor
|
// can take a while when using tor
|
||||||
long startTs = System.currentTimeMillis();
|
long startTs = System.currentTimeMillis();
|
||||||
@ -145,7 +145,7 @@ public abstract class NetworkNode implements MessageListener {
|
|||||||
if (duration > CREATE_SOCKET_TIMEOUT)
|
if (duration > CREATE_SOCKET_TIMEOUT)
|
||||||
throw new TimeoutException("A timeout occurred when creating a socket.");
|
throw new TimeoutException("A timeout occurred when creating a socket.");
|
||||||
|
|
||||||
// Tor needs sometimes quite long to create a connection. To avoid that we get too many double
|
// Tor needs sometimes quite long to create a connection. To avoid that we get too many double-
|
||||||
// sided connections we check again if we still don't have any connection for that node address.
|
// sided connections we check again if we still don't have any connection for that node address.
|
||||||
Connection existingConnection = getInboundConnection(peersNodeAddress);
|
Connection existingConnection = getInboundConnection(peersNodeAddress);
|
||||||
if (existingConnection == null)
|
if (existingConnection == null)
|
||||||
@ -212,9 +212,7 @@ public abstract class NetworkNode implements MessageListener {
|
|||||||
return outboundConnection;
|
return outboundConnection;
|
||||||
}
|
}
|
||||||
} catch (Throwable throwable) {
|
} catch (Throwable throwable) {
|
||||||
if (!(throwable instanceof ConnectException ||
|
if (!(throwable instanceof IOException || throwable instanceof TimeoutException)) {
|
||||||
throwable instanceof IOException ||
|
|
||||||
throwable instanceof TimeoutException)) {
|
|
||||||
log.warn("Executing task failed. " + throwable.getMessage());
|
log.warn("Executing task failed. " + throwable.getMessage());
|
||||||
}
|
}
|
||||||
throw throwable;
|
throw throwable;
|
||||||
@ -389,7 +387,7 @@ public abstract class NetworkNode implements MessageListener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMessage(NetworkEnvelope networkEnvelope, Connection connection) {
|
public void onMessage(NetworkEnvelope networkEnvelope, Connection connection) {
|
||||||
messageListeners.stream().forEach(e -> e.onMessage(networkEnvelope, connection));
|
messageListeners.forEach(e -> e.onMessage(networkEnvelope, connection));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -441,7 +439,7 @@ public abstract class NetworkNode implements MessageListener {
|
|||||||
if (!connection.isStopped()) {
|
if (!connection.isStopped()) {
|
||||||
inBoundConnections.add((InboundConnection) connection);
|
inBoundConnections.add((InboundConnection) connection);
|
||||||
printInboundConnections();
|
printInboundConnections();
|
||||||
connectionListeners.stream().forEach(e -> e.onConnection(connection));
|
connectionListeners.forEach(e -> e.onConnection(connection));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -451,13 +449,13 @@ public abstract class NetworkNode implements MessageListener {
|
|||||||
//noinspection SuspiciousMethodCalls
|
//noinspection SuspiciousMethodCalls
|
||||||
inBoundConnections.remove(connection);
|
inBoundConnections.remove(connection);
|
||||||
printInboundConnections();
|
printInboundConnections();
|
||||||
connectionListeners.stream().forEach(e -> e.onDisconnect(closeConnectionReason, connection));
|
connectionListeners.forEach(e -> e.onDisconnect(closeConnectionReason, connection));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(Throwable throwable) {
|
public void onError(Throwable throwable) {
|
||||||
log.error("server.ConnectionListener.onError " + throwable.getMessage());
|
log.error("server.ConnectionListener.onError " + throwable.getMessage());
|
||||||
connectionListeners.stream().forEach(e -> e.onError(throwable));
|
connectionListeners.forEach(e -> e.onError(throwable));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
server = new Server(serverSocket,
|
server = new Server(serverSocket,
|
||||||
@ -479,7 +477,7 @@ public abstract class NetworkNode implements MessageListener {
|
|||||||
private void printOutBoundConnections() {
|
private void printOutBoundConnections() {
|
||||||
StringBuilder sb = new StringBuilder("outBoundConnections size()=")
|
StringBuilder sb = new StringBuilder("outBoundConnections size()=")
|
||||||
.append(outBoundConnections.size()).append("\n\toutBoundConnections=");
|
.append(outBoundConnections.size()).append("\n\toutBoundConnections=");
|
||||||
outBoundConnections.stream().forEach(e -> sb.append(e).append("\n\t"));
|
outBoundConnections.forEach(e -> sb.append(e).append("\n\t"));
|
||||||
log.debug(sb.toString());
|
log.debug(sb.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -494,7 +492,7 @@ public abstract class NetworkNode implements MessageListener {
|
|||||||
private void printInboundConnections() {
|
private void printInboundConnections() {
|
||||||
StringBuilder sb = new StringBuilder("inBoundConnections size()=")
|
StringBuilder sb = new StringBuilder("inBoundConnections size()=")
|
||||||
.append(inBoundConnections.size()).append("\n\tinBoundConnections=");
|
.append(inBoundConnections.size()).append("\n\tinBoundConnections=");
|
||||||
inBoundConnections.stream().forEach(e -> sb.append(e).append("\n\t"));
|
inBoundConnections.forEach(e -> sb.append(e).append("\n\t"));
|
||||||
log.debug(sb.toString());
|
log.debug(sb.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -512,4 +510,22 @@ public abstract class NetworkNode implements MessageListener {
|
|||||||
.map(Connection::getCapabilities)
|
.map(Connection::getCapabilities)
|
||||||
.findAny();
|
.findAny();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long upTime() {
|
||||||
|
// how long Haveno has been running with at least one connection
|
||||||
|
// uptime is relative to last all connections lost event
|
||||||
|
long earliestConnection = new Date().getTime();
|
||||||
|
for (Connection connection : outBoundConnections) {
|
||||||
|
earliestConnection = Math.min(earliestConnection, connection.getStatistic().getCreationDate().getTime());
|
||||||
|
}
|
||||||
|
return new Date().getTime() - earliestConnection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getInboundConnectionCount() {
|
||||||
|
return inBoundConnections.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getOutboundConnectionCount() {
|
||||||
|
return outBoundConnections.size();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
36
p2p/src/test/java/bisq/network/utils/UtilsTest.java
Normal file
36
p2p/src/test/java/bisq/network/utils/UtilsTest.java
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of Haveno.
|
||||||
|
*
|
||||||
|
* Haveno 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.
|
||||||
|
*
|
||||||
|
* Haveno 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 Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package bisq.network.utils;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
public class UtilsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkV2Address() {
|
||||||
|
assertFalse(Utils.isV3Address("xmh57jrzrnw6insl.onion"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void checkV3Address() {
|
||||||
|
assertTrue(Utils.isV3Address("vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd.onion"));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user