Skip to content

Commit

Permalink
Merge pull request #55 from Qortal/master
Browse files Browse the repository at this point in the history
Merge branch master into qdn-search-api
  • Loading branch information
QuickMythril authored Jan 17, 2024
2 parents 5a16293 + 4251526 commit d203ce5
Show file tree
Hide file tree
Showing 75 changed files with 1,693 additions and 519 deletions.
2 changes: 1 addition & 1 deletion AutoUpdates.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* Build auto-update download: `tools/build-auto-update.sh` - uploads auto-update file into new git branch
* Restart local node
* Publish auto-update transaction using *private key* for **non-admin** member of "dev" group:
`tools/publish-auto-update.sh non-admin-dev-member-private-key-in-base58`
`tools/publish-auto-update.pl non-admin-dev-member-private-key-in-base58`
* Wait for auto-update `ARBITRARY` transaction to be confirmed into a block
* Have "dev" group admins 'approve' auto-update using `tools/approve-auto-update.sh`
This tool will prompt for *private key* of **admin** of "dev" group
Expand Down
16 changes: 8 additions & 8 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.qortal</groupId>
<artifactId>qortal</artifactId>
<version>4.4.0</version>
<version>4.4.1</version>
<packaging>jar</packaging>
<properties>
<skipTests>true</skipTests>
Expand All @@ -21,12 +21,12 @@
<dagger.version>1.2.2</dagger.version>
<extendedset.version>0.12.3</extendedset.version>
<git-commit-id-plugin.version>4.9.10</git-commit-id-plugin.version>
<grpc.version>1.60.0</grpc.version>
<guava.version>32.1.3-jre</guava.version>
<grpc.version>1.60.1</grpc.version>
<guava.version>33.0.0-jre</guava.version>
<hamcrest-library.version>2.2</hamcrest-library.version>
<homoglyph.version>1.2.1</homoglyph.version>
<hsqldb.version>2.5.1</hsqldb.version>
<icu4j.version>74.1</icu4j.version>
<icu4j.version>74.2</icu4j.version>
<java-diff-utils.version>4.12</java-diff-utils.version>
<javax.servlet-api.version>4.0.1</javax.servlet-api.version>
<jaxb-runtime.version>2.3.9</jaxb-runtime.version>
Expand All @@ -37,16 +37,16 @@
<jsoup.version>1.17.1</jsoup.version>
<junit-jupiter-engine.version>5.10.0</junit-jupiter-engine.version>
<lifecycle-mapping.version>1.0.0</lifecycle-mapping.version>
<log4j.version>2.21.1</log4j.version>
<log4j.version>2.22.0</log4j.version>
<mail.version>1.5.0-b01</mail.version>
<maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
<maven-compiler-plugin.version>3.12.1</maven-compiler-plugin.version>
<maven-jar-plugin.version>3.3.0</maven-jar-plugin.version>
<maven-resources-plugin.version>3.3.1</maven-resources-plugin.version>
<maven-shade-plugin.version>3.5.1</maven-shade-plugin.version>
<maven-surefire-plugin.version>3.2.2</maven-surefire-plugin.version>
<maven-surefire-plugin.version>3.2.3</maven-surefire-plugin.version>
<package-info-maven-plugin.version>1.1.0</package-info-maven-plugin.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<protobuf.version>3.25.0</protobuf.version>
<protobuf.version>3.25.1</protobuf.version>
<replacer.version>1.5.3</replacer.version>
<reproducible-build-maven-plugin.version>0.16</reproducible-build-maven-plugin.version>
<simplemagic.version>1.17</simplemagic.version>
Expand Down
227 changes: 227 additions & 0 deletions src/main/java/org/qortal/ApplyBootstrap.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
package org.qortal;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
import org.qortal.api.ApiKey;
import org.qortal.api.ApiRequest;
import org.qortal.controller.BootstrapNode;
import org.qortal.settings.Settings;

import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.security.Security;
import java.util.*;
import java.util.stream.Collectors;

import static org.qortal.controller.BootstrapNode.AGENTLIB_JVM_HOLDER_ARG;

public class ApplyBootstrap {

static {
// This static block will be called before others if using ApplyBootstrap.main()

// Log into different files for bootstrap - this has to be before LogManger.getLogger() calls
System.setProperty("log4j2.filenameTemplate", "log-apply-bootstrap.txt");

// This must go before any calls to LogManager/Logger
System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
}

private static final Logger LOGGER = LogManager.getLogger(ApplyBootstrap.class);
private static final String JAR_FILENAME = BootstrapNode.JAR_FILENAME;
private static final String WINDOWS_EXE_LAUNCHER = "qortal.exe";
private static final String JAVA_TOOL_OPTIONS_NAME = "JAVA_TOOL_OPTIONS";
private static final String JAVA_TOOL_OPTIONS_VALUE = "";

private static final long CHECK_INTERVAL = 15 * 1000L; // ms
private static final int MAX_ATTEMPTS = 20;

public static void main(String[] args) {
Security.insertProviderAt(new BouncyCastleProvider(), 0);
Security.insertProviderAt(new BouncyCastleJsseProvider(), 1);

// Load/check settings, which potentially sets up blockchain config, etc.
if (args.length > 0)
Settings.fileInstance(args[0]);
else
Settings.getInstance();

LOGGER.info("Applying bootstrap...");

// Shutdown node using API
if (!shutdownNode())
return;

// Delete db
deleteDB();

// Restart node
restartNode(args);

LOGGER.info("Bootstrapping...");
}

private static boolean shutdownNode() {
String baseUri = "http://localhost:" + Settings.getInstance().getApiPort() + "/";
LOGGER.info(() -> String.format("Shutting down node using API via %s", baseUri));

// The /admin/stop endpoint requires an API key, which may or may not be already generated
boolean apiKeyNewlyGenerated = false;
ApiKey apiKey = null;
try {
apiKey = new ApiKey();
if (!apiKey.generated()) {
apiKey.generate();
apiKeyNewlyGenerated = true;
LOGGER.info("Generated API key");
}
} catch (IOException e) {
LOGGER.info("Error loading API key: {}", e.getMessage());
}

// Create GET params
Map<String, String> params = new HashMap<>();
if (apiKey != null) {
params.put("apiKey", apiKey.toString());
}

// Attempt to stop the node
int attempt;
for (attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) {
final int attemptForLogging = attempt;
LOGGER.info(() -> String.format("Attempt #%d out of %d to shutdown node", attemptForLogging + 1, MAX_ATTEMPTS));
String response = ApiRequest.perform(baseUri + "admin/stop", params);
if (response == null) {
// No response - consider node shut down
if (apiKeyNewlyGenerated) {
// API key was newly generated for bootstrapping node, so we need to remove it
ApplyBootstrap.removeGeneratedApiKey();
}
return true;
}

LOGGER.info(() -> String.format("Response from API: %s", response));

try {
Thread.sleep(CHECK_INTERVAL);
} catch (InterruptedException e) {
// We still need to check...
break;
}
}

if (apiKeyNewlyGenerated) {
// API key was newly generated for bootstrapping node, so we need to remove it
ApplyBootstrap.removeGeneratedApiKey();
}

if (attempt == MAX_ATTEMPTS) {
LOGGER.error("Failed to shutdown node - giving up");
return false;
}

return true;
}

private static void removeGeneratedApiKey() {
try {
LOGGER.info("Removing newly generated API key...");

// Delete the API key since it was only generated for bootstrapping node
ApiKey apiKey = new ApiKey();
apiKey.delete();

} catch (IOException e) {
LOGGER.info("Error loading or deleting API key: {}", e.getMessage());
}
}

private static void deleteDB() {
// Get the repository path from settings
String repositoryPath = Settings.getInstance().getRepositoryPath();
LOGGER.debug(String.format("Repository path: %s", repositoryPath));

try {
Path directory = Paths.get(repositoryPath);
Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}

@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
} catch (IOException e) {
LOGGER.error("Error deleting DB: {}", e.getMessage());
}
}

private static void restartNode(String[] args) {
String javaHome = System.getProperty("java.home");
LOGGER.debug(() -> String.format("Java home: %s", javaHome));

Path javaBinary = Paths.get(javaHome, "bin", "java");
LOGGER.debug(() -> String.format("Java binary: %s", javaBinary));

Path exeLauncher = Paths.get(WINDOWS_EXE_LAUNCHER);
LOGGER.debug(() -> String.format("Windows EXE launcher: %s", exeLauncher));

List<String> javaCmd;
if (Files.exists(exeLauncher)) {
javaCmd = Arrays.asList(exeLauncher.toString());
} else {
javaCmd = new ArrayList<>();
// Java runtime binary itself
javaCmd.add(javaBinary.toString());

// JVM arguments
javaCmd.addAll(ManagementFactory.getRuntimeMXBean().getInputArguments());

// Reapply any retained, but disabled, -agentlib JVM arg
javaCmd = javaCmd.stream()
.map(arg -> arg.replace(AGENTLIB_JVM_HOLDER_ARG, "-agentlib"))
.collect(Collectors.toList());

// Call mainClass in JAR
javaCmd.addAll(Arrays.asList("-jar", JAR_FILENAME));

// Add saved command-line args
javaCmd.addAll(Arrays.asList(args));
}

try {
LOGGER.info(String.format("Restarting node with: %s", String.join(" ", javaCmd)));

ProcessBuilder processBuilder = new ProcessBuilder(javaCmd);

if (Files.exists(exeLauncher)) {
LOGGER.debug(() -> String.format("Setting env %s to %s", JAVA_TOOL_OPTIONS_NAME, JAVA_TOOL_OPTIONS_VALUE));
processBuilder.environment().put(JAVA_TOOL_OPTIONS_NAME, JAVA_TOOL_OPTIONS_VALUE);
}

// New process will inherit our stdout and stderr
processBuilder.redirectOutput(ProcessBuilder.Redirect.INHERIT);
processBuilder.redirectError(ProcessBuilder.Redirect.INHERIT);

Process process = processBuilder.start();

// Nothing to pipe to new process, so close output stream (process's stdin)
process.getOutputStream().close();
} catch (Exception e) {
LOGGER.error(String.format("Failed to restart node (BAD): %s", e.getMessage()));
}
}
}
Loading

0 comments on commit d203ce5

Please sign in to comment.