forked from Qortal/qortal
Merge branch 'master' into arbitrary-resources-cache
This commit is contained in:
commit
21f01226e9
13
Q-Apps.md
13
Q-Apps.md
@ -252,6 +252,7 @@ Here is a list of currently supported actions:
|
||||
- GET_USER_ACCOUNT
|
||||
- GET_ACCOUNT_DATA
|
||||
- GET_ACCOUNT_NAMES
|
||||
- SEARCH_NAMES
|
||||
- GET_NAME_DATA
|
||||
- LIST_QDN_RESOURCES
|
||||
- SEARCH_QDN_RESOURCES
|
||||
@ -324,6 +325,18 @@ let res = await qortalRequest({
|
||||
});
|
||||
```
|
||||
|
||||
### Search names
|
||||
```
|
||||
let res = await qortalRequest({
|
||||
action: "SEARCH_NAMES",
|
||||
query: "search query goes here",
|
||||
prefix: false, // Optional - if true, only the beginning of the name is matched
|
||||
limit: 100,
|
||||
offset: 0,
|
||||
reverse: false
|
||||
});
|
||||
```
|
||||
|
||||
### Get name data
|
||||
```
|
||||
let res = await qortalRequest({
|
||||
|
2
pom.xml
2
pom.xml
@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.qortal</groupId>
|
||||
<artifactId>qortal</artifactId>
|
||||
<version>4.0.2</version>
|
||||
<version>4.0.3</version>
|
||||
<packaging>jar</packaging>
|
||||
<properties>
|
||||
<skipTests>true</skipTests>
|
||||
|
56
src/main/java/org/qortal/api/model/PollVotes.java
Normal file
56
src/main/java/org/qortal/api/model/PollVotes.java
Normal file
@ -0,0 +1,56 @@
|
||||
package org.qortal.api.model;
|
||||
|
||||
import java.util.List;
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import org.qortal.data.voting.VoteOnPollData;
|
||||
|
||||
@Schema(description = "Poll vote info, including voters")
|
||||
// All properties to be converted to JSON via JAX-RS
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class PollVotes {
|
||||
|
||||
@Schema(description = "List of individual votes")
|
||||
@XmlElement(name = "votes")
|
||||
public List<VoteOnPollData> votes;
|
||||
|
||||
@Schema(description = "Total number of votes")
|
||||
public Integer totalVotes;
|
||||
|
||||
@Schema(description = "List of vote counts for each option")
|
||||
public List<OptionCount> voteCounts;
|
||||
|
||||
// For JAX-RS
|
||||
protected PollVotes() {
|
||||
}
|
||||
|
||||
public PollVotes(List<VoteOnPollData> votes, Integer totalVotes, List<OptionCount> voteCounts) {
|
||||
this.votes = votes;
|
||||
this.totalVotes = totalVotes;
|
||||
this.voteCounts = voteCounts;
|
||||
}
|
||||
|
||||
@Schema(description = "Vote info")
|
||||
// All properties to be converted to JSON via JAX-RS
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public static class OptionCount {
|
||||
@Schema(description = "Option name")
|
||||
public String optionName;
|
||||
|
||||
@Schema(description = "Vote count")
|
||||
public Integer voteCount;
|
||||
|
||||
// For JAX-RS
|
||||
protected OptionCount() {
|
||||
}
|
||||
|
||||
public OptionCount(String optionName, Integer voteCount) {
|
||||
this.optionName = optionName;
|
||||
this.voteCount = voteCount;
|
||||
}
|
||||
}
|
||||
}
|
@ -115,6 +115,9 @@ public class CrossChainResource {
|
||||
crossChainTrades.sort((a, b) -> Longs.compare(a.creationTimestamp, b.creationTimestamp));
|
||||
}
|
||||
|
||||
// Remove any trades that have had too many failures
|
||||
crossChainTrades = TradeBot.getInstance().removeFailedTrades(repository, crossChainTrades);
|
||||
|
||||
if (limit != null && limit > 0) {
|
||||
// Make sure to not return more than the limit
|
||||
int upperLimit = Math.min(limit, crossChainTrades.size());
|
||||
@ -129,6 +132,64 @@ public class CrossChainResource {
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/tradeoffers/hidden")
|
||||
@Operation(
|
||||
summary = "Find cross-chain trade offers that have been hidden due to too many failures",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
content = @Content(
|
||||
array = @ArraySchema(
|
||||
schema = @Schema(
|
||||
implementation = CrossChainTradeData.class
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.INVALID_CRITERIA, ApiError.REPOSITORY_ISSUE})
|
||||
public List<CrossChainTradeData> getHiddenTradeOffers(
|
||||
@Parameter(
|
||||
description = "Limit to specific blockchain",
|
||||
example = "LITECOIN",
|
||||
schema = @Schema(implementation = SupportedBlockchain.class)
|
||||
) @QueryParam("foreignBlockchain") SupportedBlockchain foreignBlockchain) {
|
||||
|
||||
final boolean isExecutable = true;
|
||||
List<CrossChainTradeData> crossChainTrades = new ArrayList<>();
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
Map<ByteArray, Supplier<ACCT>> acctsByCodeHash = SupportedBlockchain.getFilteredAcctMap(foreignBlockchain);
|
||||
|
||||
for (Map.Entry<ByteArray, Supplier<ACCT>> acctInfo : acctsByCodeHash.entrySet()) {
|
||||
byte[] codeHash = acctInfo.getKey().value;
|
||||
ACCT acct = acctInfo.getValue().get();
|
||||
|
||||
List<ATData> atsData = repository.getATRepository().getATsByFunctionality(codeHash, isExecutable, null, null, null);
|
||||
|
||||
for (ATData atData : atsData) {
|
||||
CrossChainTradeData crossChainTradeData = acct.populateTradeData(repository, atData);
|
||||
if (crossChainTradeData.mode == AcctMode.OFFERING) {
|
||||
crossChainTrades.add(crossChainTradeData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the trades by timestamp
|
||||
crossChainTrades.sort((a, b) -> Longs.compare(a.creationTimestamp, b.creationTimestamp));
|
||||
|
||||
// Remove trades that haven't failed
|
||||
crossChainTrades.removeIf(t -> !TradeBot.getInstance().isFailedTrade(repository, t));
|
||||
|
||||
crossChainTrades.stream().forEach(CrossChainResource::decorateTradeDataWithPresence);
|
||||
|
||||
return crossChainTrades;
|
||||
} catch (DataException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/trade/{ataddress}")
|
||||
@Operation(
|
||||
|
@ -47,6 +47,7 @@ import org.qortal.transform.transaction.RegisterNameTransactionTransformer;
|
||||
import org.qortal.transform.transaction.SellNameTransactionTransformer;
|
||||
import org.qortal.transform.transaction.UpdateNameTransactionTransformer;
|
||||
import org.qortal.utils.Base58;
|
||||
import org.qortal.utils.Unicode;
|
||||
|
||||
@Path("/names")
|
||||
@Tag(name = "Names")
|
||||
@ -63,19 +64,19 @@ public class NamesResource {
|
||||
description = "registered name info",
|
||||
content = @Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
array = @ArraySchema(schema = @Schema(implementation = NameSummary.class))
|
||||
array = @ArraySchema(schema = @Schema(implementation = NameData.class))
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.REPOSITORY_ISSUE})
|
||||
public List<NameSummary> getAllNames(@Parameter(ref = "limit") @QueryParam("limit") Integer limit, @Parameter(ref = "offset") @QueryParam("offset") Integer offset,
|
||||
public List<NameData> getAllNames(@Parameter(description = "Return only names registered or updated after timestamp") @QueryParam("after") Long after,
|
||||
@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<NameData> names = repository.getNameRepository().getAllNames(limit, offset, reverse);
|
||||
|
||||
// Convert to summary
|
||||
return names.stream().map(NameSummary::new).collect(Collectors.toList());
|
||||
return repository.getNameRepository().getAllNames(after, limit, offset, reverse);
|
||||
} catch (DataException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
@ -135,12 +136,13 @@ public class NamesResource {
|
||||
public NameData getName(@PathParam("name") String name) {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
NameData nameData;
|
||||
String reducedName = Unicode.sanitize(name);
|
||||
|
||||
if (Settings.getInstance().isLite()) {
|
||||
nameData = LiteNode.getInstance().fetchNameData(name);
|
||||
}
|
||||
else {
|
||||
nameData = repository.getNameRepository().fromName(name);
|
||||
nameData = repository.getNameRepository().fromReducedName(reducedName);
|
||||
}
|
||||
|
||||
if (nameData == null) {
|
||||
@ -171,6 +173,7 @@ public class NamesResource {
|
||||
)
|
||||
@ApiErrors({ApiError.NAME_UNKNOWN, ApiError.REPOSITORY_ISSUE})
|
||||
public List<NameData> searchNames(@QueryParam("query") String query,
|
||||
@Parameter(description = "Prefix only (if true, only the beginning of the name is matched)") @QueryParam("prefix") Boolean prefixOnly,
|
||||
@Parameter(ref = "limit") @QueryParam("limit") Integer limit,
|
||||
@Parameter(ref = "offset") @QueryParam("offset") Integer offset,
|
||||
@Parameter(ref="reverse") @QueryParam("reverse") Boolean reverse) {
|
||||
@ -179,7 +182,9 @@ public class NamesResource {
|
||||
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, "Missing query");
|
||||
}
|
||||
|
||||
return repository.getNameRepository().searchNames(query, limit, offset, reverse);
|
||||
boolean usePrefixOnly = Boolean.TRUE.equals(prefixOnly);
|
||||
|
||||
return repository.getNameRepository().searchNames(query, usePrefixOnly, limit, offset, reverse);
|
||||
} catch (ApiException e) {
|
||||
throw e;
|
||||
} catch (DataException e) {
|
||||
|
@ -31,12 +31,18 @@ import javax.ws.rs.core.MediaType;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import org.qortal.api.ApiException;
|
||||
import org.qortal.api.model.PollVotes;
|
||||
import org.qortal.data.voting.PollData;
|
||||
import org.qortal.data.voting.PollOptionData;
|
||||
import org.qortal.data.voting.VoteOnPollData;
|
||||
|
||||
@Path("/polls")
|
||||
@Tag(name = "Polls")
|
||||
@ -102,6 +108,61 @@ public class PollsResource {
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/votes/{pollName}")
|
||||
@Operation(
|
||||
summary = "Votes on poll",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
description = "poll votes",
|
||||
content = @Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(implementation = PollVotes.class)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.REPOSITORY_ISSUE})
|
||||
public PollVotes getPollVotes(@PathParam("pollName") String pollName, @QueryParam("onlyCounts") Boolean onlyCounts) {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PollData pollData = repository.getVotingRepository().fromPollName(pollName);
|
||||
if (pollData == null)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.POLL_NO_EXISTS);
|
||||
|
||||
List<VoteOnPollData> votes = repository.getVotingRepository().getVotes(pollName);
|
||||
|
||||
// Initialize map for counting votes
|
||||
Map<String, Integer> voteCountMap = new HashMap<>();
|
||||
for (PollOptionData optionData : pollData.getPollOptions()) {
|
||||
voteCountMap.put(optionData.getOptionName(), 0);
|
||||
}
|
||||
|
||||
int totalVotes = 0;
|
||||
for (VoteOnPollData vote : votes) {
|
||||
String selectedOption = pollData.getPollOptions().get(vote.getOptionIndex()).getOptionName();
|
||||
if (voteCountMap.containsKey(selectedOption)) {
|
||||
voteCountMap.put(selectedOption, voteCountMap.get(selectedOption) + 1);
|
||||
totalVotes++;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert map to list of VoteInfo
|
||||
List<PollVotes.OptionCount> voteCounts = voteCountMap.entrySet().stream()
|
||||
.map(entry -> new PollVotes.OptionCount(entry.getKey(), entry.getValue()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (onlyCounts != null && onlyCounts) {
|
||||
return new PollVotes(null, totalVotes, voteCounts);
|
||||
} else {
|
||||
return new PollVotes(votes, totalVotes, voteCounts);
|
||||
}
|
||||
} catch (ApiException e) {
|
||||
throw e;
|
||||
} catch (DataException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/create")
|
||||
@Operation(
|
||||
|
@ -24,6 +24,7 @@ import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
|
||||
import org.qortal.api.model.CrossChainOfferSummary;
|
||||
import org.qortal.controller.Controller;
|
||||
import org.qortal.controller.Synchronizer;
|
||||
import org.qortal.controller.tradebot.TradeBot;
|
||||
import org.qortal.crosschain.SupportedBlockchain;
|
||||
import org.qortal.crosschain.ACCT;
|
||||
import org.qortal.crosschain.AcctMode;
|
||||
@ -315,7 +316,7 @@ public class TradeOffersWebSocket extends ApiWebSocket implements Listener {
|
||||
throw new DataException("Couldn't fetch historic trades from repository");
|
||||
|
||||
for (ATStateData historicAtState : historicAtStates) {
|
||||
CrossChainOfferSummary historicOfferSummary = produceSummary(repository, acct, historicAtState, null);
|
||||
CrossChainOfferSummary historicOfferSummary = produceSummary(repository, acct, historicAtState, null, null);
|
||||
|
||||
if (!isHistoric.test(historicOfferSummary))
|
||||
continue;
|
||||
@ -330,8 +331,10 @@ public class TradeOffersWebSocket extends ApiWebSocket implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
private static CrossChainOfferSummary produceSummary(Repository repository, ACCT acct, ATStateData atState, Long timestamp) throws DataException {
|
||||
CrossChainTradeData crossChainTradeData = acct.populateTradeData(repository, atState);
|
||||
private static CrossChainOfferSummary produceSummary(Repository repository, ACCT acct, ATStateData atState, CrossChainTradeData crossChainTradeData, Long timestamp) throws DataException {
|
||||
if (crossChainTradeData == null) {
|
||||
crossChainTradeData = acct.populateTradeData(repository, atState);
|
||||
}
|
||||
|
||||
long atStateTimestamp;
|
||||
|
||||
@ -346,9 +349,16 @@ public class TradeOffersWebSocket extends ApiWebSocket implements Listener {
|
||||
|
||||
private static List<CrossChainOfferSummary> produceSummaries(Repository repository, ACCT acct, List<ATStateData> atStates, Long timestamp) throws DataException {
|
||||
List<CrossChainOfferSummary> offerSummaries = new ArrayList<>();
|
||||
for (ATStateData atState : atStates) {
|
||||
CrossChainTradeData crossChainTradeData = acct.populateTradeData(repository, atState);
|
||||
|
||||
for (ATStateData atState : atStates)
|
||||
offerSummaries.add(produceSummary(repository, acct, atState, timestamp));
|
||||
// Ignore trade if it has failed
|
||||
if (TradeBot.getInstance().isFailedTrade(repository, crossChainTradeData)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
offerSummaries.add(produceSummary(repository, acct, atState, crossChainTradeData, timestamp));
|
||||
}
|
||||
|
||||
return offerSummaries;
|
||||
}
|
||||
|
@ -488,6 +488,11 @@ public class ArbitraryDataStorageManager extends Thread {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Settings.getInstance().getStoragePolicy() == StoragePolicy.ALL) {
|
||||
// Using storage policy ALL, so don't limit anything per name
|
||||
return true;
|
||||
}
|
||||
|
||||
if (name == null) {
|
||||
// This transaction doesn't have a name, so fall back to total space limitations
|
||||
return true;
|
||||
|
@ -10,6 +10,7 @@ import org.apache.logging.log4j.Logger;
|
||||
import org.bitcoinj.core.ECKey;
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.api.model.crosschain.TradeBotCreateRequest;
|
||||
import org.qortal.api.resource.TransactionsResource;
|
||||
import org.qortal.controller.Controller;
|
||||
import org.qortal.controller.Synchronizer;
|
||||
import org.qortal.controller.tradebot.AcctTradeBot.ResponseResult;
|
||||
@ -19,6 +20,7 @@ import org.qortal.data.at.ATData;
|
||||
import org.qortal.data.crosschain.CrossChainTradeData;
|
||||
import org.qortal.data.crosschain.TradeBotData;
|
||||
import org.qortal.data.network.TradePresenceData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.event.Event;
|
||||
import org.qortal.event.EventBus;
|
||||
import org.qortal.event.Listener;
|
||||
@ -33,6 +35,7 @@ import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.repository.hsqldb.HSQLDBImportExport;
|
||||
import org.qortal.settings.Settings;
|
||||
import org.qortal.transaction.Transaction;
|
||||
import org.qortal.utils.ByteArray;
|
||||
import org.qortal.utils.NTP;
|
||||
|
||||
@ -113,6 +116,9 @@ public class TradeBot implements Listener {
|
||||
private Map<ByteArray, TradePresenceData> safeAllTradePresencesByPubkey = Collections.emptyMap();
|
||||
private long nextTradePresenceBroadcastTimestamp = 0L;
|
||||
|
||||
private Map<String, Long> failedTrades = new HashMap<>();
|
||||
private Map<String, Long> validTrades = new HashMap<>();
|
||||
|
||||
private TradeBot() {
|
||||
EventBus.INSTANCE.addListener(event -> TradeBot.getInstance().listen(event));
|
||||
}
|
||||
@ -674,6 +680,78 @@ public class TradeBot implements Listener {
|
||||
});
|
||||
}
|
||||
|
||||
/** Removes any trades that have had multiple failures */
|
||||
public List<CrossChainTradeData> removeFailedTrades(Repository repository, List<CrossChainTradeData> crossChainTrades) {
|
||||
Long now = NTP.getTime();
|
||||
if (now == null) {
|
||||
return crossChainTrades;
|
||||
}
|
||||
|
||||
List<CrossChainTradeData> updatedCrossChainTrades = new ArrayList<>(crossChainTrades);
|
||||
int getMaxTradeOfferAttempts = Settings.getInstance().getMaxTradeOfferAttempts();
|
||||
|
||||
for (CrossChainTradeData crossChainTradeData : crossChainTrades) {
|
||||
// We only care about trades in the OFFERING state
|
||||
if (crossChainTradeData.mode != AcctMode.OFFERING) {
|
||||
failedTrades.remove(crossChainTradeData.qortalAtAddress);
|
||||
validTrades.remove(crossChainTradeData.qortalAtAddress);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Return recently cached values if they exist
|
||||
Long failedTimestamp = failedTrades.get(crossChainTradeData.qortalAtAddress);
|
||||
if (failedTimestamp != null && now - failedTimestamp < 60 * 60 * 1000L) {
|
||||
updatedCrossChainTrades.remove(crossChainTradeData);
|
||||
//LOGGER.info("Removing cached failed trade AT {}", crossChainTradeData.qortalAtAddress);
|
||||
continue;
|
||||
}
|
||||
Long validTimestamp = validTrades.get(crossChainTradeData.qortalAtAddress);
|
||||
if (validTimestamp != null && now - validTimestamp < 60 * 60 * 1000L) {
|
||||
//LOGGER.info("NOT removing cached valid trade AT {}", crossChainTradeData.qortalAtAddress);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
List<byte[]> signatures = repository.getTransactionRepository().getSignaturesMatchingCriteria(null, null, null, Arrays.asList(Transaction.TransactionType.MESSAGE), null, null, crossChainTradeData.qortalCreatorTradeAddress, TransactionsResource.ConfirmationStatus.CONFIRMED, null, null, null);
|
||||
if (signatures.size() < getMaxTradeOfferAttempts) {
|
||||
// Less than 3 (or user-specified number of) MESSAGE transactions relate to this trade, so assume it is ok
|
||||
validTrades.put(crossChainTradeData.qortalAtAddress, now);
|
||||
continue;
|
||||
}
|
||||
|
||||
List<TransactionData> transactions = new ArrayList<>(signatures.size());
|
||||
for (byte[] signature : signatures) {
|
||||
transactions.add(repository.getTransactionRepository().fromSignature(signature));
|
||||
}
|
||||
transactions.sort(Transaction.getDataComparator());
|
||||
|
||||
// Get timestamp of the first MESSAGE transaction
|
||||
long firstMessageTimestamp = transactions.get(0).getTimestamp();
|
||||
|
||||
// Treat as failed if first buy attempt was more than 60 mins ago (as it's still in the OFFERING state)
|
||||
boolean isFailed = (now - firstMessageTimestamp > 60*60*1000L);
|
||||
if (isFailed) {
|
||||
failedTrades.put(crossChainTradeData.qortalAtAddress, now);
|
||||
updatedCrossChainTrades.remove(crossChainTradeData);
|
||||
}
|
||||
else {
|
||||
validTrades.put(crossChainTradeData.qortalAtAddress, now);
|
||||
}
|
||||
|
||||
} catch (DataException e) {
|
||||
LOGGER.info("Unable to determine failed state of AT {}", crossChainTradeData.qortalAtAddress);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return updatedCrossChainTrades;
|
||||
}
|
||||
|
||||
public boolean isFailedTrade(Repository repository, CrossChainTradeData crossChainTradeData) {
|
||||
List<CrossChainTradeData> results = removeFailedTrades(repository, Arrays.asList(crossChainTradeData));
|
||||
return results.isEmpty();
|
||||
}
|
||||
|
||||
private long generateExpiry(long timestamp) {
|
||||
return ((timestamp - 1) / EXPIRY_ROUNDING) * EXPIRY_ROUNDING + PRESENCE_LIFETIME;
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorNode;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.data.voting.PollData;
|
||||
import org.qortal.data.voting.VoteOnPollData;
|
||||
import org.qortal.transaction.Transaction.ApprovalStatus;
|
||||
import org.qortal.transaction.Transaction.TransactionType;
|
||||
|
||||
@ -30,7 +31,7 @@ import io.swagger.v3.oas.annotations.media.Schema.AccessMode;
|
||||
@XmlSeeAlso({GenesisTransactionData.class, PaymentTransactionData.class, RegisterNameTransactionData.class, UpdateNameTransactionData.class,
|
||||
SellNameTransactionData.class, CancelSellNameTransactionData.class, BuyNameTransactionData.class,
|
||||
CreatePollTransactionData.class, VoteOnPollTransactionData.class, ArbitraryTransactionData.class,
|
||||
PollData.class,
|
||||
PollData.class, VoteOnPollData.class,
|
||||
IssueAssetTransactionData.class, TransferAssetTransactionData.class,
|
||||
CreateAssetOrderTransactionData.class, CancelAssetOrderTransactionData.class,
|
||||
MultiPaymentTransactionData.class, DeployAtTransactionData.class, MessageTransactionData.class, ATTransactionData.class,
|
||||
|
@ -9,6 +9,11 @@ public class VoteOnPollData {
|
||||
|
||||
// Constructors
|
||||
|
||||
// For JAXB
|
||||
protected VoteOnPollData() {
|
||||
super();
|
||||
}
|
||||
|
||||
public VoteOnPollData(String pollName, byte[] voterPublicKey, int optionIndex) {
|
||||
this.pollName = pollName;
|
||||
this.voterPublicKey = voterPublicKey;
|
||||
@ -21,12 +26,24 @@ public class VoteOnPollData {
|
||||
return this.pollName;
|
||||
}
|
||||
|
||||
public void setPollName(String pollName) {
|
||||
this.pollName = pollName;
|
||||
}
|
||||
|
||||
public byte[] getVoterPublicKey() {
|
||||
return this.voterPublicKey;
|
||||
}
|
||||
|
||||
public void setVoterPublicKey(byte[] voterPublicKey) {
|
||||
this.voterPublicKey = voterPublicKey;
|
||||
}
|
||||
|
||||
public int getOptionIndex() {
|
||||
return this.optionIndex;
|
||||
}
|
||||
|
||||
public void setOptionIndex(int optionIndex) {
|
||||
this.optionIndex = optionIndex;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -14,12 +14,12 @@ public interface NameRepository {
|
||||
|
||||
public boolean reducedNameExists(String reducedName) throws DataException;
|
||||
|
||||
public List<NameData> searchNames(String query, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||
public List<NameData> searchNames(String query, boolean prefixOnly, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||
|
||||
public List<NameData> getAllNames(Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||
public List<NameData> getAllNames(Long after, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||
|
||||
public default List<NameData> getAllNames() throws DataException {
|
||||
return getAllNames(null, null, null);
|
||||
return getAllNames(null, null, null, null);
|
||||
}
|
||||
|
||||
public List<NameData> getNamesForSale(Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||
|
@ -103,7 +103,7 @@ public class HSQLDBNameRepository implements NameRepository {
|
||||
}
|
||||
}
|
||||
|
||||
public List<NameData> searchNames(String query, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||
public List<NameData> searchNames(String query, boolean prefixOnly, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||
StringBuilder sql = new StringBuilder(512);
|
||||
List<Object> bindParams = new ArrayList<>();
|
||||
|
||||
@ -111,7 +111,10 @@ public class HSQLDBNameRepository implements NameRepository {
|
||||
+ "is_for_sale, sale_price, reference, creation_group_id FROM Names "
|
||||
+ "WHERE LCASE(name) LIKE ? ORDER BY name");
|
||||
|
||||
bindParams.add(String.format("%%%s%%", query.toLowerCase()));
|
||||
// Search anywhere in the name, unless "prefixOnly" has been requested
|
||||
// Note that without prefixOnly it will bypass any indexes
|
||||
String queryWildcard = prefixOnly ? String.format("%s%%", query.toLowerCase()) : String.format("%%%s%%", query.toLowerCase());
|
||||
bindParams.add(queryWildcard);
|
||||
|
||||
if (reverse != null && reverse)
|
||||
sql.append(" DESC");
|
||||
@ -155,11 +158,20 @@ public class HSQLDBNameRepository implements NameRepository {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<NameData> getAllNames(Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||
public List<NameData> getAllNames(Long after, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||
StringBuilder sql = new StringBuilder(256);
|
||||
List<Object> bindParams = new ArrayList<>();
|
||||
|
||||
sql.append("SELECT name, reduced_name, owner, data, registered_when, updated_when, "
|
||||
+ "is_for_sale, sale_price, reference, creation_group_id FROM Names ORDER BY name");
|
||||
+ "is_for_sale, sale_price, reference, creation_group_id FROM Names");
|
||||
|
||||
if (after != null) {
|
||||
sql.append(" WHERE registered_when > ? OR updated_when > ?");
|
||||
bindParams.add(after);
|
||||
bindParams.add(after);
|
||||
}
|
||||
|
||||
sql.append(" ORDER BY name");
|
||||
|
||||
if (reverse != null && reverse)
|
||||
sql.append(" DESC");
|
||||
@ -168,7 +180,7 @@ public class HSQLDBNameRepository implements NameRepository {
|
||||
|
||||
List<NameData> names = new ArrayList<>();
|
||||
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(sql.toString())) {
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(sql.toString(), bindParams.toArray())) {
|
||||
if (resultSet == null)
|
||||
return names;
|
||||
|
||||
|
@ -253,6 +253,9 @@ public class Settings {
|
||||
/** Whether to show SysTray pop-up notifications when trade-bot entries change state */
|
||||
private boolean tradebotSystrayEnabled = false;
|
||||
|
||||
/** Maximum buy attempts for each trade offer before it is considered failed, and hidden from the list */
|
||||
private int maxTradeOfferAttempts = 3;
|
||||
|
||||
/** Wallets path - used for storing encrypted wallet caches for coins that require them */
|
||||
private String walletsPath = "wallets";
|
||||
|
||||
@ -771,6 +774,10 @@ public class Settings {
|
||||
return this.pirateChainNet;
|
||||
}
|
||||
|
||||
public int getMaxTradeOfferAttempts() {
|
||||
return this.maxTradeOfferAttempts;
|
||||
}
|
||||
|
||||
public String getWalletsPath() {
|
||||
return this.walletsPath;
|
||||
}
|
||||
|
@ -169,7 +169,7 @@ window.addEventListener("message", (event) => {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Core received event: " + JSON.stringify(event.data));
|
||||
console.log("Core received action: " + JSON.stringify(event.data.action));
|
||||
|
||||
let url;
|
||||
let data = event.data;
|
||||
@ -181,6 +181,15 @@ window.addEventListener("message", (event) => {
|
||||
case "GET_ACCOUNT_NAMES":
|
||||
return httpGetAsyncWithEvent(event, "/names/address/" + data.address);
|
||||
|
||||
case "SEARCH_NAMES":
|
||||
url = "/names/search?";
|
||||
if (data.query != null) url = url.concat("&query=" + data.query);
|
||||
if (data.prefix != null) url = url.concat("&prefix=" + new Boolean(data.prefix).toString());
|
||||
if (data.limit != null) url = url.concat("&limit=" + data.limit);
|
||||
if (data.offset != null) url = url.concat("&offset=" + data.offset);
|
||||
if (data.reverse != null) url = url.concat("&reverse=" + new Boolean(data.reverse).toString());
|
||||
return httpGetAsyncWithEvent(event, url);
|
||||
|
||||
case "GET_NAME_DATA":
|
||||
return httpGetAsyncWithEvent(event, "/names/" + data.name);
|
||||
|
||||
|
@ -37,8 +37,8 @@ public class NamesApiTests extends ApiCommon {
|
||||
|
||||
@Test
|
||||
public void testGetAllNames() {
|
||||
assertNotNull(this.namesResource.getAllNames(null, null, null));
|
||||
assertNotNull(this.namesResource.getAllNames(1, 1, true));
|
||||
assertNotNull(this.namesResource.getAllNames(null, null, null, null));
|
||||
assertNotNull(this.namesResource.getAllNames(1L, 1, 1, true));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
Loading…
x
Reference in New Issue
Block a user