Merge branch 'master' into arbitrary-resources-cache

This commit is contained in:
CalDescent 2023-05-12 11:50:24 +01:00
commit 21f01226e9
16 changed files with 362 additions and 27 deletions

View File

@ -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({

View File

@ -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>

View 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;
}
}
}

View File

@ -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(

View File

@ -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) {

View File

@ -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(

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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,

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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;
}

View File

@ -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);

View File

@ -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