From 14279e58bca4e82b9c250adb0c451bc3eedb73f1 Mon Sep 17 00:00:00 2001 From: QuickMythril Date: Sat, 2 Dec 2023 23:06:36 -0500 Subject: [PATCH 1/3] Added API call for bootstrapping node --- .../restricted/resource/AdminResource.java | 31 ++++++ .../org/qortal/controller/AutoUpdate.java | 96 +++++++++++++++++++ 2 files changed, 127 insertions(+) diff --git a/src/main/java/org/qortal/api/restricted/resource/AdminResource.java b/src/main/java/org/qortal/api/restricted/resource/AdminResource.java index f0e045ed3..35bf91089 100644 --- a/src/main/java/org/qortal/api/restricted/resource/AdminResource.java +++ b/src/main/java/org/qortal/api/restricted/resource/AdminResource.java @@ -257,6 +257,37 @@ public String restart(@HeaderParam(Security.API_KEY_HEADER) String apiKey) { return "true"; } + @GET + @Path("/bootstrap") + @Operation( + summary = "Bootstrap", + description = "Delete and download new database archive", + responses = { + @ApiResponse( + description = "\"true\"", + content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string")) + ) + } + ) + @SecurityRequirement(name = "apiKey") + public String bootstrap(@HeaderParam(Security.API_KEY_HEADER) String apiKey) { + Security.checkApiCallAllowed(request); + + new Thread(() -> { + // Short sleep to allow HTTP response body to be emitted + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // Not important + } + + AutoUpdate.attemptBootstrap(); + + }).start(); + + return "true"; + } + @GET @Path("/summary") @Operation( diff --git a/src/main/java/org/qortal/controller/AutoUpdate.java b/src/main/java/org/qortal/controller/AutoUpdate.java index bc232e1bf..0bdcfd488 100644 --- a/src/main/java/org/qortal/controller/AutoUpdate.java +++ b/src/main/java/org/qortal/controller/AutoUpdate.java @@ -23,9 +23,12 @@ import java.io.OutputStream; import java.lang.management.ManagementFactory; import java.nio.ByteBuffer; +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.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; @@ -365,4 +368,97 @@ public static boolean attemptRestart() { } } + public static boolean attemptBootstrap() { + LOGGER.info(String.format("Bootstrapping node...")); + + // Give repository a chance to backup in case things go badly wrong (if enabled) + if (Settings.getInstance().getRepositoryBackupInterval() > 0) { + try { + // Timeout if the database isn't ready for backing up after 60 seconds + long timeout = 60 * 1000L; + RepositoryManager.backup(true, "backup", timeout); + + } catch (TimeoutException e) { + LOGGER.info("Attempt to backup repository failed due to timeout: {}", e.getMessage()); + // Continue with the bootstrap anyway... + } + } + + // Get the repository path from settings + String repositoryPath = Settings.getInstance().getRepositoryPath(); + LOGGER.debug(String.format("Repository path: %s", repositoryPath)); + + // Call ApplyUpdate to end this process (unlocking current JAR so it can be replaced) + 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)); + + try { + Path directory = Paths.get(repositoryPath); + Files.walkFileTree(directory, new SimpleFileVisitor() { + @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; + } + }); + + List javaCmd = new ArrayList<>(); + + // Java runtime binary itself + javaCmd.add(javaBinary.toString()); + + // JVM arguments + javaCmd.addAll(ManagementFactory.getRuntimeMXBean().getInputArguments()); + + // Disable, but retain, any -agentlib JVM arg as sub-process might fail if it tries to reuse same port + javaCmd = javaCmd.stream() + .map(arg -> arg.replace("-agentlib", AGENTLIB_JVM_HOLDER_ARG)) + .collect(Collectors.toList()); + + // Remove JNI options as they won't be supported by command-line 'java' + // These are typically added by the AdvancedInstaller Java launcher EXE + javaCmd.removeAll(Arrays.asList("abort", "exit", "vfprintf")); + + // Call ApplyUpdate using JAR + javaCmd.addAll(Arrays.asList("-cp", JAR_FILENAME, ApplyUpdate.class.getCanonicalName())); + + // Add command-line args saved from start-up + String[] savedArgs = Controller.getInstance().getSavedArgs(); + if (savedArgs != null) + javaCmd.addAll(Arrays.asList(savedArgs)); + + LOGGER.info(String.format("Restarting node with: %s", String.join(" ", javaCmd))); + + SysTray.getInstance().showMessage(Translator.INSTANCE.translate("SysTray", "AUTO_UPDATE"), //TODO + Translator.INSTANCE.translate("SysTray", "APPLYING_UPDATE_AND_RESTARTING"), //TODO + MessageType.INFO); + + ProcessBuilder processBuilder = new ProcessBuilder(javaCmd); + + // 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(); + + return true; // restarting node OK + } catch (Exception e) { + LOGGER.error(String.format("Failed to restart node: %s", e.getMessage())); + + return true; // repo was okay, even if applying update failed + } + } + } From 7dbf53261627b38fcce648d4e720336c687cd067 Mon Sep 17 00:00:00 2001 From: AlphaX-Projects <77661270+AlphaX-Projects@users.noreply.github.com> Date: Thu, 28 Dec 2023 14:22:01 +0100 Subject: [PATCH 2/3] Remove unusedaddress ( no more functional ) --- .../resource/CrossChainBitcoinResource.java | 39 ------------------- .../resource/CrossChainDigibyteResource.java | 39 ------------------- .../resource/CrossChainDogecoinResource.java | 39 ------------------- .../resource/CrossChainLitecoinResource.java | 39 ------------------- .../resource/CrossChainRavencoinResource.java | 39 ------------------- 5 files changed, 195 deletions(-) diff --git a/src/main/java/org/qortal/api/resource/CrossChainBitcoinResource.java b/src/main/java/org/qortal/api/resource/CrossChainBitcoinResource.java index 0abf7c0e3..6f3b9f506 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainBitcoinResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainBitcoinResource.java @@ -189,45 +189,6 @@ public List getBitcoinAddressInfos(@HeaderParam(Security.API_KEY_HE } } - @POST - @Path("/unusedaddress") - @Operation( - summary = "Returns first unused address for hierarchical, deterministic BIP32 wallet", - description = "Supply BIP32 'm' private/public key in base58, starting with 'xprv'/'xpub' for mainnet, 'tprv'/'tpub' for testnet", - requestBody = @RequestBody( - required = true, - content = @Content( - mediaType = MediaType.TEXT_PLAIN, - schema = @Schema( - type = "string", - description = "BIP32 'm' private/public key in base58", - example = "tpubD6NzVbkrYhZ4XTPc4btCZ6SMgn8CxmWkj6VBVZ1tfcJfMq4UwAjZbG8U74gGSypL9XBYk2R2BLbDBe8pcEyBKM1edsGQEPKXNbEskZozeZc" - ) - ) - ), - responses = { - @ApiResponse( - content = @Content(array = @ArraySchema( schema = @Schema( implementation = SimpleTransaction.class ) ) ) - ) - } - ) - @ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE}) - @SecurityRequirement(name = "apiKey") - public String getUnusedBitcoinReceiveAddress(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String key58) { - Security.checkApiCallAllowed(request); - - Bitcoin bitcoin = Bitcoin.getInstance(); - - if (!bitcoin.isValidDeterministicKey(key58)) - throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY); - - try { - return bitcoin.getUnusedReceiveAddress(key58); - } catch (ForeignBlockchainException e) { - throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE); - } - } - @POST @Path("/send") @Operation( diff --git a/src/main/java/org/qortal/api/resource/CrossChainDigibyteResource.java b/src/main/java/org/qortal/api/resource/CrossChainDigibyteResource.java index e675200b5..898754d6b 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainDigibyteResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainDigibyteResource.java @@ -189,45 +189,6 @@ public List getDigibyteAddressInfos(@HeaderParam(Security.API_KEY_H } } - @POST - @Path("/unusedaddress") - @Operation( - summary = "Returns first unused address for hierarchical, deterministic BIP32 wallet", - description = "Supply BIP32 'm' private/public key in base58, starting with 'xprv'/'xpub' for mainnet, 'tprv'/'tpub' for testnet", - requestBody = @RequestBody( - required = true, - content = @Content( - mediaType = MediaType.TEXT_PLAIN, - schema = @Schema( - type = "string", - description = "BIP32 'm' private/public key in base58", - example = "tpubD6NzVbkrYhZ4XTPc4btCZ6SMgn8CxmWkj6VBVZ1tfcJfMq4UwAjZbG8U74gGSypL9XBYk2R2BLbDBe8pcEyBKM1edsGQEPKXNbEskZozeZc" - ) - ) - ), - responses = { - @ApiResponse( - content = @Content(array = @ArraySchema( schema = @Schema( implementation = SimpleTransaction.class ) ) ) - ) - } - ) - @ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE}) - @SecurityRequirement(name = "apiKey") - public String getUnusedDigibyteReceiveAddress(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String key58) { - Security.checkApiCallAllowed(request); - - Digibyte digibyte = Digibyte.getInstance(); - - if (!digibyte.isValidDeterministicKey(key58)) - throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY); - - try { - return digibyte.getUnusedReceiveAddress(key58); - } catch (ForeignBlockchainException e) { - throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE); - } - } - @POST @Path("/send") @Operation( diff --git a/src/main/java/org/qortal/api/resource/CrossChainDogecoinResource.java b/src/main/java/org/qortal/api/resource/CrossChainDogecoinResource.java index e292f8e2d..1608c90ec 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainDogecoinResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainDogecoinResource.java @@ -189,45 +189,6 @@ public List getDogecoinAddressInfos(@HeaderParam(Security.API_KEY_H } } - @POST - @Path("/unusedaddress") - @Operation( - summary = "Returns first unused address for hierarchical, deterministic BIP32 wallet", - description = "Supply BIP32 'm' private/public key in base58, starting with 'xprv'/'xpub' for mainnet, 'tprv'/'tpub' for testnet", - requestBody = @RequestBody( - required = true, - content = @Content( - mediaType = MediaType.TEXT_PLAIN, - schema = @Schema( - type = "string", - description = "BIP32 'm' private/public key in base58", - example = "tpubD6NzVbkrYhZ4XTPc4btCZ6SMgn8CxmWkj6VBVZ1tfcJfMq4UwAjZbG8U74gGSypL9XBYk2R2BLbDBe8pcEyBKM1edsGQEPKXNbEskZozeZc" - ) - ) - ), - responses = { - @ApiResponse( - content = @Content(array = @ArraySchema( schema = @Schema( implementation = SimpleTransaction.class ) ) ) - ) - } - ) - @ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE}) - @SecurityRequirement(name = "apiKey") - public String getUnusedDogecoinReceiveAddress(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String key58) { - Security.checkApiCallAllowed(request); - - Dogecoin dogecoin = Dogecoin.getInstance(); - - if (!dogecoin.isValidDeterministicKey(key58)) - throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY); - - try { - return dogecoin.getUnusedReceiveAddress(key58); - } catch (ForeignBlockchainException e) { - throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE); - } - } - @POST @Path("/send") @Operation( diff --git a/src/main/java/org/qortal/api/resource/CrossChainLitecoinResource.java b/src/main/java/org/qortal/api/resource/CrossChainLitecoinResource.java index 38a8a5981..5ad953447 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainLitecoinResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainLitecoinResource.java @@ -189,45 +189,6 @@ public List getLitecoinAddressInfos(@HeaderParam(Security.API_KEY_H } } - @POST - @Path("/unusedaddress") - @Operation( - summary = "Returns first unused address for hierarchical, deterministic BIP32 wallet", - description = "Supply BIP32 'm' private/public key in base58, starting with 'xprv'/'xpub' for mainnet, 'tprv'/'tpub' for testnet", - requestBody = @RequestBody( - required = true, - content = @Content( - mediaType = MediaType.TEXT_PLAIN, - schema = @Schema( - type = "string", - description = "BIP32 'm' private/public key in base58", - example = "tpubD6NzVbkrYhZ4XTPc4btCZ6SMgn8CxmWkj6VBVZ1tfcJfMq4UwAjZbG8U74gGSypL9XBYk2R2BLbDBe8pcEyBKM1edsGQEPKXNbEskZozeZc" - ) - ) - ), - responses = { - @ApiResponse( - content = @Content(array = @ArraySchema( schema = @Schema( implementation = SimpleTransaction.class ) ) ) - ) - } - ) - @ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE}) - @SecurityRequirement(name = "apiKey") - public String getUnusedLitecoinReceiveAddress(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String key58) { - Security.checkApiCallAllowed(request); - - Litecoin litecoin = Litecoin.getInstance(); - - if (!litecoin.isValidDeterministicKey(key58)) - throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY); - - try { - return litecoin.getUnusedReceiveAddress(key58); - } catch (ForeignBlockchainException e) { - throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE); - } - } - @POST @Path("/send") @Operation( diff --git a/src/main/java/org/qortal/api/resource/CrossChainRavencoinResource.java b/src/main/java/org/qortal/api/resource/CrossChainRavencoinResource.java index 64ebc1364..9b702b25c 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainRavencoinResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainRavencoinResource.java @@ -189,45 +189,6 @@ public List getRavencoinAddressInfos(@HeaderParam(Security.API_KEY_ } } - @POST - @Path("/unusedaddress") - @Operation( - summary = "Returns first unused address for hierarchical, deterministic BIP32 wallet", - description = "Supply BIP32 'm' private/public key in base58, starting with 'xprv'/'xpub' for mainnet, 'tprv'/'tpub' for testnet", - requestBody = @RequestBody( - required = true, - content = @Content( - mediaType = MediaType.TEXT_PLAIN, - schema = @Schema( - type = "string", - description = "BIP32 'm' private/public key in base58", - example = "tpubD6NzVbkrYhZ4XTPc4btCZ6SMgn8CxmWkj6VBVZ1tfcJfMq4UwAjZbG8U74gGSypL9XBYk2R2BLbDBe8pcEyBKM1edsGQEPKXNbEskZozeZc" - ) - ) - ), - responses = { - @ApiResponse( - content = @Content(array = @ArraySchema( schema = @Schema( implementation = SimpleTransaction.class ) ) ) - ) - } - ) - @ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE}) - @SecurityRequirement(name = "apiKey") - public String getUnusedRavencoinReceiveAddress(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String key58) { - Security.checkApiCallAllowed(request); - - Ravencoin ravencoin = Ravencoin.getInstance(); - - if (!ravencoin.isValidDeterministicKey(key58)) - throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY); - - try { - return ravencoin.getUnusedReceiveAddress(key58); - } catch (ForeignBlockchainException e) { - throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_NETWORK_ISSUE); - } - } - @POST @Path("/send") @Operation( From 97b4db4095ddcf724176c2122aa56604f70c1b4a Mon Sep 17 00:00:00 2001 From: AlphaX-Projects <77661270+AlphaX-Projects@users.noreply.github.com> Date: Thu, 28 Dec 2023 14:54:20 +0100 Subject: [PATCH 3/3] Update dependencies --- pom.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 4d736704a..bff266a47 100644 --- a/pom.xml +++ b/pom.xml @@ -21,12 +21,12 @@ 1.2.2 0.12.3 4.9.10 - 1.60.0 - 32.1.3-jre + 1.60.1 + 33.0.0-jre 2.2 1.2.1 2.5.1 - 74.1 + 74.2 4.12 4.0.1 2.3.9 @@ -39,11 +39,11 @@ 1.0.0 2.21.1 1.5.0-b01 - 3.11.0 + 3.12.1 3.3.0 3.3.1 3.5.1 - 3.2.2 + 3.2.3 1.1.0 UTF-8 3.25.0