forked from Qortal/qortal
Merge branch 'master' into q-apps
This commit is contained in:
commit
dc25d33739
2
pom.xml
2
pom.xml
@ -3,7 +3,7 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>org.qortal</groupId>
|
<groupId>org.qortal</groupId>
|
||||||
<artifactId>qortal</artifactId>
|
<artifactId>qortal</artifactId>
|
||||||
<version>3.9.0</version>
|
<version>3.9.1</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<properties>
|
<properties>
|
||||||
<skipTests>true</skipTests>
|
<skipTests>true</skipTests>
|
||||||
|
@ -79,7 +79,7 @@ public enum ApiError {
|
|||||||
// BUYER_ALREADY_OWNER(411, 422),
|
// BUYER_ALREADY_OWNER(411, 422),
|
||||||
|
|
||||||
// POLLS
|
// POLLS
|
||||||
// POLL_NO_EXISTS(501, 404),
|
POLL_NO_EXISTS(501, 404),
|
||||||
// POLL_ALREADY_EXISTS(502, 422),
|
// POLL_ALREADY_EXISTS(502, 422),
|
||||||
// DUPLICATE_OPTION(503, 422),
|
// DUPLICATE_OPTION(503, 422),
|
||||||
// POLL_OPTION_NO_EXISTS(504, 404),
|
// POLL_OPTION_NO_EXISTS(504, 404),
|
||||||
|
@ -41,6 +41,7 @@ import org.glassfish.jersey.servlet.ServletContainer;
|
|||||||
import org.qortal.api.resource.AnnotationPostProcessor;
|
import org.qortal.api.resource.AnnotationPostProcessor;
|
||||||
import org.qortal.api.resource.ApiDefinition;
|
import org.qortal.api.resource.ApiDefinition;
|
||||||
import org.qortal.api.websocket.*;
|
import org.qortal.api.websocket.*;
|
||||||
|
import org.qortal.network.Network;
|
||||||
import org.qortal.settings.Settings;
|
import org.qortal.settings.Settings;
|
||||||
|
|
||||||
public class ApiService {
|
public class ApiService {
|
||||||
@ -125,13 +126,13 @@ public class ApiService {
|
|||||||
ServerConnector portUnifiedConnector = new ServerConnector(this.server,
|
ServerConnector portUnifiedConnector = new ServerConnector(this.server,
|
||||||
new DetectorConnectionFactory(sslConnectionFactory),
|
new DetectorConnectionFactory(sslConnectionFactory),
|
||||||
httpConnectionFactory);
|
httpConnectionFactory);
|
||||||
portUnifiedConnector.setHost(Settings.getInstance().getBindAddress());
|
portUnifiedConnector.setHost(Network.getInstance().getBindAddress());
|
||||||
portUnifiedConnector.setPort(Settings.getInstance().getApiPort());
|
portUnifiedConnector.setPort(Settings.getInstance().getApiPort());
|
||||||
|
|
||||||
this.server.addConnector(portUnifiedConnector);
|
this.server.addConnector(portUnifiedConnector);
|
||||||
} else {
|
} else {
|
||||||
// Non-SSL
|
// Non-SSL
|
||||||
InetAddress bindAddr = InetAddress.getByName(Settings.getInstance().getBindAddress());
|
InetAddress bindAddr = InetAddress.getByName(Network.getInstance().getBindAddress());
|
||||||
InetSocketAddress endpoint = new InetSocketAddress(bindAddr, Settings.getInstance().getApiPort());
|
InetSocketAddress endpoint = new InetSocketAddress(bindAddr, Settings.getInstance().getApiPort());
|
||||||
this.server = new Server(endpoint);
|
this.server = new Server(endpoint);
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ import org.glassfish.jersey.server.ResourceConfig;
|
|||||||
import org.glassfish.jersey.servlet.ServletContainer;
|
import org.glassfish.jersey.servlet.ServletContainer;
|
||||||
import org.qortal.api.resource.AnnotationPostProcessor;
|
import org.qortal.api.resource.AnnotationPostProcessor;
|
||||||
import org.qortal.api.resource.ApiDefinition;
|
import org.qortal.api.resource.ApiDefinition;
|
||||||
|
import org.qortal.network.Network;
|
||||||
import org.qortal.settings.Settings;
|
import org.qortal.settings.Settings;
|
||||||
|
|
||||||
import javax.net.ssl.KeyManagerFactory;
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
@ -98,13 +99,13 @@ public class DomainMapService {
|
|||||||
ServerConnector portUnifiedConnector = new ServerConnector(this.server,
|
ServerConnector portUnifiedConnector = new ServerConnector(this.server,
|
||||||
new DetectorConnectionFactory(sslConnectionFactory),
|
new DetectorConnectionFactory(sslConnectionFactory),
|
||||||
httpConnectionFactory);
|
httpConnectionFactory);
|
||||||
portUnifiedConnector.setHost(Settings.getInstance().getBindAddress());
|
portUnifiedConnector.setHost(Network.getInstance().getBindAddress());
|
||||||
portUnifiedConnector.setPort(Settings.getInstance().getDomainMapPort());
|
portUnifiedConnector.setPort(Settings.getInstance().getDomainMapPort());
|
||||||
|
|
||||||
this.server.addConnector(portUnifiedConnector);
|
this.server.addConnector(portUnifiedConnector);
|
||||||
} else {
|
} else {
|
||||||
// Non-SSL
|
// Non-SSL
|
||||||
InetAddress bindAddr = InetAddress.getByName(Settings.getInstance().getBindAddress());
|
InetAddress bindAddr = InetAddress.getByName(Network.getInstance().getBindAddress());
|
||||||
InetSocketAddress endpoint = new InetSocketAddress(bindAddr, Settings.getInstance().getDomainMapPort());
|
InetSocketAddress endpoint = new InetSocketAddress(bindAddr, Settings.getInstance().getDomainMapPort());
|
||||||
this.server = new Server(endpoint);
|
this.server = new Server(endpoint);
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ import org.glassfish.jersey.server.ResourceConfig;
|
|||||||
import org.glassfish.jersey.servlet.ServletContainer;
|
import org.glassfish.jersey.servlet.ServletContainer;
|
||||||
import org.qortal.api.resource.AnnotationPostProcessor;
|
import org.qortal.api.resource.AnnotationPostProcessor;
|
||||||
import org.qortal.api.resource.ApiDefinition;
|
import org.qortal.api.resource.ApiDefinition;
|
||||||
|
import org.qortal.network.Network;
|
||||||
import org.qortal.settings.Settings;
|
import org.qortal.settings.Settings;
|
||||||
|
|
||||||
import javax.net.ssl.KeyManagerFactory;
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
@ -98,13 +99,13 @@ public class GatewayService {
|
|||||||
ServerConnector portUnifiedConnector = new ServerConnector(this.server,
|
ServerConnector portUnifiedConnector = new ServerConnector(this.server,
|
||||||
new DetectorConnectionFactory(sslConnectionFactory),
|
new DetectorConnectionFactory(sslConnectionFactory),
|
||||||
httpConnectionFactory);
|
httpConnectionFactory);
|
||||||
portUnifiedConnector.setHost(Settings.getInstance().getBindAddress());
|
portUnifiedConnector.setHost(Network.getInstance().getBindAddress());
|
||||||
portUnifiedConnector.setPort(Settings.getInstance().getGatewayPort());
|
portUnifiedConnector.setPort(Settings.getInstance().getGatewayPort());
|
||||||
|
|
||||||
this.server.addConnector(portUnifiedConnector);
|
this.server.addConnector(portUnifiedConnector);
|
||||||
} else {
|
} else {
|
||||||
// Non-SSL
|
// Non-SSL
|
||||||
InetAddress bindAddr = InetAddress.getByName(Settings.getInstance().getBindAddress());
|
InetAddress bindAddr = InetAddress.getByName(Network.getInstance().getBindAddress());
|
||||||
InetSocketAddress endpoint = new InetSocketAddress(bindAddr, Settings.getInstance().getGatewayPort());
|
InetSocketAddress endpoint = new InetSocketAddress(bindAddr, Settings.getInstance().getGatewayPort());
|
||||||
this.server = new Server(endpoint);
|
this.server = new Server(endpoint);
|
||||||
}
|
}
|
||||||
|
197
src/main/java/org/qortal/api/resource/PollsResource.java
Normal file
197
src/main/java/org/qortal/api/resource/PollsResource.java
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
package org.qortal.api.resource;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||||
|
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import org.qortal.api.ApiError;
|
||||||
|
import org.qortal.api.ApiErrors;
|
||||||
|
import org.qortal.api.ApiExceptionFactory;
|
||||||
|
import org.qortal.data.transaction.CreatePollTransactionData;
|
||||||
|
import org.qortal.data.transaction.PaymentTransactionData;
|
||||||
|
import org.qortal.data.transaction.VoteOnPollTransactionData;
|
||||||
|
import org.qortal.repository.DataException;
|
||||||
|
import org.qortal.repository.Repository;
|
||||||
|
import org.qortal.repository.RepositoryManager;
|
||||||
|
import org.qortal.settings.Settings;
|
||||||
|
import org.qortal.transaction.Transaction;
|
||||||
|
import org.qortal.transform.TransformationException;
|
||||||
|
import org.qortal.transform.transaction.CreatePollTransactionTransformer;
|
||||||
|
import org.qortal.transform.transaction.PaymentTransactionTransformer;
|
||||||
|
import org.qortal.transform.transaction.VoteOnPollTransactionTransformer;
|
||||||
|
import org.qortal.utils.Base58;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.ws.rs.POST;
|
||||||
|
import javax.ws.rs.Path;
|
||||||
|
import javax.ws.rs.core.Context;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.ws.rs.GET;
|
||||||
|
import javax.ws.rs.PathParam;
|
||||||
|
import javax.ws.rs.QueryParam;
|
||||||
|
import org.qortal.api.ApiException;
|
||||||
|
import org.qortal.data.voting.PollData;
|
||||||
|
|
||||||
|
@Path("/polls")
|
||||||
|
@Tag(name = "Polls")
|
||||||
|
public class PollsResource {
|
||||||
|
@Context
|
||||||
|
HttpServletRequest request;
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Operation(
|
||||||
|
summary = "List all polls",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "poll info",
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.APPLICATION_JSON,
|
||||||
|
array = @ArraySchema(schema = @Schema(implementation = PollData.class))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ApiErrors({ApiError.REPOSITORY_ISSUE})
|
||||||
|
public List<PollData> getAllPolls(@Parameter(
|
||||||
|
ref = "limit"
|
||||||
|
) @QueryParam("limit") Integer limit, @Parameter(
|
||||||
|
ref = "offset"
|
||||||
|
) @QueryParam("offset") Integer offset, @Parameter(
|
||||||
|
ref = "reverse"
|
||||||
|
) @QueryParam("reverse") Boolean reverse) {
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
List<PollData> allPollData = repository.getVotingRepository().getAllPolls(limit, offset, reverse);
|
||||||
|
return allPollData;
|
||||||
|
} catch (DataException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/{pollName}")
|
||||||
|
@Operation(
|
||||||
|
summary = "Info on poll",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "poll info",
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.APPLICATION_JSON,
|
||||||
|
schema = @Schema(implementation = PollData.class)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ApiErrors({ApiError.REPOSITORY_ISSUE})
|
||||||
|
public PollData getPollData(@PathParam("pollName") String pollName) {
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
PollData pollData = repository.getVotingRepository().fromPollName(pollName);
|
||||||
|
if (pollData == null)
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.POLL_NO_EXISTS);
|
||||||
|
|
||||||
|
return pollData;
|
||||||
|
} catch (ApiException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (DataException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/create")
|
||||||
|
@Operation(
|
||||||
|
summary = "Build raw, unsigned, CREATE_POLL transaction",
|
||||||
|
requestBody = @RequestBody(
|
||||||
|
required = true,
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.APPLICATION_JSON,
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = CreatePollTransactionData.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "raw, unsigned, CREATE_POLL transaction encoded in Base58",
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.TEXT_PLAIN,
|
||||||
|
schema = @Schema(
|
||||||
|
type = "string"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
|
public String CreatePoll(CreatePollTransactionData transactionData) {
|
||||||
|
if (Settings.getInstance().isApiRestricted())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
|
|
||||||
|
Transaction.ValidationResult result = transaction.isValidUnconfirmed();
|
||||||
|
if (result != Transaction.ValidationResult.OK)
|
||||||
|
throw TransactionsResource.createTransactionInvalidException(request, result);
|
||||||
|
|
||||||
|
byte[] bytes = CreatePollTransactionTransformer.toBytes(transactionData);
|
||||||
|
return Base58.encode(bytes);
|
||||||
|
} catch (TransformationException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSFORMATION_ERROR, e);
|
||||||
|
} catch (DataException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/vote")
|
||||||
|
@Operation(
|
||||||
|
summary = "Build raw, unsigned, VOTE_ON_POLL transaction",
|
||||||
|
requestBody = @RequestBody(
|
||||||
|
required = true,
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.APPLICATION_JSON,
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = VoteOnPollTransactionData.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "raw, unsigned, VOTE_ON_POLL transaction encoded in Base58",
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.TEXT_PLAIN,
|
||||||
|
schema = @Schema(
|
||||||
|
type = "string"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||||
|
public String VoteOnPoll(VoteOnPollTransactionData transactionData) {
|
||||||
|
if (Settings.getInstance().isApiRestricted())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
|
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||||
|
|
||||||
|
Transaction.ValidationResult result = transaction.isValidUnconfirmed();
|
||||||
|
if (result != Transaction.ValidationResult.OK)
|
||||||
|
throw TransactionsResource.createTransactionInvalidException(request, result);
|
||||||
|
|
||||||
|
byte[] bytes = VoteOnPollTransactionTransformer.toBytes(transactionData);
|
||||||
|
return Base58.encode(bytes);
|
||||||
|
} catch (TransformationException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSFORMATION_ERROR, e);
|
||||||
|
} catch (DataException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -153,6 +153,22 @@ public class AdminResource {
|
|||||||
return nodeStatus;
|
return nodeStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/settings")
|
||||||
|
@Operation(
|
||||||
|
summary = "Fetch node settings",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = Settings.class))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public Settings settings() {
|
||||||
|
Settings nodeSettings = Settings.getInstance();
|
||||||
|
|
||||||
|
return nodeSettings;
|
||||||
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/stop")
|
@Path("/stop")
|
||||||
@Operation(
|
@Operation(
|
||||||
|
@ -13,7 +13,9 @@ import org.qortal.repository.RepositoryManager;
|
|||||||
import org.qortal.transaction.Transaction.TransactionType;
|
import org.qortal.transaction.Transaction.TransactionType;
|
||||||
import org.qortal.utils.Unicode;
|
import org.qortal.utils.Unicode;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class NamesDatabaseIntegrityCheck {
|
public class NamesDatabaseIntegrityCheck {
|
||||||
|
|
||||||
@ -28,16 +30,8 @@ public class NamesDatabaseIntegrityCheck {
|
|||||||
|
|
||||||
private List<TransactionData> nameTransactions = new ArrayList<>();
|
private List<TransactionData> nameTransactions = new ArrayList<>();
|
||||||
|
|
||||||
|
|
||||||
public int rebuildName(String name, Repository repository) {
|
public int rebuildName(String name, Repository repository) {
|
||||||
return this.rebuildName(name, repository, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int rebuildName(String name, Repository repository, List<String> referenceNames) {
|
|
||||||
// "referenceNames" tracks the linked names that have already been rebuilt, to prevent circular dependencies
|
|
||||||
if (referenceNames == null) {
|
|
||||||
referenceNames = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
int modificationCount = 0;
|
int modificationCount = 0;
|
||||||
try {
|
try {
|
||||||
List<TransactionData> transactions = this.fetchAllTransactionsInvolvingName(name, repository);
|
List<TransactionData> transactions = this.fetchAllTransactionsInvolvingName(name, repository);
|
||||||
@ -46,6 +40,14 @@ public class NamesDatabaseIntegrityCheck {
|
|||||||
return modificationCount;
|
return modificationCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If this name has been updated at any point, we need to add transactions from the other names to the sequence
|
||||||
|
int added = this.addAdditionalTransactionsRelatingToName(transactions, name, repository);
|
||||||
|
while (added > 0) {
|
||||||
|
// Keep going until all have been added
|
||||||
|
LOGGER.trace("{} added for {}. Looking for more transactions...", added, name);
|
||||||
|
added = this.addAdditionalTransactionsRelatingToName(transactions, name, repository);
|
||||||
|
}
|
||||||
|
|
||||||
// Loop through each past transaction and re-apply it to the Names table
|
// Loop through each past transaction and re-apply it to the Names table
|
||||||
for (TransactionData currentTransaction : transactions) {
|
for (TransactionData currentTransaction : transactions) {
|
||||||
|
|
||||||
@ -61,29 +63,14 @@ public class NamesDatabaseIntegrityCheck {
|
|||||||
// Process UPDATE_NAME transactions
|
// Process UPDATE_NAME transactions
|
||||||
if (currentTransaction.getType() == TransactionType.UPDATE_NAME) {
|
if (currentTransaction.getType() == TransactionType.UPDATE_NAME) {
|
||||||
UpdateNameTransactionData updateNameTransactionData = (UpdateNameTransactionData) currentTransaction;
|
UpdateNameTransactionData updateNameTransactionData = (UpdateNameTransactionData) currentTransaction;
|
||||||
|
Name nameObj = new Name(repository, updateNameTransactionData.getName());
|
||||||
if (Objects.equals(updateNameTransactionData.getNewName(), name) &&
|
if (nameObj != null && nameObj.getNameData() != null) {
|
||||||
!Objects.equals(updateNameTransactionData.getName(), updateNameTransactionData.getNewName())) {
|
nameObj.update(updateNameTransactionData);
|
||||||
// This renames an existing name, so we need to process that instead
|
modificationCount++;
|
||||||
|
LOGGER.trace("Processed UPDATE_NAME transaction for name {}", name);
|
||||||
if (!referenceNames.contains(name)) {
|
} else {
|
||||||
referenceNames.add(name);
|
// Something went wrong
|
||||||
this.rebuildName(updateNameTransactionData.getName(), repository, referenceNames);
|
throw new DataException(String.format("Name data not found for name %s", updateNameTransactionData.getName()));
|
||||||
}
|
|
||||||
else {
|
|
||||||
// We've already processed this name so there's nothing more to do
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Name nameObj = new Name(repository, name);
|
|
||||||
if (nameObj != null && nameObj.getNameData() != null) {
|
|
||||||
nameObj.update(updateNameTransactionData);
|
|
||||||
modificationCount++;
|
|
||||||
LOGGER.trace("Processed UPDATE_NAME transaction for name {}", name);
|
|
||||||
} else {
|
|
||||||
// Something went wrong
|
|
||||||
throw new DataException(String.format("Name data not found for name %s", updateNameTransactionData.getName()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -354,8 +341,8 @@ public class NamesDatabaseIntegrityCheck {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort by lowest timestamp first
|
// Sort by lowest block height first
|
||||||
transactions.sort(Comparator.comparingLong(TransactionData::getTimestamp));
|
sortTransactions(transactions);
|
||||||
|
|
||||||
return transactions;
|
return transactions;
|
||||||
}
|
}
|
||||||
@ -419,4 +406,67 @@ public class NamesDatabaseIntegrityCheck {
|
|||||||
return names;
|
return names;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int addAdditionalTransactionsRelatingToName(List<TransactionData> transactions, String name, Repository repository) throws DataException {
|
||||||
|
int added = 0;
|
||||||
|
|
||||||
|
// If this name has been updated at any point, we need to add transactions from the other names to the sequence
|
||||||
|
List<String> otherNames = new ArrayList<>();
|
||||||
|
List<TransactionData> updateNameTransactions = transactions.stream().filter(t -> t.getType() == TransactionType.UPDATE_NAME).collect(Collectors.toList());
|
||||||
|
for (TransactionData transactionData : updateNameTransactions) {
|
||||||
|
UpdateNameTransactionData updateNameTransactionData = (UpdateNameTransactionData) transactionData;
|
||||||
|
// If the newName field isn't empty, and either the "name" or "newName" is different from our reference name,
|
||||||
|
// we should remember this additional name, in case it has relevant transactions associated with it.
|
||||||
|
if (updateNameTransactionData.getNewName() != null && !updateNameTransactionData.getNewName().isEmpty()) {
|
||||||
|
if (!Objects.equals(updateNameTransactionData.getName(), name)) {
|
||||||
|
otherNames.add(updateNameTransactionData.getName());
|
||||||
|
}
|
||||||
|
if (!Objects.equals(updateNameTransactionData.getNewName(), name)) {
|
||||||
|
otherNames.add(updateNameTransactionData.getNewName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for (String otherName : otherNames) {
|
||||||
|
List<TransactionData> otherNameTransactions = this.fetchAllTransactionsInvolvingName(otherName, repository);
|
||||||
|
for (TransactionData otherNameTransactionData : otherNameTransactions) {
|
||||||
|
if (!transactions.contains(otherNameTransactionData)) {
|
||||||
|
// Add new transaction relating to other name
|
||||||
|
transactions.add(otherNameTransactionData);
|
||||||
|
added++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (added > 0) {
|
||||||
|
// New transaction(s) added, so re-sort
|
||||||
|
sortTransactions(transactions);
|
||||||
|
}
|
||||||
|
|
||||||
|
return added;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sortTransactions(List<TransactionData> transactions) {
|
||||||
|
Collections.sort(transactions, new Comparator() {
|
||||||
|
public int compare(Object o1, Object o2) {
|
||||||
|
TransactionData td1 = (TransactionData) o1;
|
||||||
|
TransactionData td2 = (TransactionData) o2;
|
||||||
|
|
||||||
|
// Sort by block height first
|
||||||
|
int heightComparison = td1.getBlockHeight().compareTo(td2.getBlockHeight());
|
||||||
|
if (heightComparison != 0) {
|
||||||
|
return heightComparison;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same height so compare timestamps
|
||||||
|
int timestampComparison = Long.compare(td1.getTimestamp(), td2.getTimestamp());
|
||||||
|
if (timestampComparison != 0) {
|
||||||
|
return timestampComparison;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same timestamp so compare signatures
|
||||||
|
return new BigInteger(td1.getSignature()).compareTo(new BigInteger(td2.getSignature()));
|
||||||
|
}});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,11 @@ package org.qortal.data.transaction;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.xml.bind.Unmarshaller;
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
|
|
||||||
|
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue;
|
||||||
import org.qortal.data.voting.PollOptionData;
|
import org.qortal.data.voting.PollOptionData;
|
||||||
import org.qortal.transaction.Transaction;
|
import org.qortal.transaction.Transaction;
|
||||||
import org.qortal.transaction.Transaction.TransactionType;
|
import org.qortal.transaction.Transaction.TransactionType;
|
||||||
@ -14,8 +16,13 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
|||||||
// All properties to be converted to JSON via JAXB
|
// All properties to be converted to JSON via JAXB
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
@Schema(allOf = { TransactionData.class })
|
@Schema(allOf = { TransactionData.class })
|
||||||
|
@XmlDiscriminatorValue("CREATE_POLL")
|
||||||
public class CreatePollTransactionData extends TransactionData {
|
public class CreatePollTransactionData extends TransactionData {
|
||||||
|
|
||||||
|
|
||||||
|
@Schema(description = "Poll creator's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP")
|
||||||
|
private byte[] pollCreatorPublicKey;
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
private String owner;
|
private String owner;
|
||||||
private String pollName;
|
private String pollName;
|
||||||
@ -29,10 +36,15 @@ public class CreatePollTransactionData extends TransactionData {
|
|||||||
super(TransactionType.CREATE_POLL);
|
super(TransactionType.CREATE_POLL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void afterUnmarshal(Unmarshaller u, Object parent) {
|
||||||
|
this.creatorPublicKey = this.pollCreatorPublicKey;
|
||||||
|
}
|
||||||
|
|
||||||
public CreatePollTransactionData(BaseTransactionData baseTransactionData,
|
public CreatePollTransactionData(BaseTransactionData baseTransactionData,
|
||||||
String owner, String pollName, String description, List<PollOptionData> pollOptions) {
|
String owner, String pollName, String description, List<PollOptionData> pollOptions) {
|
||||||
super(Transaction.TransactionType.CREATE_POLL, baseTransactionData);
|
super(Transaction.TransactionType.CREATE_POLL, baseTransactionData);
|
||||||
|
|
||||||
|
this.creatorPublicKey = baseTransactionData.creatorPublicKey;
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
this.pollName = pollName;
|
this.pollName = pollName;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
@ -41,6 +53,7 @@ public class CreatePollTransactionData extends TransactionData {
|
|||||||
|
|
||||||
// Getters/setters
|
// Getters/setters
|
||||||
|
|
||||||
|
public byte[] getPollCreatorPublicKey() { return this.creatorPublicKey; }
|
||||||
public String getOwner() {
|
public String getOwner() {
|
||||||
return this.owner;
|
return this.owner;
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
|||||||
|
|
||||||
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorNode;
|
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorNode;
|
||||||
import org.qortal.crypto.Crypto;
|
import org.qortal.crypto.Crypto;
|
||||||
|
import org.qortal.data.voting.PollData;
|
||||||
import org.qortal.transaction.Transaction.ApprovalStatus;
|
import org.qortal.transaction.Transaction.ApprovalStatus;
|
||||||
import org.qortal.transaction.Transaction.TransactionType;
|
import org.qortal.transaction.Transaction.TransactionType;
|
||||||
|
|
||||||
@ -29,6 +30,7 @@ import io.swagger.v3.oas.annotations.media.Schema.AccessMode;
|
|||||||
@XmlSeeAlso({GenesisTransactionData.class, PaymentTransactionData.class, RegisterNameTransactionData.class, UpdateNameTransactionData.class,
|
@XmlSeeAlso({GenesisTransactionData.class, PaymentTransactionData.class, RegisterNameTransactionData.class, UpdateNameTransactionData.class,
|
||||||
SellNameTransactionData.class, CancelSellNameTransactionData.class, BuyNameTransactionData.class,
|
SellNameTransactionData.class, CancelSellNameTransactionData.class, BuyNameTransactionData.class,
|
||||||
CreatePollTransactionData.class, VoteOnPollTransactionData.class, ArbitraryTransactionData.class,
|
CreatePollTransactionData.class, VoteOnPollTransactionData.class, ArbitraryTransactionData.class,
|
||||||
|
PollData.class,
|
||||||
IssueAssetTransactionData.class, TransferAssetTransactionData.class,
|
IssueAssetTransactionData.class, TransferAssetTransactionData.class,
|
||||||
CreateAssetOrderTransactionData.class, CancelAssetOrderTransactionData.class,
|
CreateAssetOrderTransactionData.class, CancelAssetOrderTransactionData.class,
|
||||||
MultiPaymentTransactionData.class, DeployAtTransactionData.class, MessageTransactionData.class, ATTransactionData.class,
|
MultiPaymentTransactionData.class, DeployAtTransactionData.class, MessageTransactionData.class, ATTransactionData.class,
|
||||||
|
@ -3,7 +3,9 @@ package org.qortal.data.transaction;
|
|||||||
import javax.xml.bind.Unmarshaller;
|
import javax.xml.bind.Unmarshaller;
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
|
import javax.xml.bind.annotation.XmlTransient;
|
||||||
|
|
||||||
|
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue;
|
||||||
import org.qortal.transaction.Transaction.TransactionType;
|
import org.qortal.transaction.Transaction.TransactionType;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
@ -11,12 +13,17 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
|||||||
// All properties to be converted to JSON via JAXB
|
// All properties to be converted to JSON via JAXB
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
@Schema(allOf = { TransactionData.class })
|
@Schema(allOf = { TransactionData.class })
|
||||||
|
@XmlDiscriminatorValue("VOTE_ON_POLL")
|
||||||
public class VoteOnPollTransactionData extends TransactionData {
|
public class VoteOnPollTransactionData extends TransactionData {
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
|
@Schema(description = "Vote creator's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP")
|
||||||
private byte[] voterPublicKey;
|
private byte[] voterPublicKey;
|
||||||
private String pollName;
|
private String pollName;
|
||||||
private int optionIndex;
|
private int optionIndex;
|
||||||
|
// For internal use when orphaning
|
||||||
|
@XmlTransient
|
||||||
|
@Schema(hidden = true)
|
||||||
private Integer previousOptionIndex;
|
private Integer previousOptionIndex;
|
||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
|
@ -14,6 +14,11 @@ public class PollData {
|
|||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
|
// For JAXB
|
||||||
|
protected PollData() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
public PollData(byte[] creatorPublicKey, String owner, String pollName, String description, List<PollOptionData> pollOptions, long published) {
|
public PollData(byte[] creatorPublicKey, String owner, String pollName, String description, List<PollOptionData> pollOptions, long published) {
|
||||||
this.creatorPublicKey = creatorPublicKey;
|
this.creatorPublicKey = creatorPublicKey;
|
||||||
this.owner = owner;
|
this.owner = owner;
|
||||||
@ -29,22 +34,42 @@ public class PollData {
|
|||||||
return this.creatorPublicKey;
|
return this.creatorPublicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setCreatorPublicKey(byte[] creatorPublicKey) {
|
||||||
|
this.creatorPublicKey = creatorPublicKey;
|
||||||
|
}
|
||||||
|
|
||||||
public String getOwner() {
|
public String getOwner() {
|
||||||
return this.owner;
|
return this.owner;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setOwner(String owner) {
|
||||||
|
this.owner = owner;
|
||||||
|
}
|
||||||
|
|
||||||
public String getPollName() {
|
public String getPollName() {
|
||||||
return this.pollName;
|
return this.pollName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setPollName(String pollName) {
|
||||||
|
this.pollName = pollName;
|
||||||
|
}
|
||||||
|
|
||||||
public String getDescription() {
|
public String getDescription() {
|
||||||
return this.description;
|
return this.description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDescription(String description) {
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
public List<PollOptionData> getPollOptions() {
|
public List<PollOptionData> getPollOptions() {
|
||||||
return this.pollOptions;
|
return this.pollOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setPollOptions(List<PollOptionData> pollOptions) {
|
||||||
|
this.pollOptions = pollOptions;
|
||||||
|
}
|
||||||
|
|
||||||
public long getPublished() {
|
public long getPublished() {
|
||||||
return this.published;
|
return this.published;
|
||||||
}
|
}
|
||||||
|
@ -124,6 +124,8 @@ public class Network {
|
|||||||
|
|
||||||
private final List<PeerAddress> selfPeers = new ArrayList<>();
|
private final List<PeerAddress> selfPeers = new ArrayList<>();
|
||||||
|
|
||||||
|
private String bindAddress = null;
|
||||||
|
|
||||||
private final ExecuteProduceConsume networkEPC;
|
private final ExecuteProduceConsume networkEPC;
|
||||||
private Selector channelSelector;
|
private Selector channelSelector;
|
||||||
private ServerSocketChannel serverChannel;
|
private ServerSocketChannel serverChannel;
|
||||||
@ -159,25 +161,43 @@ public class Network {
|
|||||||
// Grab P2P port from settings
|
// Grab P2P port from settings
|
||||||
int listenPort = Settings.getInstance().getListenPort();
|
int listenPort = Settings.getInstance().getListenPort();
|
||||||
|
|
||||||
// Grab P2P bind address from settings
|
// Grab P2P bind addresses from settings
|
||||||
try {
|
List<String> bindAddresses = new ArrayList<>();
|
||||||
InetAddress bindAddr = InetAddress.getByName(Settings.getInstance().getBindAddress());
|
if (Settings.getInstance().getBindAddress() != null) {
|
||||||
InetSocketAddress endpoint = new InetSocketAddress(bindAddr, listenPort);
|
bindAddresses.add(Settings.getInstance().getBindAddress());
|
||||||
|
}
|
||||||
|
if (Settings.getInstance().getBindAddressFallback() != null) {
|
||||||
|
bindAddresses.add(Settings.getInstance().getBindAddressFallback());
|
||||||
|
}
|
||||||
|
|
||||||
channelSelector = Selector.open();
|
for (int i=0; i<bindAddresses.size(); i++) {
|
||||||
|
try {
|
||||||
|
String bindAddress = bindAddresses.get(i);
|
||||||
|
InetAddress bindAddr = InetAddress.getByName(bindAddress);
|
||||||
|
InetSocketAddress endpoint = new InetSocketAddress(bindAddr, listenPort);
|
||||||
|
|
||||||
// Set up listen socket
|
channelSelector = Selector.open();
|
||||||
serverChannel = ServerSocketChannel.open();
|
|
||||||
serverChannel.configureBlocking(false);
|
// Set up listen socket
|
||||||
serverChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true);
|
serverChannel = ServerSocketChannel.open();
|
||||||
serverChannel.bind(endpoint, LISTEN_BACKLOG);
|
serverChannel.configureBlocking(false);
|
||||||
serverSelectionKey = serverChannel.register(channelSelector, SelectionKey.OP_ACCEPT);
|
serverChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true);
|
||||||
} catch (UnknownHostException e) {
|
serverChannel.bind(endpoint, LISTEN_BACKLOG);
|
||||||
LOGGER.error("Can't bind listen socket to address {}", Settings.getInstance().getBindAddress());
|
serverSelectionKey = serverChannel.register(channelSelector, SelectionKey.OP_ACCEPT);
|
||||||
throw new IOException("Can't bind listen socket to address", e);
|
|
||||||
} catch (IOException e) {
|
this.bindAddress = bindAddress; // Store the selected address, so that it can be used by other parts of the app
|
||||||
LOGGER.error("Can't create listen socket: {}", e.getMessage());
|
break; // We don't want to bind to more than one address
|
||||||
throw new IOException("Can't create listen socket", e);
|
} catch (UnknownHostException e) {
|
||||||
|
LOGGER.error("Can't bind listen socket to address {}", Settings.getInstance().getBindAddress());
|
||||||
|
if (i == bindAddresses.size()-1) { // Only throw an exception if all addresses have been tried
|
||||||
|
throw new IOException("Can't bind listen socket to address", e);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOGGER.error("Can't create listen socket: {}", e.getMessage());
|
||||||
|
if (i == bindAddresses.size()-1) { // Only throw an exception if all addresses have been tried
|
||||||
|
throw new IOException("Can't create listen socket", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load all known peers from repository
|
// Load all known peers from repository
|
||||||
@ -228,6 +248,10 @@ public class Network {
|
|||||||
return this.maxPeers;
|
return this.maxPeers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getBindAddress() {
|
||||||
|
return this.bindAddress;
|
||||||
|
}
|
||||||
|
|
||||||
public byte[] getMessageMagic() {
|
public byte[] getMessageMagic() {
|
||||||
return Settings.getInstance().isTestNet() ? TESTNET_MESSAGE_MAGIC : MAINNET_MESSAGE_MAGIC;
|
return Settings.getInstance().isTestNet() ? TESTNET_MESSAGE_MAGIC : MAINNET_MESSAGE_MAGIC;
|
||||||
}
|
}
|
||||||
@ -1556,7 +1580,7 @@ public class Network {
|
|||||||
this.isShuttingDown = true;
|
this.isShuttingDown = true;
|
||||||
|
|
||||||
// Close listen socket to prevent more incoming connections
|
// Close listen socket to prevent more incoming connections
|
||||||
if (this.serverChannel.isOpen()) {
|
if (this.serverChannel != null && this.serverChannel.isOpen()) {
|
||||||
try {
|
try {
|
||||||
this.serverChannel.close();
|
this.serverChannel.close();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
@ -9,6 +9,8 @@ public interface VotingRepository {
|
|||||||
|
|
||||||
// Polls
|
// Polls
|
||||||
|
|
||||||
|
public List<PollData> getAllPolls(Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||||
|
|
||||||
public PollData fromPollName(String pollName) throws DataException;
|
public PollData fromPollName(String pollName) throws DataException;
|
||||||
|
|
||||||
public boolean pollExists(String pollName) throws DataException;
|
public boolean pollExists(String pollName) throws DataException;
|
||||||
|
@ -21,6 +21,55 @@ public class HSQLDBVotingRepository implements VotingRepository {
|
|||||||
|
|
||||||
// Polls
|
// Polls
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<PollData> getAllPolls(Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
|
StringBuilder sql = new StringBuilder(512);
|
||||||
|
|
||||||
|
sql.append("SELECT poll_name, description, creator, owner, published_when FROM Polls ORDER BY poll_name");
|
||||||
|
|
||||||
|
if (reverse != null && reverse)
|
||||||
|
sql.append(" DESC");
|
||||||
|
|
||||||
|
HSQLDBRepository.limitOffsetSql(sql, limit, offset);
|
||||||
|
|
||||||
|
List<PollData> polls = new ArrayList<>();
|
||||||
|
|
||||||
|
try (ResultSet resultSet = this.repository.checkedExecute(sql.toString())) {
|
||||||
|
if (resultSet == null)
|
||||||
|
return polls;
|
||||||
|
|
||||||
|
do {
|
||||||
|
String pollName = resultSet.getString(1);
|
||||||
|
String description = resultSet.getString(2);
|
||||||
|
byte[] creatorPublicKey = resultSet.getBytes(3);
|
||||||
|
String owner = resultSet.getString(4);
|
||||||
|
long published = resultSet.getLong(5);
|
||||||
|
|
||||||
|
String optionsSql = "SELECT option_name FROM PollOptions WHERE poll_name = ? ORDER BY option_index ASC";
|
||||||
|
try (ResultSet optionsResultSet = this.repository.checkedExecute(optionsSql, pollName)) {
|
||||||
|
if (optionsResultSet == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
List<PollOptionData> pollOptions = new ArrayList<>();
|
||||||
|
|
||||||
|
// NOTE: do-while because checkedExecute() above has already called rs.next() for us
|
||||||
|
do {
|
||||||
|
String optionName = optionsResultSet.getString(1);
|
||||||
|
|
||||||
|
pollOptions.add(new PollOptionData(optionName));
|
||||||
|
} while (optionsResultSet.next());
|
||||||
|
|
||||||
|
polls.add(new PollData(creatorPublicKey, owner, pollName, description, pollOptions, published));
|
||||||
|
}
|
||||||
|
|
||||||
|
} while (resultSet.next());
|
||||||
|
|
||||||
|
return polls;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DataException("Unable to fetch polls from repository", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PollData fromPollName(String pollName) throws DataException {
|
public PollData fromPollName(String pollName) throws DataException {
|
||||||
String sql = "SELECT description, creator, owner, published_when FROM Polls WHERE poll_name = ?";
|
String sql = "SELECT description, creator, owner, published_when FROM Polls WHERE poll_name = ?";
|
||||||
|
@ -61,6 +61,7 @@ public class Settings {
|
|||||||
|
|
||||||
// Common to all networking (API/P2P)
|
// Common to all networking (API/P2P)
|
||||||
private String bindAddress = "::"; // Use IPv6 wildcard to listen on all local addresses
|
private String bindAddress = "::"; // Use IPv6 wildcard to listen on all local addresses
|
||||||
|
private String bindAddressFallback = "0.0.0.0"; // Some systems are unable to bind using IPv6
|
||||||
|
|
||||||
// UI servers
|
// UI servers
|
||||||
private int uiPort = 12388;
|
private int uiPort = 12388;
|
||||||
@ -689,6 +690,10 @@ public class Settings {
|
|||||||
return this.bindAddress;
|
return this.bindAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getBindAddressFallback() {
|
||||||
|
return this.bindAddressFallback;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isUPnPEnabled() {
|
public boolean isUPnPEnabled() {
|
||||||
return this.uPnPEnabled;
|
return this.uPnPEnabled;
|
||||||
}
|
}
|
||||||
|
@ -128,7 +128,7 @@ public class IntegrityTests extends Common {
|
|||||||
|
|
||||||
// Run the database integrity check for the initial name, to ensure it doesn't get into a loop
|
// Run the database integrity check for the initial name, to ensure it doesn't get into a loop
|
||||||
NamesDatabaseIntegrityCheck integrityCheck = new NamesDatabaseIntegrityCheck();
|
NamesDatabaseIntegrityCheck integrityCheck = new NamesDatabaseIntegrityCheck();
|
||||||
assertEquals(2, integrityCheck.rebuildName(initialName, repository));
|
assertEquals(4, integrityCheck.rebuildName(initialName, repository)); // 4 transactions total
|
||||||
|
|
||||||
// Ensure the new name still exists and the data is still correct
|
// Ensure the new name still exists and the data is still correct
|
||||||
assertTrue(repository.getNameRepository().nameExists(initialName));
|
assertTrue(repository.getNameRepository().nameExists(initialName));
|
||||||
|
Loading…
Reference in New Issue
Block a user