Browse Source

Include NTP checking/reconfigure tools + bump version to 1.3.1

SysTray pop-up menu now includes entry for launching https://time.is
so node owners can check their system clocks against internet time.

Windows installs also have additional systray menu entry which
runs ntpcfg.bat script, included in resources.
Also available as download via node-UI servlet,
e.g. http://localhost:9880/downloads/ntpcfg.bat

ntpcfg.bat reconfigures Windows Time Service with many NTP servers,
restarts the service, and also makes sure it auto-starts on boot.

Added DEBUG-level logging when rejecting nodes due to excessive
time difference (during PROOF handshake stage).

Bumped default settings values for minOutboundPeers from 10 to 20.
Bumped default settings values for maxPeers from 30 to 50.
split-DB
catbref 5 years ago
parent
commit
7042dd819f
  1. 67
      src/main/java/org/qora/gui/SysTray.java
  2. 4
      src/main/java/org/qora/network/Handshake.java
  3. 4
      src/main/java/org/qora/settings/Settings.java
  4. 46
      src/main/java/org/qora/ui/DownloadResourceService.java
  5. 12
      src/main/java/org/qora/ui/UiService.java
  6. 10
      src/main/resources/i18n/SysTray_en.properties
  7. 10
      src/main/resources/i18n/SysTray_zh.properties
  8. 33
      src/main/resources/node-ui-downloads/ntpcfg.bat

67
src/main/java/org/qora/gui/SysTray.java

@ -9,7 +9,15 @@ import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowEvent;
import java.awt.event.WindowFocusListener;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;
import java.util.List;
import javax.swing.JDialog;
import javax.swing.JMenuItem;
@ -23,11 +31,13 @@ import org.apache.logging.log4j.Logger;
import org.qora.controller.Controller;
import org.qora.globalization.Translator;
import org.qora.settings.Settings;
import org.qora.ui.UiService;
import org.qora.utils.URLViewer;
public class SysTray {
protected static final Logger LOGGER = LogManager.getLogger(SplashFrame.class);
private static final String NTP_SCRIPT = "ntpcfg.bat";
private static SysTray instance;
private TrayIcon trayIcon = null;
@ -145,6 +155,35 @@ public class SysTray {
});
menu.add(openUi);
JMenuItem openTimeCheck = new JMenuItem(Translator.INSTANCE.translate("SysTray", "CHECK_TIME_ACCURACY"));
openTimeCheck.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
destroyHiddenDialog();
try {
URLViewer.openWebpage(new URL("https://time.is"));
} catch (Exception e1) {
LOGGER.error("Unable to open time-check website in browser");
}
}
});
menu.add(openTimeCheck);
// Only for Windows users
if (System.getProperty("os.name").toLowerCase().contains("win")) {
JMenuItem syncTime = new JMenuItem(Translator.INSTANCE.translate("SysTray", "SYNCHRONIZE_CLOCK"));
syncTime.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
destroyHiddenDialog();
new SynchronizeWorker().execute();
}
});
menu.add(syncTime);
}
JMenuItem exit = new JMenuItem(Translator.INSTANCE.translate("SysTray", "EXIT"));
exit.addActionListener(new ActionListener() {
@Override
@ -159,6 +198,34 @@ public class SysTray {
return menu;
}
class SynchronizeWorker extends SwingWorker<Void, Void> {
@Override
protected Void doInBackground() {
// Extract reconfiguration script from resources
String resourceName = "/" + UiService.DOWNLOADS_RESOURCE_PATH + "/" + NTP_SCRIPT;
Path scriptPath = Paths.get(NTP_SCRIPT);
try (InputStream in = SysTray.class.getResourceAsStream(resourceName)) {
Files.copy(in, scriptPath, StandardCopyOption.REPLACE_EXISTING);
} catch (IllegalArgumentException | IOException e) {
LOGGER.warn(String.format("Couldn't locate NTP configuration resource: %s", resourceName));
return null;
}
// Now execute extracted script
List<String> scriptCmd = Arrays.asList(NTP_SCRIPT);
LOGGER.info(String.format("Running NTP configuration script: %s", String.join(" ", scriptCmd)));
try {
new ProcessBuilder(scriptCmd).start();
} catch (IOException e) {
LOGGER.warn(String.format("Failed to execute NTP configuration script: %s", e.getMessage()));
return null;
}
return null;
}
}
class ClosingWorker extends SwingWorker<Void, Void> {
@Override
protected Void doInBackground() {

4
src/main/java/org/qora/network/Handshake.java

@ -101,8 +101,10 @@ public enum Handshake {
ProofMessage proofMessage = (ProofMessage) message;
// Check peer's timestamp is within acceptable bounds
if (Math.abs(proofMessage.getTimestamp() - peer.getConnectionTimestamp()) > MAX_TIMESTAMP_DELTA)
if (Math.abs(proofMessage.getTimestamp() - peer.getConnectionTimestamp()) > MAX_TIMESTAMP_DELTA) {
LOGGER.debug(String.format("Rejecting PROOF from %s as timestamp delta %d greater than max %d", peer, Math.abs(proofMessage.getTimestamp() - peer.getConnectionTimestamp()), MAX_TIMESTAMP_DELTA));
return null;
}
// If we connected outbound to peer, then this is a faked confirmation response, so we're good
if (peer.isOutbound())

4
src/main/java/org/qora/settings/Settings.java

@ -75,9 +75,9 @@ public class Settings {
/** Minimum number of peers to allow block generation / synchronization. */
private int minBlockchainPeers = 3;
/** Target number of outbound connections to peers we should make. */
private int minOutboundPeers = 10;
private int minOutboundPeers = 20;
/** Maximum number of peer connections we allow. */
private int maxPeers = 30;
private int maxPeers = 50;
// Which blockchains this node is running
private String blockchainConfig = null; // use default from resources

46
src/main/java/org/qora/ui/DownloadResourceService.java

@ -0,0 +1,46 @@
package org.qora.ui;
import java.io.IOException;
import java.util.Enumeration;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpContent;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.server.ResourceService;
import org.eclipse.jetty.util.URIUtil;
/**
* Replace ResourceService that delivers content as "attachments", typically forcing download instead of rendering.
* <p>
* Sets <tt>Content-Type</tt> header to <tt>application/octet-stream</tt><br>
* Sets <tt>Content-Disposition</tt> header to <tt>attachment; filename="<i>basename</i>"</tt><br>
* where <i>basename</i> is that last component of requested URI path.
* <p>
* Example usage:<br>
* <br>
* <tt>... = new ServletHolder("servlet-name", new DefaultServlet(new DownloadResourceService()));</tt>
*/
public class DownloadResourceService extends ResourceService {
@Override
protected boolean sendData(HttpServletRequest request, HttpServletResponse response, boolean include, final HttpContent content, Enumeration<String> reqRanges) throws IOException {
final boolean _pathInfoOnly = super.isPathInfoOnly();
String servletPath = _pathInfoOnly ? "/" : request.getServletPath();
String pathInfo = request.getPathInfo();
String pathInContext = URIUtil.addPaths(servletPath,pathInfo);
// Find basename of requested content
final int slashIndex = pathInContext.lastIndexOf(URIUtil.SLASH);
if (slashIndex != -1)
pathInContext = pathInContext.substring(slashIndex + 1);
// Add appropriate headers
response.setHeader(HttpHeader.CONTENT_TYPE.asString(), "application/octet-stream");
response.setHeader("Content-Disposition", "attachment; filename=\"" + pathInContext + "\"");
return super.sendData(request, response, include, content, reqRanges);
}
}

12
src/main/java/org/qora/ui/UiService.java

@ -13,6 +13,8 @@ import org.qora.settings.Settings;
public class UiService {
public static final String DOWNLOADS_RESOURCE_PATH = "node-ui-downloads";
private final Server server;
public UiService() {
@ -42,9 +44,17 @@ public class UiService {
corsFilterHolder.setInitParameter(CrossOriginFilter.CHAIN_PREFLIGHT_PARAM, "false");
context.addFilter(corsFilterHolder, "/*", null);
ClassLoader loader = this.getClass().getClassLoader();
// Node management UI download servlet
ServletHolder uiDownloadServlet = new ServletHolder("node-ui-download", new DefaultServlet(new DownloadResourceService()));
uiDownloadServlet.setInitParameter("resourceBase", loader.getResource(DOWNLOADS_RESOURCE_PATH + "/").toString());
uiDownloadServlet.setInitParameter("dirAllowed", "true");
uiDownloadServlet.setInitParameter("pathInfoOnly", "true");
context.addServlet(uiDownloadServlet, "/downloads/*");
// Node management UI static content servlet
ServletHolder uiServlet = new ServletHolder("node-management-ui", DefaultServlet.class);
ClassLoader loader = this.getClass().getClassLoader();
uiServlet.setInitParameter("resourceBase", loader.getResource("node-management-ui/").toString());
uiServlet.setInitParameter("dirAllowed", "true");
uiServlet.setInitParameter("pathInfoOnly", "true");

10
src/main/resources/i18n/SysTray_en.properties

@ -1,7 +1,15 @@
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
# SysTray pop-up menu
OPEN_NODE_UI=Open Node UI
CHECK_TIME_ACCURACY = Check time accuracy
EXIT = Exit
# Nagging about lack of NTP time sync
NTP_NAG_CAPTION = No connections?
NTP_NAG_TEXT = Please enable Windows automatic time synchronization
OPEN_NODE_UI = Open Node UI
SYNCHRONIZE_CLOCK = Synchronize clock

10
src/main/resources/i18n/SysTray_zh.properties

@ -1,7 +1,15 @@
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
# SysTray pop-up menu
OPEN_NODE_UI=\u5F00\u542F\u754C\u9762
CHECK_TIME_ACCURACY = \u68C0\u67E5\u65F6\u95F4\u51C6\u786E\u6027
EXIT = \u9000\u51FA\u8F6F\u4EF6
# Nagging about lack of NTP time sync
NTP_NAG_CAPTION = \u6CA1\u6709\u8FDE\u63A5\u4E0A\u8282\u70B9\uFF1F
NTP_NAG_TEXT = \u8BF7\u542F\u7528Windows\u81EA\u52A8\u65F6\u95F4\u540C\u6B65\u3002
OPEN_NODE_UI = \u5F00\u542F\u754C\u9762
SYNCHRONIZE_CLOCK = \u540C\u6B65\u65F6\u949F

33
src/main/resources/node-ui-downloads/ntpcfg.bat

@ -0,0 +1,33 @@
@echo off
:: BatchGotAdmin
:-------------------------------------
REM --> Check for permissions
>nul 2>&1 "%SYSTEMROOT%\system32\cacls.exe" "%SYSTEMROOT%\system32\config\system"
REM --> If error flag set, we do not have admin.
if '%errorlevel%' NEQ '0' (
echo Requesting administrative privileges...
goto UACPrompt
) else ( goto gotAdmin )
:UACPrompt
echo Set UAC = CreateObject^("Shell.Application"^) > "%temp%\getadmin.vbs"
echo UAC.ShellExecute "%~s0", "", "", "runas", 1 >> "%temp%\getadmin.vbs"
"%temp%\getadmin.vbs"
exit /B
:gotAdmin
if exist "%temp%\getadmin.vbs" ( del "%temp%\getadmin.vbs" )
pushd "%CD%"
CD /D "%~dp0"
:--------------------------------------
net stop "Windows Time"
w32tm /config "/manualpeerlist:pool.ntp.org 0.pool.ntp.org 1.pool.ntp.org 2.pool.ntp.org 3.pool.ntp.org cn.pool.ntp.org 0.cn.pool.ntp.org 1.cn.pool.ntp.org 2.cn.pool.ntp.org 3.cn.pool.ntp.org"
net start "Windows Time"
sc config w32time start= auto
Loading…
Cancel
Save