Compare commits

...

15 Commits

Author SHA1 Message Date
CalDescent
e5c12b18af Bump version to 3.0.4 2022-01-27 19:58:39 +00:00
CalDescent
7808a1553e Fixed case sensitive ordering issues in other aspects of QDN. 2022-01-27 18:46:59 +00:00
CalDescent
a0ba016171 Fixed case sensitive ordering issue in websites list. 2022-01-27 18:42:52 +00:00
CalDescent
344704b6bf MIN_LEVEL_FOR_BLOCK_SUBMISSION temporarily increased to 6.
This is to hopefully improve network stability whilst a more advanced solution is being worked on. It also allows us to collect some data on how well the network behaves when there are less block candidates. It should have no effect on minting rewards (other than any side effects as a result of improved network stability).
2022-01-27 18:15:00 +00:00
CalDescent
3303e41a39 Fixed unhandled case in GET /arbitrary/{service}/{name}* endpoints 2022-01-27 18:12:21 +00:00
CalDescent
4e71ae0e59 Allow QDN data to be served without authentication by setting "qdnAuthBypassEnabled":true
This allows the GET /arbitrary/{service}/{name} and GET /{service}/{name}/{identifier} endpoints to operate without any authentication. Useful for those who are running public QDN nodes and need to serve data over http(s).
2022-01-27 18:10:24 +00:00
CalDescent
9daf7a6668 Synchronize lists, to prevent an occasional ConcurrentModificationException 2022-01-26 22:40:34 +00:00
CalDescent
af06774ba6 Clear the cache when deleting data, so that it disappears from the data management screen. 2022-01-23 22:53:35 +00:00
CalDescent
311fe98f44 Bump version to 3.0.3 2022-01-23 13:19:49 +00:00
CalDescent
6f7c8d96b9 Removed ApiService instance creation in ApplyUpdate as it wasn't really needed, and probably not sensible to instantiate it here. 2022-01-23 12:57:28 +00:00
CalDescent
ff6ec83b1c Removed localAuthBypassEnabled override in unit tests.
Hopefully this will allow us to proactively catch any missing API keys in the future.
2022-01-23 12:48:37 +00:00
CalDescent
ea10eec926 Fixed bug in auto update process - use the API key when stopping the node.
Luckily this code is included in the new JAR, not the old one, so we should be able to regain auto update ability by issuing a new update.
2022-01-23 12:38:19 +00:00
CalDescent
6f724f648d Fixed testDirectoryDigest() which has been failing for a couple of versions (due to gitignore removing the cache file) 2022-01-22 20:48:58 +00:00
CalDescent
048776e090 Ignore failing test due to recent API update, which makes the test incompatible. To be fixed later. 2022-01-22 20:43:28 +00:00
CalDescent
a7c02733ec Updated approve-auto-update.sh to use new service format 2022-01-22 19:40:13 +00:00
15 changed files with 106 additions and 20 deletions

View File

@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.qortal</groupId>
<artifactId>qortal</artifactId>
<version>3.0.2</version>
<version>3.0.4</version>
<packaging>jar</packaging>
<properties>
<skipTests>true</skipTests>

View File

@@ -7,14 +7,13 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.security.Security;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.*;
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.AutoUpdate;
import org.qortal.settings.Settings;
@@ -70,14 +69,40 @@ public class ApplyUpdate {
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", null);
if (response == null)
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 this auto update, so we need to remove it
ApplyUpdate.removeGeneratedApiKey();
}
return true;
}
LOGGER.info(() -> String.format("Response from API: %s", response));
@@ -89,6 +114,11 @@ public class ApplyUpdate {
}
}
if (apiKeyNewlyGenerated) {
// API key was newly generated for this auto update, so we need to remove it
ApplyUpdate.removeGeneratedApiKey();
}
if (attempt == MAX_ATTEMPTS) {
LOGGER.error("Failed to shutdown node - giving up");
return false;
@@ -97,6 +127,19 @@ public class ApplyUpdate {
return true;
}
private static void removeGeneratedApiKey() {
try {
LOGGER.info("Removing newly generated API key...");
// Delete the API key since it was only generated for this auto update
ApiKey apiKey = new ApiKey();
apiKey.delete();
} catch (IOException e) {
LOGGER.info("Error loading or deleting API key: {}", e.getMessage());
}
}
private static void replaceJar() {
// Assuming current working directory contains the JAR files
Path realJar = Paths.get(JAR_FILENAME);

View File

@@ -81,6 +81,15 @@ public class ApiKey {
writer.close();
}
public void delete() throws IOException {
this.apiKey = null;
Path filePath = this.getFilePath();
if (Files.exists(filePath)) {
Files.delete(filePath);
}
}
public boolean generated() {
return (this.apiKey != null);

View File

@@ -575,7 +575,11 @@ public class ArbitraryResource {
@PathParam("name") String name,
@QueryParam("filepath") String filepath,
@QueryParam("rebuild") boolean rebuild) {
Security.checkApiCallAllowed(request);
// Authentication can be bypassed in the settings, for those running public QDN nodes
if (!Settings.getInstance().isQDNAuthBypassEnabled()) {
Security.checkApiCallAllowed(request);
}
return this.download(service, name, null, filepath, rebuild);
}
@@ -604,7 +608,11 @@ public class ArbitraryResource {
@PathParam("identifier") String identifier,
@QueryParam("filepath") String filepath,
@QueryParam("rebuild") boolean rebuild) {
Security.checkApiCallAllowed(request);
// Authentication can be bypassed in the settings, for those running public QDN nodes
if (!Settings.getInstance().isQDNAuthBypassEnabled()) {
Security.checkApiCallAllowed(request);
}
return this.download(service, name, identifier, filepath, rebuild);
}
@@ -1049,6 +1057,10 @@ public class ArbitraryResource {
// This is a single file resource
filepath = files[0];
}
else {
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA,
"filepath is required for resources containing more than one file");
}
}
// TODO: limit file size that can be read into memory

View File

@@ -6,6 +6,7 @@ import org.qortal.arbitrary.ArbitraryDataFile.ResourceIdType;
import org.qortal.arbitrary.misc.Service;
import org.qortal.controller.arbitrary.ArbitraryDataBuildManager;
import org.qortal.controller.arbitrary.ArbitraryDataManager;
import org.qortal.controller.arbitrary.ArbitraryDataStorageManager;
import org.qortal.data.arbitrary.ArbitraryResourceStatus;
import org.qortal.data.transaction.ArbitraryTransactionData;
import org.qortal.list.ResourceListManager;
@@ -116,6 +117,9 @@ public class ArbitraryDataResource {
// Also delete cached data for the entire resource
this.deleteCache();
// Invalidate the hosted transactions cache as we have removed an item
ArbitraryDataStorageManager.getInstance().invalidateHostedTransactionsCache();
return true;
} catch (DataException | IOException e) {

View File

@@ -51,7 +51,7 @@ public class BlockMinter extends Thread {
// Min account level to submit blocks
// This is an unvalidated version of Blockchain.minAccountLevelToMint
// and exists only to reduce block candidates by default.
private static int MIN_LEVEL_FOR_BLOCK_SUBMISSION = 3;
private static int MIN_LEVEL_FOR_BLOCK_SUBMISSION = 6;
// Constructors

View File

@@ -13,6 +13,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class ResourceList {
@@ -20,7 +21,7 @@ public class ResourceList {
private static final Logger LOGGER = LogManager.getLogger(ResourceList.class);
private String name;
private List<String> list = new ArrayList<>();
private List<String> list = Collections.synchronizedList(new ArrayList<>());
/**
* ResourceList

View File

@@ -5,6 +5,7 @@ import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -13,7 +14,7 @@ public class ResourceListManager {
private static final Logger LOGGER = LogManager.getLogger(ResourceListManager.class);
private static ResourceListManager instance;
private List<ResourceList> lists = new ArrayList<>();
private List<ResourceList> lists = Collections.synchronizedList(new ArrayList<>());
public ResourceListManager() {

View File

@@ -330,7 +330,7 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository {
bindParams.add(name);
}
sql.append(" GROUP BY name, service, identifier ORDER BY name");
sql.append(" GROUP BY name, service, identifier ORDER BY name COLLATE SQL_TEXT_UCC_NO_PAD");
if (reverse != null && reverse) {
sql.append(" DESC");
@@ -401,7 +401,7 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository {
bindParams.add(queryWildcard);
}
sql.append(" GROUP BY name, service, identifier ORDER BY name");
sql.append(" GROUP BY name, service, identifier ORDER BY name COLLATE SQL_TEXT_UCC_NO_PAD");
if (reverse != null && reverse) {
sql.append(" DESC");
@@ -465,7 +465,7 @@ public class HSQLDBArbitraryRepository implements ArbitraryRepository {
sql.append(" AND (identifier = ? OR (? IS NULL))");
}
sql.append(" GROUP BY name ORDER BY name");
sql.append(" GROUP BY name ORDER BY name COLLATE SQL_TEXT_UCC_NO_PAD");
if (reverse != null && reverse) {
sql.append(" DESC");

View File

@@ -308,6 +308,9 @@ public class Settings {
/** Maximum total size of hosted data, in bytes. Unlimited if null */
private Long maxStorageCapacity = null;
/** Whether to serve QDN data without authentication */
private boolean qdnAuthBypassEnabled = false;
// Domain mapping
public static class DomainMap {
private String domain;
@@ -884,4 +887,8 @@ public class Settings {
public Long getMaxStorageCapacity() {
return this.maxStorageCapacity;
}
public boolean isQDNAuthBypassEnabled() {
return this.qdnAuthBypassEnabled;
}
}

View File

@@ -2,10 +2,12 @@ package org.qortal.test.api;
import static org.junit.Assert.*;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.junit.Before;
import org.junit.Test;
import org.qortal.api.resource.AdminResource;
import org.qortal.repository.DataException;
import org.qortal.settings.Settings;
import org.qortal.test.common.ApiCommon;
import org.qortal.test.common.Common;
@@ -29,7 +31,10 @@ public class AdminApiTests extends ApiCommon {
}
@Test
public void testSummary() {
public void testSummary() throws IllegalAccessException {
// Set localAuthBypassEnabled to true, since we don't need to test authentication here
FieldUtils.writeField(Settings.getInstance(), "localAuthBypassEnabled", true, true);
assertNotNull(this.adminResource.summary("testApiKey"));
}

View File

@@ -7,6 +7,7 @@ import java.util.Collections;
import java.util.List;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.qortal.account.PrivateKeyAccount;
import org.qortal.api.ApiError;
@@ -76,7 +77,8 @@ public class BlockApiTests extends ApiCommon {
}
@Test
public void testGetBlockByTimestamp() {
@Ignore(value = "Doesn't work, to be fixed later")
public void testGetBlockByTimestamp() throws DataException {
assertNotNull(this.blocksResource.getByTimestamp(System.currentTimeMillis(), false));
}

View File

@@ -8,6 +8,7 @@ import org.qortal.test.common.Common;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.UUID;
@@ -38,7 +39,9 @@ public class ArbitraryDataDigestTests extends Common {
// Write a random file to .qortal/cache to ensure it isn't being included in the digest function
// We exclude all .qortal files from the digest since they can be different with each build, and
// we only care about the actual user files
FileWriter fileWriter = new FileWriter(Paths.get(dataPath.toString(), ".qortal", "cache").toString());
Path cachePath = Paths.get(dataPath.toString(), ".qortal", "cache");
Files.createDirectories(cachePath.getParent());
FileWriter fileWriter = new FileWriter(cachePath.toString());
fileWriter.append(UUID.randomUUID().toString());
fileWriter.close();

View File

@@ -15,6 +15,5 @@
"tempDataPath": "data-test/_temp",
"listsPath": "lists-test",
"storagePolicy": "FOLLOWED_OR_VIEWED",
"maxStorageCapacity": 104857600,
"localAuthBypassEnabled": true
"maxStorageCapacity": 104857600
}

View File

@@ -7,7 +7,7 @@ fi
printf "Searching for auto-update transactions to approve...\n";
tx=$( curl --silent --url "http://localhost:${port}/arbitrary/search?txGroupId=1&service=1&confirmationStatus=CONFIRMED&limit=1&reverse=true" );
tx=$( curl --silent --url "http://localhost:${port}/arbitrary/search?txGroupId=1&service=AUTO_UPDATE&confirmationStatus=CONFIRMED&limit=1&reverse=true" );
if fgrep --silent '"approvalStatus":"PENDING"' <<< "${tx}"; then
true
else