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.SendMailboxMessageListener;
|
||||
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.config.Config;
|
||||
@ -36,6 +41,10 @@ import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
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.ReadOnlyObjectProperty;
|
||||
@ -45,16 +54,20 @@ import java.security.SignatureException;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
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 final P2PService p2PService;
|
||||
@ -68,6 +81,8 @@ public class PrivateNotificationManager {
|
||||
private ECKey privateNotificationSigningKey;
|
||||
@Nullable
|
||||
private PrivateNotificationMessage privateNotificationMessage;
|
||||
private final NetworkNode networkNode;
|
||||
private Consumer<String> pingResponseHandler = null;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -76,11 +91,13 @@ public class PrivateNotificationManager {
|
||||
|
||||
@Inject
|
||||
public PrivateNotificationManager(P2PService p2PService,
|
||||
NetworkNode networkNode,
|
||||
MailboxMessageService mailboxMessageService,
|
||||
KeyRing keyRing,
|
||||
@Named(Config.IGNORE_DEV_MSG) boolean ignoreDevMsg,
|
||||
@Named(Config.USE_DEV_PRIVILEGE_KEYS) boolean useDevPrivilegeKeys) {
|
||||
this.p2PService = p2PService;
|
||||
this.networkNode = networkNode;
|
||||
this.mailboxMessageService = mailboxMessageService;
|
||||
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.setDownGradePreventionHandler(lastVersion -> log.info("Downgrade from version {} to version {} is not supported",
|
||||
lastVersion, Version.VERSION));
|
||||
|
||||
bisqSetup.setTorAddressUpgradeHandler(() -> log.info("setTorAddressUpgradeHandler"));
|
||||
corruptedStorageFileHandler.getFiles().ifPresent(files -> log.warn("getCorruptedDatabaseFiles. files={}", files));
|
||||
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.alert.Alert;
|
||||
import bisq.core.alert.AlertManager;
|
||||
import bisq.core.alert.PrivateNotificationManager;
|
||||
import bisq.core.alert.PrivateNotificationPayload;
|
||||
import bisq.core.api.CoreMoneroNodeService;
|
||||
import bisq.core.btc.model.AddressEntry;
|
||||
@ -37,6 +38,10 @@ import bisq.core.payment.AmazonGiftCardAccount;
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
import bisq.core.payment.RevolutAccount;
|
||||
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.TradeTxException;
|
||||
import bisq.core.user.Preferences;
|
||||
@ -45,8 +50,10 @@ import bisq.core.util.FormattingUtils;
|
||||
import bisq.core.util.coin.CoinFormatter;
|
||||
|
||||
import bisq.network.Socks5ProxyProvider;
|
||||
import bisq.network.p2p.NodeAddress;
|
||||
import bisq.network.p2p.P2PService;
|
||||
import bisq.network.p2p.storage.payload.PersistableNetworkPayload;
|
||||
import bisq.network.utils.Utils;
|
||||
|
||||
import bisq.common.Timer;
|
||||
import bisq.common.UserThread;
|
||||
@ -86,6 +93,8 @@ import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Random;
|
||||
import java.util.Scanner;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -106,19 +115,6 @@ public class HavenoSetup {
|
||||
private static final String VERSION_FILE_NAME = "version";
|
||||
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 final DomainInitialisation domainInitialisation;
|
||||
@ -129,6 +125,7 @@ public class HavenoSetup {
|
||||
private final BtcWalletService btcWalletService;
|
||||
private final XmrWalletService xmrWalletService;
|
||||
private final P2PService p2PService;
|
||||
private final PrivateNotificationManager privateNotificationManager;
|
||||
private final SignedWitnessStorageService signedWitnessStorageService;
|
||||
private final TradeManager tradeManager;
|
||||
private final OpenOfferManager openOfferManager;
|
||||
@ -141,7 +138,9 @@ public class HavenoSetup {
|
||||
private final CoinFormatter formatter;
|
||||
private final LocalBitcoinNode localBitcoinNode;
|
||||
private final AppStartupState appStartupState;
|
||||
|
||||
private final MediationManager mediationManager;
|
||||
private final RefundManager refundManager;
|
||||
private final ArbitrationManager arbitrationManager;
|
||||
@Setter
|
||||
@Nullable
|
||||
private Consumer<Runnable> displayTacHandler;
|
||||
@ -188,8 +187,10 @@ public class HavenoSetup {
|
||||
private Runnable qubesOSInfoHandler;
|
||||
@Setter
|
||||
@Nullable
|
||||
private Runnable torAddressUpgradeHandler;
|
||||
@Setter
|
||||
@Nullable
|
||||
private Consumer<String> downGradePreventionHandler;
|
||||
|
||||
@Getter
|
||||
final BooleanProperty newVersionAvailableProperty = new SimpleBooleanProperty(false);
|
||||
private BooleanProperty p2pNetworkReady;
|
||||
@ -199,6 +200,19 @@ public class HavenoSetup {
|
||||
private MonadicBinding<Boolean> p2pNetworkAndWalletInitialized;
|
||||
private final List<HavenoSetupListener> havenoSetupListeners = new ArrayList<>();
|
||||
|
||||
public interface HavenoSetupListener {
|
||||
default void onInitP2pNetwork() {
|
||||
}
|
||||
|
||||
default void onInitWallet() {
|
||||
}
|
||||
|
||||
default void onRequestWalletPassword() {
|
||||
}
|
||||
|
||||
void onSetupComplete();
|
||||
}
|
||||
|
||||
@Inject
|
||||
public HavenoSetup(DomainInitialisation domainInitialisation,
|
||||
P2PNetworkSetup p2PNetworkSetup,
|
||||
@ -208,6 +222,7 @@ public class HavenoSetup {
|
||||
XmrWalletService xmrWalletService,
|
||||
BtcWalletService btcWalletService,
|
||||
P2PService p2PService,
|
||||
PrivateNotificationManager privateNotificationManager,
|
||||
SignedWitnessStorageService signedWitnessStorageService,
|
||||
TradeManager tradeManager,
|
||||
OpenOfferManager openOfferManager,
|
||||
@ -220,7 +235,10 @@ public class HavenoSetup {
|
||||
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter,
|
||||
LocalBitcoinNode localBitcoinNode,
|
||||
AppStartupState appStartupState,
|
||||
Socks5ProxyProvider socks5ProxyProvider) {
|
||||
Socks5ProxyProvider socks5ProxyProvider,
|
||||
MediationManager mediationManager,
|
||||
RefundManager refundManager,
|
||||
ArbitrationManager arbitrationManager) {
|
||||
this.domainInitialisation = domainInitialisation;
|
||||
this.p2PNetworkSetup = p2PNetworkSetup;
|
||||
this.walletAppSetup = walletAppSetup;
|
||||
@ -229,6 +247,7 @@ public class HavenoSetup {
|
||||
this.xmrWalletService = xmrWalletService;
|
||||
this.btcWalletService = btcWalletService;
|
||||
this.p2PService = p2PService;
|
||||
this.privateNotificationManager = privateNotificationManager;
|
||||
this.signedWitnessStorageService = signedWitnessStorageService;
|
||||
this.tradeManager = tradeManager;
|
||||
this.openOfferManager = openOfferManager;
|
||||
@ -241,6 +260,9 @@ public class HavenoSetup {
|
||||
this.formatter = formatter;
|
||||
this.localBitcoinNode = localBitcoinNode;
|
||||
this.appStartupState = appStartupState;
|
||||
this.mediationManager = mediationManager;
|
||||
this.refundManager = refundManager;
|
||||
this.arbitrationManager = arbitrationManager;
|
||||
|
||||
MemPoolSpaceTxBroadcaster.init(socks5ProxyProvider, preferences, localBitcoinNode);
|
||||
}
|
||||
@ -313,6 +335,8 @@ public class HavenoSetup {
|
||||
maybeShowSecurityRecommendation();
|
||||
maybeShowLocalhostRunningInfo();
|
||||
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() {
|
||||
if (user.getPaymentAccountsAsObservable() == null) return;
|
||||
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
|
||||
@ -776,6 +855,10 @@ public class HavenoSetup {
|
||||
return p2PNetworkSetup.getP2PNetworkIconId();
|
||||
}
|
||||
|
||||
public StringProperty getP2PNetworkStatusIconId() {
|
||||
return p2PNetworkSetup.getP2PNetworkStatusIconId();
|
||||
}
|
||||
|
||||
public BooleanProperty getUpdatedDataReceived() {
|
||||
return p2PNetworkSetup.getUpdatedDataReceived();
|
||||
}
|
||||
|
@ -63,6 +63,8 @@ public class P2PNetworkSetup {
|
||||
@Getter
|
||||
final StringProperty p2PNetworkIconId = new SimpleStringProperty();
|
||||
@Getter
|
||||
final StringProperty p2PNetworkStatusIconId = new SimpleStringProperty();
|
||||
@Getter
|
||||
final BooleanProperty splashP2PNetworkAnimationVisible = new SimpleBooleanProperty(true);
|
||||
@Getter
|
||||
final StringProperty p2pNetworkLabelId = new SimpleStringProperty("footer-pane");
|
||||
@ -118,10 +120,12 @@ public class P2PNetworkSetup {
|
||||
p2PService.getNetworkNode().addConnectionListener(new ConnectionListener() {
|
||||
@Override
|
||||
public void onConnection(Connection connection) {
|
||||
updateNetworkStatusIndicator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisconnect(CloseConnectionReason closeConnectionReason, Connection connection) {
|
||||
updateNetworkStatusIndicator();
|
||||
// We only check at seed nodes as they are running the latest version
|
||||
// Other disconnects might be caused by peers running an older version
|
||||
if (connection.getConnectionState().isSeedNode() &&
|
||||
@ -225,4 +229,14 @@ public class P2PNetworkSetup {
|
||||
public void setSplashP2PNetworkAnimationVisible(boolean 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.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.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.
|
||||
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].
|
||||
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. \
|
||||
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.
|
||||
@ -2196,13 +2203,17 @@ popup.accountSigning.unsignedPubKeys.signed=Pubkeys were signed
|
||||
popup.accountSigning.unsignedPubKeys.result.signed=Signed pubkeys
|
||||
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
|
||||
####################################################################
|
||||
|
||||
notification.trade.headline=Notification 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.unlocked=Your trade has been confirmed.\nYou can start the payment now.
|
||||
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 \
|
||||
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.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.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\
|
||||
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.
|
||||
payment.altcoin=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\
|
||||
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 \
|
||||
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\
|
||||
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 \
|
||||
@ -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.
|
||||
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\
|
||||
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.
|
||||
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.
|
||||
|
||||
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\
|
||||
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\
|
||||
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 \
|
||||
@ -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.sepaNotSupported=SEPA is not supported in this country
|
||||
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.invalidAnswer=Must be one word and contain only letters, numbers, and/or the symbol -
|
||||
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.numberFormatException=Number format exception {0}
|
||||
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.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
|
||||
|
@ -21,6 +21,10 @@
|
||||
-fx-image: url("../../images/green_circle.png");
|
||||
}
|
||||
|
||||
#image-yellow_circle {
|
||||
-fx-image: url("../../images/yellow_circle.png");
|
||||
}
|
||||
|
||||
#image-blue_circle {
|
||||
-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.SellOfferView;
|
||||
import bisq.desktop.main.overlays.popups.Popup;
|
||||
import bisq.desktop.main.overlays.windows.TorNetworkSettingsWindow;
|
||||
import bisq.desktop.main.portfolio.PortfolioView;
|
||||
import bisq.desktop.main.settings.SettingsView;
|
||||
import bisq.desktop.main.shared.PriceFeedComboBoxItem;
|
||||
@ -58,6 +59,10 @@ import com.jfoenix.controls.JFXBadge;
|
||||
import com.jfoenix.controls.JFXComboBox;
|
||||
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.ComboBox;
|
||||
import javafx.scene.control.Label;
|
||||
@ -91,6 +96,8 @@ import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
|
||||
import javafx.util.Duration;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
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;
|
||||
@Setter
|
||||
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() {
|
||||
return MainView.rootContainer;
|
||||
@ -135,34 +158,17 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
|
||||
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
|
||||
public MainView(MainViewModel model,
|
||||
CachingViewLoader viewLoader,
|
||||
Navigation navigation,
|
||||
Transitions transitions) {
|
||||
Transitions transitions,
|
||||
TorNetworkSettingsWindow torNetworkSettingsWindow) {
|
||||
super(model);
|
||||
this.viewLoader = viewLoader;
|
||||
this.navigation = navigation;
|
||||
MainView.transitions = transitions;
|
||||
this.torNetworkSettingsWindow = torNetworkSettingsWindow;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -596,6 +602,9 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
|
||||
splashP2PNetworkIcon.setVisible(false);
|
||||
splashP2PNetworkIcon.setManaged(false);
|
||||
HBox.setMargin(splashP2PNetworkIcon, new Insets(0, 0, 5, 0));
|
||||
splashP2PNetworkIcon.setOnMouseClicked(e -> {
|
||||
torNetworkSettingsWindow.show();
|
||||
});
|
||||
|
||||
Timer showTorNetworkSettingsTimer = UserThread.runAfter(() -> {
|
||||
showTorNetworkSettingsButton.setVisible(true);
|
||||
@ -737,6 +746,40 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
|
||||
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) -> {
|
||||
UserThread.execute(() -> {
|
||||
@ -752,10 +795,10 @@ public class MainView extends InitializableView<StackPane, MainViewModel> {
|
||||
VBox vBox = new VBox();
|
||||
vBox.setAlignment(Pos.CENTER_RIGHT);
|
||||
vBox.getChildren().addAll(p2PNetworkLabel, p2pNetworkProgressBar);
|
||||
setRightAnchor(vBox, 33d);
|
||||
setRightAnchor(vBox, 53d);
|
||||
setBottomAnchor(vBox, 5d);
|
||||
|
||||
return new AnchorPane(separator, btcInfoLabel, versionBox, vBox, p2PNetworkIcon) {{
|
||||
return new AnchorPane(separator, btcInfoLabel, versionBox, vBox, p2PNetworkStatusIcon, p2PNetworkIcon) {{
|
||||
setId("footer-pane");
|
||||
setMinHeight(30);
|
||||
setMaxHeight(30);
|
||||
|
@ -17,9 +17,12 @@
|
||||
|
||||
package bisq.desktop.main;
|
||||
|
||||
import bisq.desktop.Navigation;
|
||||
import bisq.desktop.app.HavenoApp;
|
||||
import bisq.desktop.common.model.ViewModel;
|
||||
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.notifications.NotificationCenter;
|
||||
import bisq.desktop.main.overlays.popups.Popup;
|
||||
@ -133,6 +136,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
|
||||
@Getter
|
||||
private final TorNetworkSettingsWindow torNetworkSettingsWindow;
|
||||
private final CorruptedStorageFileHandler corruptedStorageFileHandler;
|
||||
private final Navigation navigation;
|
||||
|
||||
@Getter
|
||||
private final BooleanProperty showAppScreen = new SimpleBooleanProperty();
|
||||
@ -175,7 +179,8 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
|
||||
LocalBitcoinNode localBitcoinNode,
|
||||
AccountAgeWitnessService accountAgeWitnessService,
|
||||
TorNetworkSettingsWindow torNetworkSettingsWindow,
|
||||
CorruptedStorageFileHandler corruptedStorageFileHandler) {
|
||||
CorruptedStorageFileHandler corruptedStorageFileHandler,
|
||||
Navigation navigation) {
|
||||
this.bisqSetup = bisqSetup;
|
||||
this.connectionService = connectionService;
|
||||
this.user = user;
|
||||
@ -199,6 +204,7 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
|
||||
this.accountAgeWitnessService = accountAgeWitnessService;
|
||||
this.torNetworkSettingsWindow = torNetworkSettingsWindow;
|
||||
this.corruptedStorageFileHandler = corruptedStorageFileHandler;
|
||||
this.navigation = navigation;
|
||||
|
||||
TxIdTextField.setPreferences(preferences);
|
||||
|
||||
@ -411,6 +417,12 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
|
||||
.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()
|
||||
.warning(Res.get("popup.warning.incompatibleDB", files.toString(), config.appDataDir))
|
||||
@ -704,6 +716,10 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
|
||||
return bisqSetup.getP2PNetworkIconId();
|
||||
}
|
||||
|
||||
StringProperty getP2PNetworkStatusIconId() {
|
||||
return bisqSetup.getP2PNetworkStatusIconId();
|
||||
}
|
||||
|
||||
BooleanProperty getUpdatedDataReceived() {
|
||||
return bisqSetup.getUpdatedDataReceived();
|
||||
}
|
||||
@ -767,4 +783,10 @@ public class MainViewModel implements ViewModel, HavenoSetup.HavenoSetupListener
|
||||
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");
|
||||
|
||||
width = 1068;
|
||||
|
||||
rowIndex = 0;
|
||||
createGridPane();
|
||||
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", "");
|
||||
}
|
||||
|
||||
// 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
|
||||
public byte[] getAddressPrefixHash() {
|
||||
if (addressPrefixHash == null)
|
||||
|
@ -39,12 +39,12 @@ import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
|
||||
import java.net.ConnectException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
@ -127,10 +127,10 @@ public abstract class NetworkNode implements MessageListener {
|
||||
Thread.currentThread().setName("NetworkNode:SendMessage-to-" + peersNodeAddress.getFullAddress());
|
||||
|
||||
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 {
|
||||
// can take a while when using tor
|
||||
long startTs = System.currentTimeMillis();
|
||||
@ -145,7 +145,7 @@ public abstract class NetworkNode implements MessageListener {
|
||||
if (duration > CREATE_SOCKET_TIMEOUT)
|
||||
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.
|
||||
Connection existingConnection = getInboundConnection(peersNodeAddress);
|
||||
if (existingConnection == null)
|
||||
@ -212,9 +212,7 @@ public abstract class NetworkNode implements MessageListener {
|
||||
return outboundConnection;
|
||||
}
|
||||
} catch (Throwable throwable) {
|
||||
if (!(throwable instanceof ConnectException ||
|
||||
throwable instanceof IOException ||
|
||||
throwable instanceof TimeoutException)) {
|
||||
if (!(throwable instanceof IOException || throwable instanceof TimeoutException)) {
|
||||
log.warn("Executing task failed. " + throwable.getMessage());
|
||||
}
|
||||
throw throwable;
|
||||
@ -389,7 +387,7 @@ public abstract class NetworkNode implements MessageListener {
|
||||
|
||||
@Override
|
||||
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()) {
|
||||
inBoundConnections.add((InboundConnection) connection);
|
||||
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
|
||||
inBoundConnections.remove(connection);
|
||||
printInboundConnections();
|
||||
connectionListeners.stream().forEach(e -> e.onDisconnect(closeConnectionReason, connection));
|
||||
connectionListeners.forEach(e -> e.onDisconnect(closeConnectionReason, connection));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable throwable) {
|
||||
log.error("server.ConnectionListener.onError " + throwable.getMessage());
|
||||
connectionListeners.stream().forEach(e -> e.onError(throwable));
|
||||
connectionListeners.forEach(e -> e.onError(throwable));
|
||||
}
|
||||
};
|
||||
server = new Server(serverSocket,
|
||||
@ -479,7 +477,7 @@ public abstract class NetworkNode implements MessageListener {
|
||||
private void printOutBoundConnections() {
|
||||
StringBuilder sb = new StringBuilder("outBoundConnections size()=")
|
||||
.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());
|
||||
}
|
||||
|
||||
@ -494,7 +492,7 @@ public abstract class NetworkNode implements MessageListener {
|
||||
private void printInboundConnections() {
|
||||
StringBuilder sb = new StringBuilder("inBoundConnections size()=")
|
||||
.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());
|
||||
}
|
||||
|
||||
@ -512,4 +510,22 @@ public abstract class NetworkNode implements MessageListener {
|
||||
.map(Connection::getCapabilities)
|
||||
.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