Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: P2P network status indicator #6330

Merged
merged 1 commit into from Aug 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion core/src/main/java/bisq/core/app/BisqHeadlessApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ protected void setupHandlers() {
bisqSetup.setShowPopupIfInvalidBtcConfigHandler(() -> log.error("onShowPopupIfInvalidBtcConfigHandler"));
bisqSetup.setRevolutAccountsUpdateHandler(revolutAccountList -> log.info("setRevolutAccountsUpdateHandler: revolutAccountList={}", revolutAccountList));
bisqSetup.setQubesOSInfoHandler(() -> log.info("setQubesOSInfoHandler"));
bisqSetup.setFirewallIssueHandler(() -> log.info("setFirewallIssueHandler"));
bisqSetup.setDownGradePreventionHandler(lastVersion -> log.info("Downgrade from version {} to version {} is not supported",
lastVersion, Version.VERSION));
bisqSetup.setDaoRequiresRestartHandler(() -> {
Expand Down
44 changes: 24 additions & 20 deletions core/src/main/java/bisq/core/app/BisqSetup.java
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,6 @@ default void onRequestWalletPassword() {
private Runnable qubesOSInfoHandler;
@Setter
@Nullable
private Runnable firewallIssueHandler;
@Setter
@Nullable
private Runnable daoRequiresRestartHandler;
@Setter
@Nullable
Expand All @@ -217,7 +214,6 @@ default void onRequestWalletPassword() {
@SuppressWarnings("FieldCanBeLocal")
private MonadicBinding<Boolean> p2pNetworkAndWalletInitialized;
private final List<BisqSetupListener> bisqSetupListeners = new ArrayList<>();
private int failedSelfPings = 0;

@Inject
public BisqSetup(DomainInitialisation domainInitialisation,
Expand Down Expand Up @@ -338,7 +334,7 @@ private void step4() {
maybeShowLocalhostRunningInfo();
maybeShowAccountSigningStateInfo();
maybeShowTorAddressUpgradeInformation();
checkTorFirewall();
checkInboundConnections();
}


Expand Down Expand Up @@ -662,30 +658,34 @@ private void checkIfRunningOnQubesOS() {
}

/**
* Check if we have inbound connections. If not, try to ping ourselves.
* If Bisq 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.
* In rare cases a self ping can fail (thanks Tor), so we retry up to 3 times at random intervals in hope of success.
* Repeat this test hourly.
*/
private void checkTorFirewall() {
private void checkInboundConnections() {
NodeAddress onionAddress = p2PService.getNetworkNode().nodeAddressProperty().get();
if (onionAddress == null || !onionAddress.getFullAddress().contains("onion")) {
return;
}
privateNotificationManager.sendPing(onionAddress, stringResult -> {
log.info(stringResult);
if (stringResult.contains("failed")) {
// the self-ping failed: after 3 failures notify the user and stop trying
if (++failedSelfPings >= 3) {
if (firewallIssueHandler != null) {
firewallIssueHandler.run();
}
} else {
// retry self ping after a random delay
UserThread.runAfter(this::checkTorFirewall, new Random().nextInt((int) STARTUP_TIMEOUT_MINUTES), TimeUnit.MINUTES);

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() {
Expand Down Expand Up @@ -828,6 +828,10 @@ public StringProperty getP2PNetworkIconId() {
return p2PNetworkSetup.getP2PNetworkIconId();
}

public StringProperty getP2PNetworkStatusIconId() {
return p2PNetworkSetup.getP2PNetworkStatusIconId();
}

public BooleanProperty getUpdatedDataReceived() {
return p2PNetworkSetup.getUpdatedDataReceived();
}
Expand Down
14 changes: 14 additions & 0 deletions core/src/main/java/bisq/core/app/P2PNetworkSetup.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,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");
Expand Down Expand Up @@ -127,10 +129,12 @@ BooleanProperty init(Runnable initWalletServiceHandler,
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() &&
Expand Down Expand Up @@ -243,4 +247,14 @@ private void addP2PMessageFilter() {
!(payload instanceof ProofOfWorkPayload);
});
}

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");
}
}
}
14 changes: 10 additions & 4 deletions core/src/main/resources/i18n/displayStrings.properties
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ mainView.networkWarning.clockWatcher=Your computer was asleep for {0} seconds. \
Standby mode has been known to cause trades to fail. \
In order to operate correctly Bisq requires that standby mode be disabled in your computer's settings.
mainView.version.update=(Update available)

mainView.status.connections=Inbound connections: {0}\nOutbound connections: {1}

####################################################################
# MarketView
Expand Down Expand Up @@ -3126,9 +3126,15 @@ 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.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.
popup.info.p2pStatusIndicator.red={0}\n\n\
Your node has no connection to the P2P network. Bisq cannot operate in this state. \
See [HYPERLINK:https://bisq.wiki/Network_status_indicator] for more information.
popup.info.p2pStatusIndicator.yellow={0}\n\n\
Your node has no inbound Tor connections. Bisq will function ok, but if this state persists for several hours it may be an indication of connectivity problems. \
See [HYPERLINK:https://bisq.wiki/Network_status_indicator] for more information.
popup.info.p2pStatusIndicator.green={0}\n\n\
Good news, your P2P connection state looks healthy! \
[HYPERLINK:https://bisq.wiki/Network_status_indicator]
popup.warn.downGradePrevention=Downgrade from version {0} to version {1} is not supported. Please use the latest Bisq version.
popup.warn.daoRequiresRestart=There was a problem with synchronizing the DAO state. You have to restart the application to fix the issue.

Expand Down
4 changes: 4 additions & 0 deletions desktop/src/main/java/bisq/desktop/images.css
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand Down
51 changes: 49 additions & 2 deletions desktop/src/main/java/bisq/desktop/main/MainView.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
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;
Expand All @@ -60,6 +61,10 @@
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;
Expand Down Expand Up @@ -92,6 +97,8 @@
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;

import javafx.util.Duration;

import java.text.DecimalFormat;
import java.text.NumberFormat;

Expand Down Expand Up @@ -156,17 +163,20 @@ public static void removeEffect() {
private Label btcSplashInfo;
private Popup p2PNetworkWarnMsgPopup, btcNetworkWarnMsgPopup;
private final DaoStateMonitoringService daoStateMonitoringService;
private final TorNetworkSettingsWindow torNetworkSettingsWindow;

@Inject
public MainView(MainViewModel model,
CachingViewLoader viewLoader,
Navigation navigation,
Transitions transitions,
TorNetworkSettingsWindow torNetworkSettingsWindow,
DaoStateMonitoringService daoStateMonitoringService) {
super(model);
this.viewLoader = viewLoader;
this.navigation = navigation;
MainView.transitions = transitions;
this.torNetworkSettingsWindow = torNetworkSettingsWindow;
this.daoStateMonitoringService = daoStateMonitoringService;
}

Expand Down Expand Up @@ -631,6 +641,9 @@ private VBox createSplashScreen() {
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);
Expand Down Expand Up @@ -772,6 +785,40 @@ private AnchorPane createFooter() {
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) -> {
p2PNetworkIcon.setOpacity(1);
Expand All @@ -785,10 +832,10 @@ private AnchorPane createFooter() {
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);
Expand Down
19 changes: 10 additions & 9 deletions desktop/src/main/java/bisq/desktop/main/MainViewModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -479,15 +479,6 @@ private void setupHandlers() {
.show();
}
});
bisqSetup.setFirewallIssueHandler(() -> {
String key = "firewallSetupInfo";
if (preferences.showAgain(key)) {
new Popup().information(Res.get("popup.info.firewallSetupInfo"))
.closeButtonText(Res.get("shared.iUnderstand"))
.dontShowAgainId(key)
.show();
}
});

bisqSetup.setDownGradePreventionHandler(lastVersion -> {
new Popup().warning(Res.get("popup.warn.downGradePrevention", lastVersion, Version.VERSION))
Expand Down Expand Up @@ -833,6 +824,10 @@ StringProperty getP2PNetworkIconId() {
return bisqSetup.getP2PNetworkIconId();
}

StringProperty getP2PNetworkStatusIconId() {
return bisqSetup.getP2PNetworkStatusIconId();
}

BooleanProperty getUpdatedDataReceived() {
return bisqSetup.getUpdatedDataReceived();
}
Expand Down Expand Up @@ -900,4 +895,10 @@ private void maybeShowPopupsFromQueue() {
overlay.show();
}
}

public String getP2pConnectionSummary() {
return Res.get("mainView.status.connections",
p2PService.getNetworkNode().getInboundConnectionCount(),
p2PService.getNetworkNode().getOutboundConnectionCount());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ public void show() {
headLine = Res.get("torNetworkSettingWindow.header");

width = 1068;

rowIndex = 0;
createGridPane();
gridPane.getColumnConstraints().get(0).setHalignment(HPos.LEFT);

Expand Down
Binary file added desktop/src/main/resources/images/yellow_circle.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions p2p/src/main/java/bisq/network/p2p/network/NetworkNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@

import java.io.IOException;

import java.util.Date;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
Expand Down Expand Up @@ -513,4 +514,22 @@ public Optional<Capabilities> findPeersCapabilities(NodeAddress nodeAddress) {
.map(Connection::getCapabilities)
.findAny();
}

public long upTime() {
// how long Bisq 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();
}
}