mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-08-14 09:25:37 -04:00
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
13 changed files with 354 additions and 64 deletions
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue