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.
This commit is contained in:
catbref
2019-07-19 15:05:58 +01:00
parent 9ee12f3e45
commit 7042dd819f
8 changed files with 186 additions and 12 deletions

View File

@@ -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() {

View File

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

View File

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

View File

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

View File

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