Merge pull request #6330 from jmacxx/network_status_indicator

Feature: P2P network status indicator
This commit is contained in:
Christoph Atteneder 2022-08-19 11:16:30 +02:00 committed by GitHub
commit 8929567725
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 131 additions and 37 deletions

View File

@ -94,7 +94,6 @@ public class BisqHeadlessApp implements HeadlessApp {
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(() -> {

View File

@ -198,9 +198,6 @@ public class BisqSetup {
private Runnable qubesOSInfoHandler;
@Setter
@Nullable
private Runnable firewallIssueHandler;
@Setter
@Nullable
private Runnable daoRequiresRestartHandler;
@Setter
@Nullable
@ -217,7 +214,6 @@ public class BisqSetup {
@SuppressWarnings("FieldCanBeLocal")
private MonadicBinding<Boolean> p2pNetworkAndWalletInitialized;
private final List<BisqSetupListener> bisqSetupListeners = new ArrayList<>();
private int failedSelfPings = 0;
@Inject
public BisqSetup(DomainInitialisation domainInitialisation,
@ -338,7 +334,7 @@ public class BisqSetup {
maybeShowLocalhostRunningInfo();
maybeShowAccountSigningStateInfo();
maybeShowTorAddressUpgradeInformation();
checkTorFirewall();
checkInboundConnections();
}
@ -662,30 +658,34 @@ public class BisqSetup {
}
/**
* 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() {
@ -828,6 +828,10 @@ public class BisqSetup {
return p2PNetworkSetup.getP2PNetworkIconId();
}
public StringProperty getP2PNetworkStatusIconId() {
return p2PNetworkSetup.getP2PNetworkStatusIconId();
}
public BooleanProperty getUpdatedDataReceived() {
return p2PNetworkSetup.getUpdatedDataReceived();
}

View File

@ -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");
@ -127,10 +129,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() &&
@ -243,4 +247,14 @@ public class P2PNetworkSetup {
!(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");
}
}
}

View File

@ -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
@ -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.

View File

@ -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");
}

View File

@ -35,6 +35,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;
@ -60,6 +61,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;
@ -92,6 +97,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;
@ -156,17 +163,20 @@ public class MainView extends InitializableView<StackPane, MainViewModel>
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;
}
@ -631,6 +641,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);
@ -772,6 +785,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) -> {
p2PNetworkIcon.setOpacity(1);
@ -785,10 +832,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);

View File

@ -475,15 +475,6 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener {
.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))
@ -829,6 +820,10 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener {
return bisqSetup.getP2PNetworkIconId();
}
StringProperty getP2PNetworkStatusIconId() {
return bisqSetup.getP2PNetworkStatusIconId();
}
BooleanProperty getUpdatedDataReceived() {
return bisqSetup.getUpdatedDataReceived();
}
@ -896,4 +891,10 @@ public class MainViewModel implements ViewModel, BisqSetup.BisqSetupListener {
overlay.show();
}
}
public String getP2pConnectionSummary() {
return Res.get("mainView.status.connections",
p2PService.getNetworkNode().getInboundConnectionCount(),
p2PService.getNetworkNode().getOutboundConnectionCount());
}
}

View File

@ -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);

Binary file not shown.

After

Width:  |  Height:  |  Size: 510 B

View File

@ -45,6 +45,7 @@ import java.net.Socket;
import java.io.IOException;
import java.util.Date;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
@ -513,4 +514,22 @@ public abstract class NetworkNode implements MessageListener {
.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();
}
}