mirror of
https://github.com/Qortal/qortal.git
synced 2025-07-30 05:31:23 +00:00
Compare commits
43 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
9574100a08 | ||
|
528583fe38 | ||
|
33cfd02c49 | ||
|
94d3664cb0 | ||
|
f5c8dfe766 | ||
|
f7e1f2fca8 | ||
|
811b647c88 | ||
|
3215bb638d | ||
|
8ae7a1d65b | ||
|
29dcd53002 | ||
|
62908f867a | ||
|
5f86ecafd9 | ||
|
fe999a11f4 | ||
|
c14fca5660 | ||
|
fd8d720946 | ||
|
d628b3ab2a | ||
|
5928b54a33 | ||
|
91dfc5efd0 | ||
|
1343a88ee3 | ||
|
7f7b02f003 | ||
|
5650923805 | ||
|
5fb2640a3a | ||
|
66c91fd365 | ||
|
bfc03db6a9 | ||
|
a4bb445f3e | ||
|
27afcf12bf | ||
|
eda6ab5701 | ||
|
13da0e8a7a | ||
|
d260c0a9a9 | ||
|
655073c524 | ||
|
c8f3b6918f | ||
|
1565a461ac | ||
|
1f30bef4f8 | ||
|
6f0479c4fc | ||
|
b967800a3e | ||
|
0b50f965cc | ||
|
90f7cee058 | ||
|
947b523e61 | ||
|
95d72866e9 | ||
|
aea1cc62c8 | ||
|
c763445e6e | ||
|
1e10bcf3b0 | ||
|
8f847d3689 |
@@ -576,14 +576,15 @@ let res = await qortalRequest({
|
||||
```
|
||||
|
||||
### Send foreign coin to address
|
||||
_Requires user approval_
|
||||
_Requires user approval_<br />
|
||||
Note: default fees can be found [here](https://github.com/Qortal/qortal-ui/blob/master/plugins/plugins/core/qdn/browser/browser.src.js#L205-L209).
|
||||
```
|
||||
let res = await qortalRequest({
|
||||
action: "SEND_COIN",
|
||||
coin: "LTC",
|
||||
destinationAddress: "LSdTvMHRm8sScqwCi6x9wzYQae8JeZhx6y",
|
||||
amount: 1.00000000, // 1 LTC
|
||||
fee: 0.00000020 // fee per byte
|
||||
fee: 0.00000020 // Optional fee per byte (default fee used if omitted, recommended) - not used for QORT or ARRR
|
||||
});
|
||||
```
|
||||
|
||||
|
2
pom.xml
2
pom.xml
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.qortal</groupId>
|
||||
<artifactId>qortal</artifactId>
|
||||
<version>4.1.0</version>
|
||||
<version>4.2.2</version>
|
||||
<packaging>jar</packaging>
|
||||
<properties>
|
||||
<skipTests>true</skipTests>
|
||||
|
@@ -96,7 +96,7 @@ public class ApiService {
|
||||
throw new RuntimeException("Failed to start SSL API due to broken keystore");
|
||||
|
||||
// BouncyCastle-specific SSLContext build
|
||||
SSLContext sslContext = SSLContext.getInstance("TLS", "BCJSSE");
|
||||
SSLContext sslContext = SSLContext.getInstance("TLSv1.3", "BCJSSE");
|
||||
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("PKIX", "BCJSSE");
|
||||
|
||||
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType(), "BC");
|
||||
|
173
src/main/java/org/qortal/api/DevProxyService.java
Normal file
173
src/main/java/org/qortal/api/DevProxyService.java
Normal file
@@ -0,0 +1,173 @@
|
||||
package org.qortal.api;
|
||||
|
||||
import io.swagger.v3.jaxrs2.integration.resources.OpenApiResource;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.rewrite.handler.RewriteHandler;
|
||||
import org.eclipse.jetty.server.*;
|
||||
import org.eclipse.jetty.server.handler.ErrorHandler;
|
||||
import org.eclipse.jetty.server.handler.InetAccessHandler;
|
||||
import org.eclipse.jetty.servlet.FilterHolder;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.eclipse.jetty.servlets.CrossOriginFilter;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.glassfish.jersey.server.ResourceConfig;
|
||||
import org.glassfish.jersey.servlet.ServletContainer;
|
||||
import org.qortal.api.resource.AnnotationPostProcessor;
|
||||
import org.qortal.api.resource.ApiDefinition;
|
||||
import org.qortal.network.Network;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.settings.Settings;
|
||||
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import java.io.InputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.KeyStore;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class DevProxyService {
|
||||
|
||||
private static DevProxyService instance;
|
||||
|
||||
private final ResourceConfig config;
|
||||
private Server server;
|
||||
|
||||
private DevProxyService() {
|
||||
this.config = new ResourceConfig();
|
||||
this.config.packages("org.qortal.api.proxy.resource", "org.qortal.api.resource");
|
||||
this.config.register(OpenApiResource.class);
|
||||
this.config.register(ApiDefinition.class);
|
||||
this.config.register(AnnotationPostProcessor.class);
|
||||
}
|
||||
|
||||
public static DevProxyService getInstance() {
|
||||
if (instance == null)
|
||||
instance = new DevProxyService();
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
public Iterable<Class<?>> getResources() {
|
||||
return this.config.getClasses();
|
||||
}
|
||||
|
||||
public void start() throws DataException {
|
||||
try {
|
||||
// Create API server
|
||||
|
||||
// SSL support if requested
|
||||
String keystorePathname = Settings.getInstance().getSslKeystorePathname();
|
||||
String keystorePassword = Settings.getInstance().getSslKeystorePassword();
|
||||
|
||||
if (keystorePathname != null && keystorePassword != null) {
|
||||
// SSL version
|
||||
if (!Files.isReadable(Path.of(keystorePathname)))
|
||||
throw new RuntimeException("Failed to start SSL API due to broken keystore");
|
||||
|
||||
// BouncyCastle-specific SSLContext build
|
||||
SSLContext sslContext = SSLContext.getInstance("TLS", "BCJSSE");
|
||||
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("PKIX", "BCJSSE");
|
||||
|
||||
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType(), "BC");
|
||||
|
||||
try (InputStream keystoreStream = Files.newInputStream(Paths.get(keystorePathname))) {
|
||||
keyStore.load(keystoreStream, keystorePassword.toCharArray());
|
||||
}
|
||||
|
||||
keyManagerFactory.init(keyStore, keystorePassword.toCharArray());
|
||||
sslContext.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom());
|
||||
|
||||
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
|
||||
sslContextFactory.setSslContext(sslContext);
|
||||
|
||||
this.server = new Server();
|
||||
|
||||
HttpConfiguration httpConfig = new HttpConfiguration();
|
||||
httpConfig.setSecureScheme("https");
|
||||
httpConfig.setSecurePort(Settings.getInstance().getDevProxyPort());
|
||||
|
||||
SecureRequestCustomizer src = new SecureRequestCustomizer();
|
||||
httpConfig.addCustomizer(src);
|
||||
|
||||
HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory(httpConfig);
|
||||
SslConnectionFactory sslConnectionFactory = new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString());
|
||||
|
||||
ServerConnector portUnifiedConnector = new ServerConnector(this.server,
|
||||
new DetectorConnectionFactory(sslConnectionFactory),
|
||||
httpConnectionFactory);
|
||||
portUnifiedConnector.setHost(Network.getInstance().getBindAddress());
|
||||
portUnifiedConnector.setPort(Settings.getInstance().getDevProxyPort());
|
||||
|
||||
this.server.addConnector(portUnifiedConnector);
|
||||
} else {
|
||||
// Non-SSL
|
||||
InetAddress bindAddr = InetAddress.getByName(Network.getInstance().getBindAddress());
|
||||
InetSocketAddress endpoint = new InetSocketAddress(bindAddr, Settings.getInstance().getDevProxyPort());
|
||||
this.server = new Server(endpoint);
|
||||
}
|
||||
|
||||
// Error handler
|
||||
ErrorHandler errorHandler = new ApiErrorHandler();
|
||||
this.server.setErrorHandler(errorHandler);
|
||||
|
||||
// Request logging
|
||||
if (Settings.getInstance().isDevProxyLoggingEnabled()) {
|
||||
RequestLogWriter logWriter = new RequestLogWriter("devproxy-requests.log");
|
||||
logWriter.setAppend(true);
|
||||
logWriter.setTimeZone("UTC");
|
||||
RequestLog requestLog = new CustomRequestLog(logWriter, CustomRequestLog.EXTENDED_NCSA_FORMAT);
|
||||
this.server.setRequestLog(requestLog);
|
||||
}
|
||||
|
||||
// Access handler (currently no whitelist is used)
|
||||
InetAccessHandler accessHandler = new InetAccessHandler();
|
||||
this.server.setHandler(accessHandler);
|
||||
|
||||
// URL rewriting
|
||||
RewriteHandler rewriteHandler = new RewriteHandler();
|
||||
accessHandler.setHandler(rewriteHandler);
|
||||
|
||||
// Context
|
||||
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.NO_SESSIONS);
|
||||
context.setContextPath("/");
|
||||
rewriteHandler.setHandler(context);
|
||||
|
||||
// Cross-origin resource sharing
|
||||
FilterHolder corsFilterHolder = new FilterHolder(CrossOriginFilter.class);
|
||||
corsFilterHolder.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM, "*");
|
||||
corsFilterHolder.setInitParameter(CrossOriginFilter.ALLOWED_METHODS_PARAM, "GET, POST, DELETE");
|
||||
corsFilterHolder.setInitParameter(CrossOriginFilter.CHAIN_PREFLIGHT_PARAM, "false");
|
||||
context.addFilter(corsFilterHolder, "/*", null);
|
||||
|
||||
// API servlet
|
||||
ServletContainer container = new ServletContainer(this.config);
|
||||
ServletHolder apiServlet = new ServletHolder(container);
|
||||
apiServlet.setInitOrder(1);
|
||||
context.addServlet(apiServlet, "/*");
|
||||
|
||||
// Start server
|
||||
this.server.start();
|
||||
} catch (Exception e) {
|
||||
// Failed to start
|
||||
throw new DataException("Failed to start developer proxy", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
try {
|
||||
// Stop server
|
||||
this.server.stop();
|
||||
} catch (Exception e) {
|
||||
// Failed to stop
|
||||
}
|
||||
|
||||
this.server = null;
|
||||
instance = null;
|
||||
}
|
||||
|
||||
}
|
@@ -69,7 +69,7 @@ public class DomainMapService {
|
||||
throw new RuntimeException("Failed to start SSL API due to broken keystore");
|
||||
|
||||
// BouncyCastle-specific SSLContext build
|
||||
SSLContext sslContext = SSLContext.getInstance("TLS", "BCJSSE");
|
||||
SSLContext sslContext = SSLContext.getInstance("TLSv1.3", "BCJSSE");
|
||||
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("PKIX", "BCJSSE");
|
||||
|
||||
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType(), "BC");
|
||||
|
@@ -69,7 +69,7 @@ public class GatewayService {
|
||||
throw new RuntimeException("Failed to start SSL API due to broken keystore");
|
||||
|
||||
// BouncyCastle-specific SSLContext build
|
||||
SSLContext sslContext = SSLContext.getInstance("TLS", "BCJSSE");
|
||||
SSLContext sslContext = SSLContext.getInstance("TLSv1.3", "BCJSSE");
|
||||
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("PKIX", "BCJSSE");
|
||||
|
||||
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType(), "BC");
|
||||
|
@@ -24,11 +24,11 @@ public class HTMLParser {
|
||||
private String theme;
|
||||
private boolean usingCustomRouting;
|
||||
|
||||
public HTMLParser(String resourceId, String inPath, String prefix, boolean usePrefix, byte[] data,
|
||||
public HTMLParser(String resourceId, String inPath, String prefix, boolean includeResourceIdInPrefix, byte[] data,
|
||||
String qdnContext, Service service, String identifier, String theme, boolean usingCustomRouting) {
|
||||
String inPathWithoutFilename = inPath.contains("/") ? inPath.substring(0, inPath.lastIndexOf('/')) : "";
|
||||
this.qdnBase = usePrefix ? String.format("%s/%s", prefix, resourceId) : "";
|
||||
this.qdnBaseWithPath = usePrefix ? String.format("%s/%s%s", prefix, resourceId, inPathWithoutFilename) : "";
|
||||
String inPathWithoutFilename = inPath.contains("/") ? inPath.substring(0, inPath.lastIndexOf('/')) : String.format("/%s",inPath);
|
||||
this.qdnBase = includeResourceIdInPrefix ? String.format("%s/%s", prefix, resourceId) : prefix;
|
||||
this.qdnBaseWithPath = includeResourceIdInPrefix ? String.format("%s/%s%s", prefix, resourceId, inPathWithoutFilename) : String.format("%s%s", prefix, inPathWithoutFilename);
|
||||
this.data = data;
|
||||
this.qdnContext = qdnContext;
|
||||
this.resourceId = resourceId;
|
||||
@@ -82,7 +82,7 @@ public class HTMLParser {
|
||||
}
|
||||
|
||||
public static boolean isHtmlFile(String path) {
|
||||
if (path.endsWith(".html") || path.endsWith(".htm")) {
|
||||
if (path.endsWith(".html") || path.endsWith(".htm") || path.equals("")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@@ -48,10 +48,10 @@ public class DomainMapResource {
|
||||
}
|
||||
|
||||
private HttpServletResponse get(String resourceId, ResourceIdType resourceIdType, Service service, String identifier,
|
||||
String inPath, String secret58, String prefix, boolean usePrefix, boolean async) {
|
||||
String inPath, String secret58, String prefix, boolean includeResourceIdInPrefix, boolean async) {
|
||||
|
||||
ArbitraryDataRenderer renderer = new ArbitraryDataRenderer(resourceId, resourceIdType, service, identifier, inPath,
|
||||
secret58, prefix, usePrefix, async, "domainMap", request, response, context);
|
||||
secret58, prefix, includeResourceIdInPrefix, async, "domainMap", request, response, context);
|
||||
return renderer.render();
|
||||
}
|
||||
|
||||
|
@@ -90,7 +90,7 @@ public class GatewayResource {
|
||||
}
|
||||
|
||||
|
||||
private HttpServletResponse parsePath(String inPath, String qdnContext, String secret58, boolean usePrefix, boolean async) {
|
||||
private HttpServletResponse parsePath(String inPath, String qdnContext, String secret58, boolean includeResourceIdInPrefix, boolean async) {
|
||||
|
||||
if (inPath == null || inPath.equals("")) {
|
||||
// Assume not a real file
|
||||
@@ -157,7 +157,7 @@ public class GatewayResource {
|
||||
}
|
||||
|
||||
ArbitraryDataRenderer renderer = new ArbitraryDataRenderer(name, ResourceIdType.NAME, service, identifier, outPath,
|
||||
secret58, prefix, usePrefix, async, qdnContext, request, response, context);
|
||||
secret58, prefix, includeResourceIdInPrefix, async, qdnContext, request, response, context);
|
||||
return renderer.render();
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,164 @@
|
||||
package org.qortal.api.proxy.resource;
|
||||
|
||||
import org.qortal.api.ApiError;
|
||||
import org.qortal.api.ApiExceptionFactory;
|
||||
import org.qortal.api.HTMLParser;
|
||||
import org.qortal.arbitrary.misc.Service;
|
||||
import org.qortal.controller.DevProxyManager;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.core.Context;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.ConnectException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.ProtocolException;
|
||||
import java.net.URL;
|
||||
import java.util.Enumeration;
|
||||
|
||||
|
||||
@Path("/")
|
||||
public class DevProxyServerResource {
|
||||
|
||||
@Context HttpServletRequest request;
|
||||
@Context HttpServletResponse response;
|
||||
@Context ServletContext context;
|
||||
|
||||
|
||||
@GET
|
||||
public HttpServletResponse getProxyIndex() {
|
||||
return this.proxy("/");
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{path:.*}")
|
||||
public HttpServletResponse getProxyPath(@PathParam("path") String inPath) {
|
||||
return this.proxy(inPath);
|
||||
}
|
||||
|
||||
private HttpServletResponse proxy(String inPath) {
|
||||
try {
|
||||
String source = DevProxyManager.getInstance().getSourceHostAndPort();
|
||||
|
||||
if (!inPath.startsWith("/")) {
|
||||
inPath = "/" + inPath;
|
||||
}
|
||||
|
||||
String queryString = request.getQueryString() != null ? "?" + request.getQueryString() : "";
|
||||
|
||||
// Open URL
|
||||
URL url = new URL(String.format("http://%s%s%s", source, inPath, queryString));
|
||||
HttpURLConnection con = (HttpURLConnection) url.openConnection();
|
||||
|
||||
// Proxy the request data
|
||||
this.proxyRequestToConnection(request, con);
|
||||
|
||||
try {
|
||||
// Make the request and proxy the response code
|
||||
response.setStatus(con.getResponseCode());
|
||||
}
|
||||
catch (ConnectException e) {
|
||||
|
||||
// Tey converting localhost / 127.0.0.1 to IPv6 [::1]
|
||||
if (source.startsWith("localhost") || source.startsWith("127.0.0.1")) {
|
||||
int port = 80;
|
||||
String[] parts = source.split(":");
|
||||
if (parts.length > 1) {
|
||||
port = Integer.parseInt(parts[1]);
|
||||
}
|
||||
source = String.format("[::1]:%d", port);
|
||||
}
|
||||
|
||||
// Retry connection
|
||||
url = new URL(String.format("http://%s%s%s", source, inPath, queryString));
|
||||
con = (HttpURLConnection) url.openConnection();
|
||||
this.proxyRequestToConnection(request, con);
|
||||
response.setStatus(con.getResponseCode());
|
||||
}
|
||||
|
||||
// Proxy the response data back to the caller
|
||||
this.proxyConnectionToResponse(con, response, inPath);
|
||||
|
||||
} catch (IOException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, e.getMessage());
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private void proxyRequestToConnection(HttpServletRequest request, HttpURLConnection con) throws ProtocolException {
|
||||
// Proxy the request method
|
||||
con.setRequestMethod(request.getMethod());
|
||||
|
||||
// Proxy the request headers
|
||||
Enumeration<String> headerNames = request.getHeaderNames();
|
||||
while (headerNames.hasMoreElements()) {
|
||||
String headerName = headerNames.nextElement();
|
||||
String headerValue = request.getHeader(headerName);
|
||||
con.setRequestProperty(headerName, headerValue);
|
||||
}
|
||||
|
||||
// TODO: proxy any POST parameters from "request" to "con"
|
||||
}
|
||||
|
||||
private void proxyConnectionToResponse(HttpURLConnection con, HttpServletResponse response, String inPath) throws IOException {
|
||||
// Proxy the response headers
|
||||
for (int i = 0; ; i++) {
|
||||
String headerKey = con.getHeaderFieldKey(i);
|
||||
String headerValue = con.getHeaderField(i);
|
||||
if (headerKey != null && headerValue != null) {
|
||||
response.addHeader(headerKey, headerValue);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Read the response body
|
||||
InputStream inputStream = con.getInputStream();
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
byte[] buffer = new byte[4096];
|
||||
int bytesRead;
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
outputStream.write(buffer, 0, bytesRead);
|
||||
}
|
||||
byte[] data = outputStream.toByteArray(); // TODO: limit file size that can be read into memory
|
||||
|
||||
// Close the streams
|
||||
outputStream.close();
|
||||
inputStream.close();
|
||||
|
||||
// Extract filename
|
||||
String filename = "";
|
||||
if (inPath.contains("/")) {
|
||||
String[] parts = inPath.split("/");
|
||||
if (parts.length > 0) {
|
||||
filename = parts[parts.length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
// Parse and modify output if needed
|
||||
if (HTMLParser.isHtmlFile(filename)) {
|
||||
// HTML file - needs to be parsed
|
||||
HTMLParser htmlParser = new HTMLParser("", inPath, "", false, data, "proxy", Service.APP, null, "light", true);
|
||||
htmlParser.addAdditionalHeaderTags();
|
||||
response.addHeader("Content-Security-Policy", "default-src 'self' 'unsafe-inline' 'unsafe-eval'; media-src 'self' data: blob:; img-src 'self' data: blob:; connect-src 'self' ws:; font-src 'self' data:;");
|
||||
response.setContentType(con.getContentType());
|
||||
response.setContentLength(htmlParser.getData().length);
|
||||
response.getOutputStream().write(htmlParser.getData());
|
||||
}
|
||||
else {
|
||||
// Regular file - can be streamed directly
|
||||
response.addHeader("Content-Security-Policy", "default-src 'self'");
|
||||
response.setContentType(con.getContentType());
|
||||
response.setContentLength(data.length);
|
||||
response.getOutputStream().write(data);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
96
src/main/java/org/qortal/api/resource/DeveloperResource.java
Normal file
96
src/main/java/org/qortal/api/resource/DeveloperResource.java
Normal file
@@ -0,0 +1,96 @@
|
||||
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.controller.DevProxyManager;
|
||||
import org.qortal.repository.DataException;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
|
||||
|
||||
@Path("/developer")
|
||||
@Tag(name = "Developer Tools")
|
||||
public class DeveloperResource {
|
||||
|
||||
@Context HttpServletRequest request;
|
||||
@Context HttpServletResponse response;
|
||||
@Context ServletContext context;
|
||||
|
||||
|
||||
@POST
|
||||
@Path("/proxy/start")
|
||||
@Operation(
|
||||
summary = "Start proxy server, for real time QDN app/website development",
|
||||
requestBody = @RequestBody(
|
||||
description = "Host and port of source webserver to be proxied",
|
||||
required = true,
|
||||
content = @Content(
|
||||
mediaType = MediaType.TEXT_PLAIN,
|
||||
schema = @Schema(
|
||||
type = "string",
|
||||
example = "127.0.0.1:5173"
|
||||
)
|
||||
)
|
||||
),
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
description = "Port number of running server",
|
||||
content = @Content(
|
||||
mediaType = MediaType.TEXT_PLAIN,
|
||||
schema = @Schema(
|
||||
type = "number"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.INVALID_CRITERIA})
|
||||
public Integer startProxy(String sourceHostAndPort) {
|
||||
// TODO: API key
|
||||
DevProxyManager devProxyManager = DevProxyManager.getInstance();
|
||||
try {
|
||||
devProxyManager.setSourceHostAndPort(sourceHostAndPort);
|
||||
devProxyManager.start();
|
||||
return devProxyManager.getPort();
|
||||
|
||||
} catch (DataException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/proxy/stop")
|
||||
@Operation(
|
||||
summary = "Stop proxy server",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
description = "true if stopped",
|
||||
content = @Content(
|
||||
mediaType = MediaType.TEXT_PLAIN,
|
||||
schema = @Schema(
|
||||
type = "boolean"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
public boolean stopProxy() {
|
||||
DevProxyManager devProxyManager = DevProxyManager.getInstance();
|
||||
devProxyManager.stop();
|
||||
return !devProxyManager.isRunning();
|
||||
}
|
||||
|
||||
}
|
@@ -157,10 +157,10 @@ public class RenderResource {
|
||||
|
||||
|
||||
private HttpServletResponse get(String resourceId, ResourceIdType resourceIdType, Service service, String identifier,
|
||||
String inPath, String secret58, String prefix, boolean usePrefix, boolean async, String theme) {
|
||||
String inPath, String secret58, String prefix, boolean includeResourceIdInPrefix, boolean async, String theme) {
|
||||
|
||||
ArbitraryDataRenderer renderer = new ArbitraryDataRenderer(resourceId, resourceIdType, service, identifier, inPath,
|
||||
secret58, prefix, usePrefix, async, "render", request, response, context);
|
||||
secret58, prefix, includeResourceIdInPrefix, async, "render", request, response, context);
|
||||
|
||||
if (theme != null) {
|
||||
renderer.setTheme(theme);
|
||||
|
@@ -40,7 +40,7 @@ public class ArbitraryDataRenderer {
|
||||
private String inPath;
|
||||
private final String secret58;
|
||||
private final String prefix;
|
||||
private final boolean usePrefix;
|
||||
private final boolean includeResourceIdInPrefix;
|
||||
private final boolean async;
|
||||
private final String qdnContext;
|
||||
private final HttpServletRequest request;
|
||||
@@ -48,7 +48,7 @@ public class ArbitraryDataRenderer {
|
||||
private final ServletContext context;
|
||||
|
||||
public ArbitraryDataRenderer(String resourceId, ResourceIdType resourceIdType, Service service, String identifier,
|
||||
String inPath, String secret58, String prefix, boolean usePrefix, boolean async, String qdnContext,
|
||||
String inPath, String secret58, String prefix, boolean includeResourceIdInPrefix, boolean async, String qdnContext,
|
||||
HttpServletRequest request, HttpServletResponse response, ServletContext context) {
|
||||
|
||||
this.resourceId = resourceId;
|
||||
@@ -58,7 +58,7 @@ public class ArbitraryDataRenderer {
|
||||
this.inPath = inPath;
|
||||
this.secret58 = secret58;
|
||||
this.prefix = prefix;
|
||||
this.usePrefix = usePrefix;
|
||||
this.includeResourceIdInPrefix = includeResourceIdInPrefix;
|
||||
this.async = async;
|
||||
this.qdnContext = qdnContext;
|
||||
this.request = request;
|
||||
@@ -159,7 +159,7 @@ public class ArbitraryDataRenderer {
|
||||
if (HTMLParser.isHtmlFile(filename)) {
|
||||
// HTML file - needs to be parsed
|
||||
byte[] data = Files.readAllBytes(filePath); // TODO: limit file size that can be read into memory
|
||||
HTMLParser htmlParser = new HTMLParser(resourceId, inPath, prefix, usePrefix, data, qdnContext, service, identifier, theme, usingCustomRouting);
|
||||
HTMLParser htmlParser = new HTMLParser(resourceId, inPath, prefix, includeResourceIdInPrefix, data, qdnContext, service, identifier, theme, usingCustomRouting);
|
||||
htmlParser.addAdditionalHeaderTags();
|
||||
response.addHeader("Content-Security-Policy", "default-src 'self' 'unsafe-inline' 'unsafe-eval'; media-src 'self' data: blob:; img-src 'self' data: blob:;");
|
||||
response.setContentType(context.getMimeType(filename));
|
||||
|
@@ -186,6 +186,7 @@ public enum Service {
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private static final String encryptedDataPrefix = "qortalEncryptedData";
|
||||
private static final String encryptedGroupDataPrefix = "qortalGroupEncryptedData";
|
||||
|
||||
Service(int value, boolean requiresValidation, Long maxSize, boolean single, boolean isPrivate, List<String> requiredKeys) {
|
||||
this.value = value;
|
||||
@@ -221,10 +222,10 @@ public enum Service {
|
||||
// Validate private data for single file resources
|
||||
if (this.single) {
|
||||
String dataString = new String(data, StandardCharsets.UTF_8);
|
||||
if (this.isPrivate && !dataString.startsWith(encryptedDataPrefix)) {
|
||||
if (this.isPrivate && !dataString.startsWith(encryptedDataPrefix) && !dataString.startsWith(encryptedGroupDataPrefix)) {
|
||||
return ValidationResult.DATA_NOT_ENCRYPTED;
|
||||
}
|
||||
if (!this.isPrivate && dataString.startsWith(encryptedDataPrefix)) {
|
||||
if (!this.isPrivate && (dataString.startsWith(encryptedDataPrefix) || dataString.startsWith(encryptedGroupDataPrefix))) {
|
||||
return ValidationResult.DATA_ENCRYPTED;
|
||||
}
|
||||
}
|
||||
|
@@ -380,9 +380,13 @@ public class BlockMinter extends Thread {
|
||||
parentSignatureForLastLowWeightBlock = null;
|
||||
timeOfLastLowWeightBlock = null;
|
||||
|
||||
Long unconfirmedStartTime = NTP.getTime();
|
||||
|
||||
// Add unconfirmed transactions
|
||||
addUnconfirmedTransactions(repository, newBlock);
|
||||
|
||||
LOGGER.info(String.format("Adding %d unconfirmed transactions took %d ms", newBlock.getTransactions().size(), (NTP.getTime()-unconfirmedStartTime)));
|
||||
|
||||
// Sign to create block's signature
|
||||
newBlock.sign();
|
||||
|
||||
@@ -484,6 +488,9 @@ public class BlockMinter extends Thread {
|
||||
// Sign to create block's signature, needed by Block.isValid()
|
||||
newBlock.sign();
|
||||
|
||||
// User-defined limit per block
|
||||
int limit = Settings.getInstance().getMaxTransactionsPerBlock();
|
||||
|
||||
// Attempt to add transactions until block is full, or we run out
|
||||
// If a transaction makes the block invalid then skip it and it'll either expire or be in next block.
|
||||
for (TransactionData transactionData : unconfirmedTransactions) {
|
||||
@@ -496,6 +503,12 @@ public class BlockMinter extends Thread {
|
||||
LOGGER.debug(() -> String.format("Skipping invalid transaction %s during block minting", Base58.encode(transactionData.getSignature())));
|
||||
newBlock.deleteTransaction(transactionData);
|
||||
}
|
||||
|
||||
// User-defined limit per block
|
||||
List<Transaction> transactions = newBlock.getTransactions();
|
||||
if (transactions != null && transactions.size() >= limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -444,6 +444,7 @@ public class Controller extends Thread {
|
||||
if (RepositoryManager.needsTransactionSequenceRebuild(repository)) {
|
||||
// Don't allow the node to start if transaction sequences haven't been built yet
|
||||
// This is needed to handle a case when bootstrapping
|
||||
LOGGER.error("Database upgrade needed. Please restart the core to complete the upgrade process.");
|
||||
Gui.getInstance().fatalError("Database upgrade needed", "Please restart the core to complete the upgrade process.");
|
||||
return;
|
||||
}
|
||||
@@ -1277,13 +1278,6 @@ public class Controller extends Thread {
|
||||
TransactionImporter.getInstance().onNetworkTransactionSignaturesMessage(peer, message);
|
||||
break;
|
||||
|
||||
case GET_ONLINE_ACCOUNTS:
|
||||
case ONLINE_ACCOUNTS:
|
||||
case GET_ONLINE_ACCOUNTS_V2:
|
||||
case ONLINE_ACCOUNTS_V2:
|
||||
// No longer supported - to be eventually removed
|
||||
break;
|
||||
|
||||
case GET_ONLINE_ACCOUNTS_V3:
|
||||
OnlineAccountsManager.getInstance().onNetworkGetOnlineAccountsV3Message(peer, message);
|
||||
break;
|
||||
|
74
src/main/java/org/qortal/controller/DevProxyManager.java
Normal file
74
src/main/java/org/qortal/controller/DevProxyManager.java
Normal file
@@ -0,0 +1,74 @@
|
||||
package org.qortal.controller;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.qortal.api.DevProxyService;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.settings.Settings;
|
||||
|
||||
public class DevProxyManager {
|
||||
|
||||
protected static final Logger LOGGER = LogManager.getLogger(DevProxyManager.class);
|
||||
|
||||
private static DevProxyManager instance;
|
||||
|
||||
private boolean running = false;
|
||||
|
||||
private String sourceHostAndPort = "127.0.0.1:5173"; // Default for React/Vite
|
||||
|
||||
private DevProxyManager() {
|
||||
|
||||
}
|
||||
|
||||
public static DevProxyManager getInstance() {
|
||||
if (instance == null)
|
||||
instance = new DevProxyManager();
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void start() throws DataException {
|
||||
synchronized(this) {
|
||||
if (this.running) {
|
||||
// Already running
|
||||
return;
|
||||
}
|
||||
|
||||
LOGGER.info(String.format("Starting developer proxy service on port %d", Settings.getInstance().getDevProxyPort()));
|
||||
DevProxyService devProxyService = DevProxyService.getInstance();
|
||||
devProxyService.start();
|
||||
this.running = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
synchronized(this) {
|
||||
if (!this.running) {
|
||||
// Not running
|
||||
return;
|
||||
}
|
||||
|
||||
LOGGER.info(String.format("Shutting down developer proxy service"));
|
||||
DevProxyService devProxyService = DevProxyService.getInstance();
|
||||
devProxyService.stop();
|
||||
this.running = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void setSourceHostAndPort(String sourceHostAndPort) {
|
||||
this.sourceHostAndPort = sourceHostAndPort;
|
||||
}
|
||||
|
||||
public String getSourceHostAndPort() {
|
||||
return this.sourceHostAndPort;
|
||||
}
|
||||
|
||||
public Integer getPort() {
|
||||
return Settings.getInstance().getDevProxyPort();
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return this.running;
|
||||
}
|
||||
|
||||
}
|
@@ -414,7 +414,7 @@ public class OnlineAccountsManager {
|
||||
boolean isSuperiorEntry = isOnlineAccountsDataSuperior(onlineAccountData);
|
||||
if (isSuperiorEntry)
|
||||
// Remove existing inferior entry so it can be re-added below (it's likely the existing copy is missing a nonce value)
|
||||
onlineAccounts.remove(onlineAccountData);
|
||||
onlineAccounts.removeIf(a -> Objects.equals(a.getPublicKey(), onlineAccountData.getPublicKey()));
|
||||
|
||||
boolean isNewEntry = onlineAccounts.add(onlineAccountData);
|
||||
|
||||
|
@@ -25,7 +25,8 @@ public class NamesDatabaseIntegrityCheck {
|
||||
TransactionType.REGISTER_NAME,
|
||||
TransactionType.UPDATE_NAME,
|
||||
TransactionType.BUY_NAME,
|
||||
TransactionType.SELL_NAME
|
||||
TransactionType.SELL_NAME,
|
||||
TransactionType.CANCEL_SELL_NAME
|
||||
);
|
||||
|
||||
private List<TransactionData> nameTransactions = new ArrayList<>();
|
||||
|
@@ -28,7 +28,7 @@ public abstract class TrustlessSSLSocketFactory {
|
||||
private static final SSLContext sc;
|
||||
static {
|
||||
try {
|
||||
sc = SSLContext.getInstance("SSL");
|
||||
sc = SSLContext.getInstance("TLSv1.3");
|
||||
sc.init(null, TRUSTLESS_MANAGER, new java.security.SecureRandom());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package org.qortal.data.network;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
@@ -34,10 +35,6 @@ public class OnlineAccountData {
|
||||
this.nonce = nonce;
|
||||
}
|
||||
|
||||
public OnlineAccountData(long timestamp, byte[] signature, byte[] publicKey) {
|
||||
this(timestamp, signature, publicKey, null);
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return this.timestamp;
|
||||
}
|
||||
@@ -76,6 +73,10 @@ public class OnlineAccountData {
|
||||
if (otherOnlineAccountData.timestamp != this.timestamp)
|
||||
return false;
|
||||
|
||||
// Almost as quick
|
||||
if (!Objects.equals(otherOnlineAccountData.nonce, this.nonce))
|
||||
return false;
|
||||
|
||||
if (!Arrays.equals(otherOnlineAccountData.publicKey, this.publicKey))
|
||||
return false;
|
||||
|
||||
@@ -88,9 +89,10 @@ public class OnlineAccountData {
|
||||
public int hashCode() {
|
||||
int h = this.hash;
|
||||
if (h == 0) {
|
||||
this.hash = h = Long.hashCode(this.timestamp)
|
||||
^ Arrays.hashCode(this.publicKey);
|
||||
h = Objects.hash(timestamp, nonce);
|
||||
h = 31 * h + Arrays.hashCode(publicKey);
|
||||
// We don't use signature because newer aggregate signatures use random nonces
|
||||
this.hash = h;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
@@ -265,7 +265,7 @@ public enum Handshake {
|
||||
private static final long PEER_VERSION_131 = 0x0100030001L;
|
||||
|
||||
/** Minimum peer version that we are allowed to communicate with */
|
||||
private static final String MIN_PEER_VERSION = "4.0.0";
|
||||
private static final String MIN_PEER_VERSION = "4.1.1";
|
||||
|
||||
private static final int POW_BUFFER_SIZE_PRE_131 = 8 * 1024 * 1024; // bytes
|
||||
private static final int POW_DIFFICULTY_PRE_131 = 8; // leading zero bits
|
||||
|
@@ -187,7 +187,7 @@ public class Network {
|
||||
|
||||
this.bindAddress = bindAddress; // Store the selected address, so that it can be used by other parts of the app
|
||||
break; // We don't want to bind to more than one address
|
||||
} catch (UnknownHostException e) {
|
||||
} catch (UnknownHostException | UnsupportedAddressTypeException 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);
|
||||
|
@@ -1,69 +0,0 @@
|
||||
package org.qortal.network.message;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.qortal.data.network.OnlineAccountData;
|
||||
import org.qortal.transform.Transformer;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.google.common.primitives.Longs;
|
||||
|
||||
public class GetOnlineAccountsMessage extends Message {
|
||||
private static final int MAX_ACCOUNT_COUNT = 5000;
|
||||
|
||||
private List<OnlineAccountData> onlineAccounts;
|
||||
|
||||
public GetOnlineAccountsMessage(List<OnlineAccountData> onlineAccounts) {
|
||||
super(MessageType.GET_ONLINE_ACCOUNTS);
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
bytes.write(Ints.toByteArray(onlineAccounts.size()));
|
||||
|
||||
for (OnlineAccountData onlineAccountData : onlineAccounts) {
|
||||
bytes.write(Longs.toByteArray(onlineAccountData.getTimestamp()));
|
||||
|
||||
bytes.write(onlineAccountData.getPublicKey());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream");
|
||||
}
|
||||
|
||||
this.dataBytes = bytes.toByteArray();
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
private GetOnlineAccountsMessage(int id, List<OnlineAccountData> onlineAccounts) {
|
||||
super(id, MessageType.GET_ONLINE_ACCOUNTS);
|
||||
|
||||
this.onlineAccounts = onlineAccounts.stream().limit(MAX_ACCOUNT_COUNT).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<OnlineAccountData> getOnlineAccounts() {
|
||||
return this.onlineAccounts;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) {
|
||||
final int accountCount = bytes.getInt();
|
||||
|
||||
List<OnlineAccountData> onlineAccounts = new ArrayList<>(accountCount);
|
||||
|
||||
for (int i = 0; i < Math.min(MAX_ACCOUNT_COUNT, accountCount); ++i) {
|
||||
long timestamp = bytes.getLong();
|
||||
|
||||
byte[] publicKey = new byte[Transformer.PUBLIC_KEY_LENGTH];
|
||||
bytes.get(publicKey);
|
||||
|
||||
onlineAccounts.add(new OnlineAccountData(timestamp, null, publicKey));
|
||||
}
|
||||
|
||||
return new GetOnlineAccountsMessage(id, onlineAccounts);
|
||||
}
|
||||
|
||||
}
|
@@ -1,109 +0,0 @@
|
||||
package org.qortal.network.message;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.google.common.primitives.Longs;
|
||||
import org.qortal.data.network.OnlineAccountData;
|
||||
import org.qortal.transform.Transformer;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* For requesting online accounts info from remote peer, given our list of online accounts.
|
||||
*
|
||||
* Different format to V1:
|
||||
* V1 is: number of entries, then timestamp + pubkey for each entry
|
||||
* V2 is: groups of: number of entries, timestamp, then pubkey for each entry
|
||||
*
|
||||
* Also V2 only builds online accounts message once!
|
||||
*/
|
||||
public class GetOnlineAccountsV2Message extends Message {
|
||||
|
||||
private List<OnlineAccountData> onlineAccounts;
|
||||
|
||||
public GetOnlineAccountsV2Message(List<OnlineAccountData> onlineAccounts) {
|
||||
super(MessageType.GET_ONLINE_ACCOUNTS_V2);
|
||||
|
||||
// If we don't have ANY online accounts then it's an easier construction...
|
||||
if (onlineAccounts.isEmpty()) {
|
||||
// Always supply a number of accounts
|
||||
this.dataBytes = Ints.toByteArray(0);
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
return;
|
||||
}
|
||||
|
||||
// How many of each timestamp
|
||||
Map<Long, Integer> countByTimestamp = new HashMap<>();
|
||||
|
||||
for (OnlineAccountData onlineAccountData : onlineAccounts) {
|
||||
Long timestamp = onlineAccountData.getTimestamp();
|
||||
countByTimestamp.compute(timestamp, (k, v) -> v == null ? 1 : ++v);
|
||||
}
|
||||
|
||||
// We should know exactly how many bytes to allocate now
|
||||
int byteSize = countByTimestamp.size() * (Transformer.INT_LENGTH + Transformer.TIMESTAMP_LENGTH)
|
||||
+ onlineAccounts.size() * Transformer.PUBLIC_KEY_LENGTH;
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(byteSize);
|
||||
|
||||
try {
|
||||
for (long timestamp : countByTimestamp.keySet()) {
|
||||
bytes.write(Ints.toByteArray(countByTimestamp.get(timestamp)));
|
||||
|
||||
bytes.write(Longs.toByteArray(timestamp));
|
||||
|
||||
for (OnlineAccountData onlineAccountData : onlineAccounts) {
|
||||
if (onlineAccountData.getTimestamp() == timestamp)
|
||||
bytes.write(onlineAccountData.getPublicKey());
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream");
|
||||
}
|
||||
|
||||
this.dataBytes = bytes.toByteArray();
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
private GetOnlineAccountsV2Message(int id, List<OnlineAccountData> onlineAccounts) {
|
||||
super(id, MessageType.GET_ONLINE_ACCOUNTS_V2);
|
||||
|
||||
this.onlineAccounts = onlineAccounts;
|
||||
}
|
||||
|
||||
public List<OnlineAccountData> getOnlineAccounts() {
|
||||
return this.onlineAccounts;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) {
|
||||
int accountCount = bytes.getInt();
|
||||
|
||||
List<OnlineAccountData> onlineAccounts = new ArrayList<>(accountCount);
|
||||
|
||||
while (accountCount > 0) {
|
||||
long timestamp = bytes.getLong();
|
||||
|
||||
for (int i = 0; i < accountCount; ++i) {
|
||||
byte[] publicKey = new byte[Transformer.PUBLIC_KEY_LENGTH];
|
||||
bytes.get(publicKey);
|
||||
|
||||
onlineAccounts.add(new OnlineAccountData(timestamp, null, publicKey));
|
||||
}
|
||||
|
||||
if (bytes.hasRemaining()) {
|
||||
accountCount = bytes.getInt();
|
||||
} else {
|
||||
// we've finished
|
||||
accountCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return new GetOnlineAccountsV2Message(id, onlineAccounts);
|
||||
}
|
||||
|
||||
}
|
@@ -43,11 +43,7 @@ public enum MessageType {
|
||||
BLOCK_SUMMARIES(70, BlockSummariesMessage::fromByteBuffer),
|
||||
GET_BLOCK_SUMMARIES(71, GetBlockSummariesMessage::fromByteBuffer),
|
||||
BLOCK_SUMMARIES_V2(72, BlockSummariesV2Message::fromByteBuffer),
|
||||
|
||||
ONLINE_ACCOUNTS(80, OnlineAccountsMessage::fromByteBuffer),
|
||||
GET_ONLINE_ACCOUNTS(81, GetOnlineAccountsMessage::fromByteBuffer),
|
||||
ONLINE_ACCOUNTS_V2(82, OnlineAccountsV2Message::fromByteBuffer),
|
||||
GET_ONLINE_ACCOUNTS_V2(83, GetOnlineAccountsV2Message::fromByteBuffer),
|
||||
|
||||
ONLINE_ACCOUNTS_V3(84, OnlineAccountsV3Message::fromByteBuffer),
|
||||
GET_ONLINE_ACCOUNTS_V3(85, GetOnlineAccountsV3Message::fromByteBuffer),
|
||||
|
||||
|
@@ -1,75 +0,0 @@
|
||||
package org.qortal.network.message;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.qortal.data.network.OnlineAccountData;
|
||||
import org.qortal.transform.Transformer;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.google.common.primitives.Longs;
|
||||
|
||||
public class OnlineAccountsMessage extends Message {
|
||||
private static final int MAX_ACCOUNT_COUNT = 5000;
|
||||
|
||||
private List<OnlineAccountData> onlineAccounts;
|
||||
|
||||
public OnlineAccountsMessage(List<OnlineAccountData> onlineAccounts) {
|
||||
super(MessageType.ONLINE_ACCOUNTS);
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
bytes.write(Ints.toByteArray(onlineAccounts.size()));
|
||||
|
||||
for (OnlineAccountData onlineAccountData : onlineAccounts) {
|
||||
bytes.write(Longs.toByteArray(onlineAccountData.getTimestamp()));
|
||||
|
||||
bytes.write(onlineAccountData.getSignature());
|
||||
|
||||
bytes.write(onlineAccountData.getPublicKey());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream");
|
||||
}
|
||||
|
||||
this.dataBytes = bytes.toByteArray();
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
private OnlineAccountsMessage(int id, List<OnlineAccountData> onlineAccounts) {
|
||||
super(id, MessageType.ONLINE_ACCOUNTS);
|
||||
|
||||
this.onlineAccounts = onlineAccounts.stream().limit(MAX_ACCOUNT_COUNT).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<OnlineAccountData> getOnlineAccounts() {
|
||||
return this.onlineAccounts;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) {
|
||||
final int accountCount = bytes.getInt();
|
||||
|
||||
List<OnlineAccountData> onlineAccounts = new ArrayList<>(accountCount);
|
||||
|
||||
for (int i = 0; i < Math.min(MAX_ACCOUNT_COUNT, accountCount); ++i) {
|
||||
long timestamp = bytes.getLong();
|
||||
|
||||
byte[] signature = new byte[Transformer.SIGNATURE_LENGTH];
|
||||
bytes.get(signature);
|
||||
|
||||
byte[] publicKey = new byte[Transformer.PUBLIC_KEY_LENGTH];
|
||||
bytes.get(publicKey);
|
||||
|
||||
OnlineAccountData onlineAccountData = new OnlineAccountData(timestamp, signature, publicKey);
|
||||
onlineAccounts.add(onlineAccountData);
|
||||
}
|
||||
|
||||
return new OnlineAccountsMessage(id, onlineAccounts);
|
||||
}
|
||||
|
||||
}
|
@@ -1,113 +0,0 @@
|
||||
package org.qortal.network.message;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.google.common.primitives.Longs;
|
||||
import org.qortal.data.network.OnlineAccountData;
|
||||
import org.qortal.transform.Transformer;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* For sending online accounts info to remote peer.
|
||||
*
|
||||
* Different format to V1:
|
||||
* V1 is: number of entries, then timestamp + sig + pubkey for each entry
|
||||
* V2 is: groups of: number of entries, timestamp, then sig + pubkey for each entry
|
||||
*
|
||||
* Also V2 only builds online accounts message once!
|
||||
*/
|
||||
public class OnlineAccountsV2Message extends Message {
|
||||
|
||||
private List<OnlineAccountData> onlineAccounts;
|
||||
|
||||
public OnlineAccountsV2Message(List<OnlineAccountData> onlineAccounts) {
|
||||
super(MessageType.ONLINE_ACCOUNTS_V2);
|
||||
|
||||
// Shortcut in case we have no online accounts
|
||||
if (onlineAccounts.isEmpty()) {
|
||||
this.dataBytes = Ints.toByteArray(0);
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
return;
|
||||
}
|
||||
|
||||
// How many of each timestamp
|
||||
Map<Long, Integer> countByTimestamp = new HashMap<>();
|
||||
|
||||
for (OnlineAccountData onlineAccountData : onlineAccounts) {
|
||||
Long timestamp = onlineAccountData.getTimestamp();
|
||||
countByTimestamp.compute(timestamp, (k, v) -> v == null ? 1 : ++v);
|
||||
}
|
||||
|
||||
// We should know exactly how many bytes to allocate now
|
||||
int byteSize = countByTimestamp.size() * (Transformer.INT_LENGTH + Transformer.TIMESTAMP_LENGTH)
|
||||
+ onlineAccounts.size() * (Transformer.SIGNATURE_LENGTH + Transformer.PUBLIC_KEY_LENGTH);
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(byteSize);
|
||||
|
||||
try {
|
||||
for (long timestamp : countByTimestamp.keySet()) {
|
||||
bytes.write(Ints.toByteArray(countByTimestamp.get(timestamp)));
|
||||
|
||||
bytes.write(Longs.toByteArray(timestamp));
|
||||
|
||||
for (OnlineAccountData onlineAccountData : onlineAccounts) {
|
||||
if (onlineAccountData.getTimestamp() == timestamp) {
|
||||
bytes.write(onlineAccountData.getSignature());
|
||||
bytes.write(onlineAccountData.getPublicKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream");
|
||||
}
|
||||
|
||||
this.dataBytes = bytes.toByteArray();
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
private OnlineAccountsV2Message(int id, List<OnlineAccountData> onlineAccounts) {
|
||||
super(id, MessageType.ONLINE_ACCOUNTS_V2);
|
||||
|
||||
this.onlineAccounts = onlineAccounts;
|
||||
}
|
||||
|
||||
public List<OnlineAccountData> getOnlineAccounts() {
|
||||
return this.onlineAccounts;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws MessageException {
|
||||
int accountCount = bytes.getInt();
|
||||
|
||||
List<OnlineAccountData> onlineAccounts = new ArrayList<>(accountCount);
|
||||
|
||||
while (accountCount > 0) {
|
||||
long timestamp = bytes.getLong();
|
||||
|
||||
for (int i = 0; i < accountCount; ++i) {
|
||||
byte[] signature = new byte[Transformer.SIGNATURE_LENGTH];
|
||||
bytes.get(signature);
|
||||
|
||||
byte[] publicKey = new byte[Transformer.PUBLIC_KEY_LENGTH];
|
||||
bytes.get(publicKey);
|
||||
|
||||
onlineAccounts.add(new OnlineAccountData(timestamp, signature, publicKey));
|
||||
}
|
||||
|
||||
if (bytes.hasRemaining()) {
|
||||
accountCount = bytes.getInt();
|
||||
} else {
|
||||
// we've finished
|
||||
accountCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return new OnlineAccountsV2Message(id, onlineAccounts);
|
||||
}
|
||||
|
||||
}
|
@@ -99,9 +99,10 @@ public class OnlineAccountsV3Message extends Message {
|
||||
bytes.get(publicKey);
|
||||
|
||||
// Nonce is optional - will be -1 if missing
|
||||
// ... but we should skip/ignore an online account if it has no nonce
|
||||
Integer nonce = bytes.getInt();
|
||||
if (nonce < 0) {
|
||||
nonce = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
onlineAccounts.add(new OnlineAccountData(timestamp, signature, publicKey, nonce));
|
||||
|
@@ -71,11 +71,11 @@ public abstract class RepositoryManager {
|
||||
}
|
||||
|
||||
public static boolean needsTransactionSequenceRebuild(Repository repository) throws DataException {
|
||||
// Check if we have any unpopulated block_sequence values for the first 1000 blocks
|
||||
// Check if we have any transactions without a block_sequence
|
||||
List<byte[]> testSignatures = repository.getTransactionRepository().getSignaturesMatchingCustomCriteria(
|
||||
null, Arrays.asList("block_height < 1000 AND block_sequence IS NULL"), new ArrayList<>());
|
||||
null, Arrays.asList("block_height IS NOT NULL AND block_sequence IS NULL"), new ArrayList<>(), 100);
|
||||
if (testSignatures.isEmpty()) {
|
||||
// block_sequence already populated for the first 1000 blocks, so assume complete.
|
||||
// block_sequence intact, so assume complete
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ public abstract class RepositoryManager {
|
||||
int blockchainHeight = repository.getBlockRepository().getBlockchainHeight();
|
||||
int totalTransactionCount = 0;
|
||||
|
||||
for (int height = 1; height < blockchainHeight; ++height) {
|
||||
for (int height = 1; height <= blockchainHeight; ++height) {
|
||||
List<TransactionData> inputTransactions = new ArrayList<>();
|
||||
|
||||
// Fetch block and transactions
|
||||
|
@@ -125,6 +125,23 @@ public interface TransactionRepository {
|
||||
public List<byte[]> getSignaturesMatchingCustomCriteria(TransactionType txType, List<String> whereClauses,
|
||||
List<Object> bindParams) throws DataException;
|
||||
|
||||
/**
|
||||
* Returns signatures for transactions that match search criteria, with optional limit.
|
||||
* <p>
|
||||
* Alternate version that allows for custom where clauses and bind params.
|
||||
* Only use for very specific use cases, such as the names integrity check.
|
||||
* Not advised to be used otherwise, given that it could be possible for
|
||||
* unsanitized inputs to be passed in if not careful.
|
||||
*
|
||||
* @param txType
|
||||
* @param whereClauses
|
||||
* @param bindParams
|
||||
* @return
|
||||
* @throws DataException
|
||||
*/
|
||||
public List<byte[]> getSignaturesMatchingCustomCriteria(TransactionType txType, List<String> whereClauses,
|
||||
List<Object> bindParams, Integer limit) throws DataException;
|
||||
|
||||
/**
|
||||
* Returns signature for latest auto-update transaction.
|
||||
* <p>
|
||||
@@ -297,7 +314,7 @@ public interface TransactionRepository {
|
||||
* @return list of transactions, or empty if none.
|
||||
* @throws DataException
|
||||
*/
|
||||
public List<TransactionData> getUnconfirmedTransactions(EnumSet<TransactionType> excludedTxTypes) throws DataException;
|
||||
public List<TransactionData> getUnconfirmedTransactions(EnumSet<TransactionType> excludedTxTypes, Integer limit) throws DataException;
|
||||
|
||||
/**
|
||||
* Remove transaction from unconfirmed transactions pile.
|
||||
|
@@ -694,6 +694,53 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
}
|
||||
}
|
||||
|
||||
public List<byte[]> getSignaturesMatchingCustomCriteria(TransactionType txType, List<String> whereClauses,
|
||||
List<Object> bindParams, Integer limit) throws DataException {
|
||||
List<byte[]> signatures = new ArrayList<>();
|
||||
|
||||
String txTypeClassName = "";
|
||||
if (txType != null) {
|
||||
txTypeClassName = txType.className;
|
||||
}
|
||||
|
||||
StringBuilder sql = new StringBuilder(1024);
|
||||
sql.append(String.format("SELECT signature FROM %sTransactions", txTypeClassName));
|
||||
|
||||
if (!whereClauses.isEmpty()) {
|
||||
sql.append(" WHERE ");
|
||||
|
||||
final int whereClausesSize = whereClauses.size();
|
||||
for (int wci = 0; wci < whereClausesSize; ++wci) {
|
||||
if (wci != 0)
|
||||
sql.append(" AND ");
|
||||
|
||||
sql.append(whereClauses.get(wci));
|
||||
}
|
||||
}
|
||||
|
||||
if (limit != null) {
|
||||
sql.append(" LIMIT ?");
|
||||
bindParams.add(limit);
|
||||
}
|
||||
|
||||
LOGGER.trace(() -> String.format("Transaction search SQL: %s", sql));
|
||||
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(sql.toString(), bindParams.toArray())) {
|
||||
if (resultSet == null)
|
||||
return signatures;
|
||||
|
||||
do {
|
||||
byte[] signature = resultSet.getBytes(1);
|
||||
|
||||
signatures.add(signature);
|
||||
} while (resultSet.next());
|
||||
|
||||
return signatures;
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to fetch matching transaction signatures from repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getLatestAutoUpdateTransaction(TransactionType txType, int txGroupId, Integer service) throws DataException {
|
||||
StringBuilder sql = new StringBuilder(1024);
|
||||
@@ -1382,8 +1429,10 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TransactionData> getUnconfirmedTransactions(EnumSet<TransactionType> excludedTxTypes) throws DataException {
|
||||
public List<TransactionData> getUnconfirmedTransactions(EnumSet<TransactionType> excludedTxTypes, Integer limit) throws DataException {
|
||||
StringBuilder sql = new StringBuilder(1024);
|
||||
List<Object> bindParams = new ArrayList<>();
|
||||
|
||||
sql.append("SELECT signature FROM UnconfirmedTransactions ");
|
||||
sql.append("JOIN Transactions USING (signature) ");
|
||||
sql.append("WHERE type NOT IN (");
|
||||
@@ -1399,12 +1448,17 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
}
|
||||
|
||||
sql.append(")");
|
||||
sql.append("ORDER BY created_when, signature");
|
||||
sql.append("ORDER BY created_when, signature ");
|
||||
|
||||
if (limit != null) {
|
||||
sql.append("LIMIT ?");
|
||||
bindParams.add(limit);
|
||||
}
|
||||
|
||||
List<TransactionData> transactions = new ArrayList<>();
|
||||
|
||||
// Find transactions with no corresponding row in BlockTransactions
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(sql.toString())) {
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(sql.toString(), bindParams.toArray())) {
|
||||
if (resultSet == null)
|
||||
return transactions;
|
||||
|
||||
|
@@ -47,6 +47,9 @@ public class Settings {
|
||||
private static final int MAINNET_GATEWAY_PORT = 80;
|
||||
private static final int TESTNET_GATEWAY_PORT = 8080;
|
||||
|
||||
private static final int MAINNET_DEV_PROXY_PORT = 12393;
|
||||
private static final int TESTNET_DEV_PROXY_PORT = 62393;
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(Settings.class);
|
||||
private static final String SETTINGS_FILENAME = "settings.json";
|
||||
|
||||
@@ -107,6 +110,11 @@ public class Settings {
|
||||
private boolean gatewayLoggingEnabled = false;
|
||||
private boolean gatewayLoopbackEnabled = false;
|
||||
|
||||
// Developer Proxy
|
||||
private Integer devProxyPort;
|
||||
private boolean devProxyLoggingEnabled = false;
|
||||
|
||||
|
||||
// Specific to this node
|
||||
private boolean wipeUnconfirmedOnStart = false;
|
||||
/** Maximum number of unconfirmed transactions allowed per account */
|
||||
@@ -138,6 +146,9 @@ public class Settings {
|
||||
/* How many blocks to cache locally. Defaulted to 10, which covers a typical Synchronizer request + a few spare */
|
||||
private int blockCacheSize = 10;
|
||||
|
||||
/** Maximum number of transactions for the block minter to include in a block */
|
||||
private int maxTransactionsPerBlock = 25;
|
||||
|
||||
/** How long to keep old, full, AT state data (ms). */
|
||||
private long atStatesMaxLifetime = 5 * 24 * 60 * 60 * 1000L; // milliseconds
|
||||
/** How often to attempt AT state trimming (ms). */
|
||||
@@ -181,7 +192,7 @@ public class Settings {
|
||||
/** How often to attempt archiving (ms). */
|
||||
private long archiveInterval = 7171L; // milliseconds
|
||||
/** Serialization version to use when building an archive */
|
||||
private int defaultArchiveVersion = 1;
|
||||
private int defaultArchiveVersion = 2;
|
||||
|
||||
|
||||
/** Whether to automatically bootstrap instead of syncing from genesis */
|
||||
@@ -209,7 +220,7 @@ public class Settings {
|
||||
/** Number of slots to reserve for short-lived QDN data transfers */
|
||||
private int maxDataPeers = 4;
|
||||
/** Maximum number of threads for network engine. */
|
||||
private int maxNetworkThreadPoolSize = 32;
|
||||
private int maxNetworkThreadPoolSize = 120;
|
||||
/** Maximum number of threads for network proof-of-work compute, used during handshaking. */
|
||||
private int networkPoWComputePoolSize = 2;
|
||||
/** Maximum number of retry attempts if a peer fails to respond with the requested data */
|
||||
@@ -219,7 +230,7 @@ public class Settings {
|
||||
public long recoveryModeTimeout = 24 * 60 * 60 * 1000L;
|
||||
|
||||
/** Minimum peer version number required in order to sync with them */
|
||||
private String minPeerVersion = "4.0.0";
|
||||
private String minPeerVersion = "4.1.2";
|
||||
/** Whether to allow connections with peers below minPeerVersion
|
||||
* If true, we won't sync with them but they can still sync with us, and will show in the peers list
|
||||
* If false, sync will be blocked both ways, and they will not appear in the peers list */
|
||||
@@ -267,7 +278,7 @@ public class Settings {
|
||||
/** Repository storage path. */
|
||||
private String repositoryPath = "db";
|
||||
/** Repository connection pool size. Needs to be a bit bigger than maxNetworkThreadPoolSize */
|
||||
private int repositoryConnectionPoolSize = 100;
|
||||
private int repositoryConnectionPoolSize = 240;
|
||||
private List<String> fixedNetwork;
|
||||
|
||||
// Export/import
|
||||
@@ -649,6 +660,18 @@ public class Settings {
|
||||
}
|
||||
|
||||
|
||||
public int getDevProxyPort() {
|
||||
if (this.devProxyPort != null)
|
||||
return this.devProxyPort;
|
||||
|
||||
return this.isTestNet ? TESTNET_DEV_PROXY_PORT : MAINNET_DEV_PROXY_PORT;
|
||||
}
|
||||
|
||||
public boolean isDevProxyLoggingEnabled() {
|
||||
return this.devProxyLoggingEnabled;
|
||||
}
|
||||
|
||||
|
||||
public boolean getWipeUnconfirmedOnStart() {
|
||||
return this.wipeUnconfirmedOnStart;
|
||||
}
|
||||
@@ -673,6 +696,10 @@ public class Settings {
|
||||
return this.blockCacheSize;
|
||||
}
|
||||
|
||||
public int getMaxTransactionsPerBlock() {
|
||||
return this.maxTransactionsPerBlock;
|
||||
}
|
||||
|
||||
public boolean isTestNet() {
|
||||
return this.isTestNet;
|
||||
}
|
||||
|
@@ -641,7 +641,7 @@ public abstract class Transaction {
|
||||
BlockData latestBlockData = repository.getBlockRepository().getLastBlock();
|
||||
|
||||
EnumSet<TransactionType> excludedTxTypes = EnumSet.of(TransactionType.CHAT, TransactionType.PRESENCE);
|
||||
List<TransactionData> unconfirmedTransactions = repository.getTransactionRepository().getUnconfirmedTransactions(excludedTxTypes);
|
||||
List<TransactionData> unconfirmedTransactions = repository.getTransactionRepository().getUnconfirmedTransactions(excludedTxTypes, null);
|
||||
|
||||
unconfirmedTransactions.sort(getDataComparator());
|
||||
|
||||
|
83
src/main/resources/i18n/ApiError_jp.properties
Normal file
83
src/main/resources/i18n/ApiError_jp.properties
Normal file
@@ -0,0 +1,83 @@
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# Keys are from api.ApiError enum
|
||||
|
||||
# "localeLang": "jp",
|
||||
|
||||
### Common ###
|
||||
JSON = JSON メッセージの解析に失敗しました
|
||||
|
||||
INSUFFICIENT_BALANCE = 残高不足
|
||||
|
||||
UNAUTHORIZED = APIコール未承認
|
||||
|
||||
REPOSITORY_ISSUE = リポジトリエラー
|
||||
|
||||
NON_PRODUCTION = この APIコールはプロダクションシステムでは許可されていません
|
||||
|
||||
BLOCKCHAIN_NEEDS_SYNC = ブロックチェーンをまず同期する必要があります
|
||||
|
||||
NO_TIME_SYNC = 時刻が未同期
|
||||
|
||||
### Validation ###
|
||||
INVALID_SIGNATURE = 無効な署名
|
||||
|
||||
INVALID_ADDRESS = 無効なアドレス
|
||||
|
||||
INVALID_PUBLIC_KEY = 無効な公開鍵
|
||||
|
||||
INVALID_DATA = 無効なデータ
|
||||
|
||||
INVALID_NETWORK_ADDRESS = 無効なネットワーク アドレス
|
||||
|
||||
ADDRESS_UNKNOWN = 不明なアカウントアドレス
|
||||
|
||||
INVALID_CRITERIA = 無効な検索条件
|
||||
|
||||
INVALID_REFERENCE = 無効な参照
|
||||
|
||||
TRANSFORMATION_ERROR = JSONをトランザクションに変換出来ませんでした
|
||||
|
||||
INVALID_PRIVATE_KEY = 無効な秘密鍵
|
||||
|
||||
INVALID_HEIGHT = 無効なブロック高
|
||||
|
||||
CANNOT_MINT = アカウントはミント出来ません
|
||||
|
||||
### Blocks ###
|
||||
BLOCK_UNKNOWN = 不明なブロック
|
||||
|
||||
### Transactions ###
|
||||
TRANSACTION_UNKNOWN = 不明なトランザクション
|
||||
|
||||
PUBLIC_KEY_NOT_FOUND = 公開鍵が見つかりません
|
||||
|
||||
# this one is special in that caller expected to pass two additional strings, hence the two %s
|
||||
TRANSACTION_INVALID = 無効なトランザクション: %s (%s)
|
||||
|
||||
### Naming ###
|
||||
NAME_UNKNOWN = 不明な名前
|
||||
|
||||
### Asset ###
|
||||
INVALID_ASSET_ID = 無効なアセット ID
|
||||
|
||||
INVALID_ORDER_ID = 無効なアセット注文 ID
|
||||
|
||||
ORDER_UNKNOWN = 不明なアセット注文 ID
|
||||
|
||||
### Groups ###
|
||||
GROUP_UNKNOWN = 不明なグループ
|
||||
|
||||
### Foreign Blockchain ###
|
||||
FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = 外部ブロックチェーンまたはElectrumXネットワークの問題
|
||||
|
||||
FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = 外部ブロックチェーンの残高が不足しています
|
||||
|
||||
FOREIGN_BLOCKCHAIN_TOO_SOON = 外部ブロックチェーン トランザクションのブロードキャストが時期尚早 (ロックタイム/ブロック時間の中央値)
|
||||
|
||||
### Trade Portal ###
|
||||
ORDER_SIZE_TOO_SMALL = 注文金額が低すぎます
|
||||
|
||||
### Data ###
|
||||
FILE_NOT_FOUND = ファイルが見つかりません
|
||||
|
||||
NO_REPLY = ピアが制限時間内に応答しませんでした
|
48
src/main/resources/i18n/SysTray_jp.properties
Normal file
48
src/main/resources/i18n/SysTray_jp.properties
Normal file
@@ -0,0 +1,48 @@
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# SysTray pop-up menu # Japanese translation by R M 2023
|
||||
|
||||
APPLYING_UPDATE_AND_RESTARTING = 自動更新を適用して再起動しています...
|
||||
|
||||
AUTO_UPDATE = 自動更新
|
||||
|
||||
BLOCK_HEIGHT = ブロック高
|
||||
|
||||
BLOCKS_REMAINING = 残りのブロック
|
||||
|
||||
BUILD_VERSION = ビルドバージョン
|
||||
|
||||
CHECK_TIME_ACCURACY = 時刻の精度を確認
|
||||
|
||||
CONNECTING = 接続中
|
||||
|
||||
CONNECTION = 接続
|
||||
|
||||
CONNECTIONS = 接続
|
||||
|
||||
CREATING_BACKUP_OF_DB_FILES = データベース ファイルのバックアップを作成中...
|
||||
|
||||
DB_BACKUP = データベースのバックアップ
|
||||
|
||||
DB_CHECKPOINT = データベースのチェックポイント
|
||||
|
||||
DB_MAINTENANCE = データベースのメンテナンス
|
||||
|
||||
EXIT = 終了
|
||||
|
||||
LITE_NODE = ライトノード
|
||||
|
||||
MINTING_DISABLED = ミント一時中止中
|
||||
|
||||
MINTING_ENABLED = \u2714 ミント
|
||||
|
||||
OPEN_UI = UIを開く
|
||||
|
||||
PERFORMING_DB_CHECKPOINT = コミットされていないデータベースの変更を保存中...
|
||||
|
||||
PERFORMING_DB_MAINTENANCE = 定期メンテナンスを実行中...
|
||||
|
||||
SYNCHRONIZE_CLOCK = 時刻を同期
|
||||
|
||||
SYNCHRONIZING_BLOCKCHAIN = ブロックチェーンを同期中
|
||||
|
||||
SYNCHRONIZING_CLOCK = 時刻を同期中
|
195
src/main/resources/i18n/TransactionValidity_jp.properties
Normal file
195
src/main/resources/i18n/TransactionValidity_jp.properties
Normal file
@@ -0,0 +1,195 @@
|
||||
#
|
||||
|
||||
ACCOUNT_ALREADY_EXISTS = 既にアカウントは存在します
|
||||
|
||||
ACCOUNT_CANNOT_REWARD_SHARE = アカウントは報酬シェアが出来ません
|
||||
|
||||
ADDRESS_ABOVE_RATE_LIMIT = アドレスが指定されたレート制限に達しました
|
||||
|
||||
ADDRESS_BLOCKED = このアドレスはブロックされています
|
||||
|
||||
ALREADY_GROUP_ADMIN = 既ににグループ管理者です
|
||||
|
||||
ALREADY_GROUP_MEMBER = 既にグループメンバーです
|
||||
|
||||
ALREADY_VOTED_FOR_THAT_OPTION = 既にそのオプションに投票しています
|
||||
|
||||
ASSET_ALREADY_EXISTS = 既にアセットは存在します
|
||||
|
||||
ASSET_DOES_NOT_EXIST = アセットが存在しません
|
||||
|
||||
ASSET_DOES_NOT_MATCH_AT = アセットがATのアセットと一致しません
|
||||
|
||||
ASSET_NOT_SPENDABLE = 資産が使用不可です
|
||||
|
||||
AT_ALREADY_EXISTS = 既にATが存在します
|
||||
|
||||
AT_IS_FINISHED = ATが終了しました
|
||||
|
||||
AT_UNKNOWN = 不明なAT
|
||||
|
||||
BAN_EXISTS = 既にバンされてます
|
||||
|
||||
BAN_UNKNOWN = 不明なバン
|
||||
|
||||
BANNED_FROM_GROUP = グループからのバンされています
|
||||
|
||||
BUYER_ALREADY_OWNER = 既に購入者が所有者です
|
||||
|
||||
CLOCK_NOT_SYNCED = 時刻が未同期
|
||||
|
||||
DUPLICATE_MESSAGE = このアドレスは重複メッセージを送信しました
|
||||
|
||||
DUPLICATE_OPTION = 重複したオプション
|
||||
|
||||
GROUP_ALREADY_EXISTS = 既にグループは存在します
|
||||
|
||||
GROUP_APPROVAL_DECIDED = 既にグループの承認は決定されています
|
||||
|
||||
GROUP_APPROVAL_NOT_REQUIRED = グループ承認が不必要
|
||||
|
||||
GROUP_DOES_NOT_EXIST = グループが存在しません
|
||||
|
||||
GROUP_ID_MISMATCH = グループ ID が不一致
|
||||
|
||||
GROUP_OWNER_CANNOT_LEAVE = グループ所有者はグループを退会出来ません
|
||||
|
||||
HAVE_EQUALS_WANT = 持っている資産は欲しい資産と同じです
|
||||
|
||||
INCORRECT_NONCE = 不正な PoW ナンス
|
||||
|
||||
INSUFFICIENT_FEE = 手数料が不十分です
|
||||
|
||||
INVALID_ADDRESS = 無効なアドレス
|
||||
|
||||
INVALID_AMOUNT = 無効な金額
|
||||
|
||||
INVALID_ASSET_OWNER = 無効なアセット所有者
|
||||
|
||||
INVALID_AT_TRANSACTION = 無効なATトランザクション
|
||||
|
||||
INVALID_AT_TYPE_LENGTH = 無効なATの「タイプ」の長さです
|
||||
|
||||
INVALID_BUT_OK = 無効だがOK
|
||||
|
||||
INVALID_CREATION_BYTES = 無効な作成バイト数
|
||||
|
||||
INVALID_DATA_LENGTH = 無効なデータ長
|
||||
|
||||
INVALID_DESCRIPTION_LENGTH = 無効な概要の長さ
|
||||
|
||||
INVALID_GROUP_APPROVAL_THRESHOLD = 無効なグループ承認のしきい値
|
||||
|
||||
INVALID_GROUP_BLOCK_DELAY = 無効なグループ承認のブロック遅延
|
||||
|
||||
INVALID_GROUP_ID = 無効なグループ ID
|
||||
|
||||
INVALID_GROUP_OWNER = 無効なグループ所有者
|
||||
|
||||
INVALID_LIFETIME = 無効な有効期間
|
||||
|
||||
INVALID_NAME_LENGTH = 無効な名前の長さです
|
||||
|
||||
INVALID_NAME_OWNER = 無効な名前の所有者
|
||||
|
||||
INVALID_OPTION_LENGTH = 無効なオプションの長さ
|
||||
|
||||
INVALID_OPTIONS_COUNT = 無効なオプションの数
|
||||
|
||||
INVALID_ORDER_CREATOR = 無効な注文作成者
|
||||
|
||||
INVALID_PAYMENTS_COUNT = 無効な入出金数
|
||||
|
||||
INVALID_PUBLIC_KEY = 無効な公開鍵
|
||||
|
||||
INVALID_QUANTITY = 無効な数量
|
||||
|
||||
INVALID_REFERENCE = 無効な参照
|
||||
|
||||
INVALID_RETURN = 無効な返品
|
||||
|
||||
INVALID_REWARD_SHARE_PERCENT = 無効な報酬シェア率
|
||||
|
||||
INVALID_SELLER = 無効な販売者
|
||||
|
||||
INVALID_TAGS_LENGTH = 無効な「タグ」の長さ
|
||||
|
||||
INVALID_TIMESTAMP_SIGNATURE = 無効なタイムスタンプ署名
|
||||
|
||||
INVALID_TX_GROUP_ID = 無効なトランザクション グループ ID
|
||||
|
||||
INVALID_VALUE_LENGTH = 無効な「値」の長さ
|
||||
|
||||
INVITE_UNKNOWN = 不明なグループ招待
|
||||
|
||||
JOIN_REQUEST_EXISTS = 既にグループ参加リクエストが存在します
|
||||
|
||||
MAXIMUM_REWARD_SHARES = 既にこのアカウントの報酬シェアは最大です
|
||||
|
||||
MISSING_CREATOR = 作成者が見つかりません
|
||||
|
||||
MULTIPLE_NAMES_FORBIDDEN = アカウントごとに複数の登録名は禁止されています
|
||||
|
||||
NAME_ALREADY_FOR_SALE = 既に名前は販売中です
|
||||
|
||||
NAME_ALREADY_REGISTERED = 既に名前は登録されています
|
||||
|
||||
NAME_BLOCKED = この名前はブロックされています
|
||||
|
||||
NAME_DOES_NOT_EXIST = 名前は存在しません
|
||||
|
||||
NAME_NOT_FOR_SALE = 名前は非売品です
|
||||
|
||||
NAME_NOT_NORMALIZED = 名前は Unicode の「正規化」形式ではありません
|
||||
|
||||
NEGATIVE_AMOUNT = 無効な/負の金額
|
||||
|
||||
NEGATIVE_FEE = 無効な/負の料金
|
||||
|
||||
NEGATIVE_PRICE = 無効な/負の価格
|
||||
|
||||
NO_BALANCE = 残高が不足しています
|
||||
|
||||
NO_BLOCKCHAIN_LOCK = ノードのブロックチェーンは現在ビジーです
|
||||
|
||||
NO_FLAG_PERMISSION = アカウントにはその権限がありません
|
||||
|
||||
NOT_GROUP_ADMIN = アカウントはグループ管理者ではありません
|
||||
|
||||
NOT_GROUP_MEMBER = アカウントはグループメンバーではありません
|
||||
|
||||
NOT_MINTING_ACCOUNT = アカウントはミント出来ません
|
||||
|
||||
NOT_YET_RELEASED = 機能はまだリリースされていません
|
||||
|
||||
OK = OK
|
||||
|
||||
ORDER_ALREADY_CLOSED = 既に資産取引注文は終了しています
|
||||
|
||||
ORDER_DOES_NOT_EXIST = 資産取引注文が存在しません
|
||||
|
||||
POLL_ALREADY_EXISTS = 既に投票は存在します
|
||||
|
||||
POLL_DOES_NOT_EXIST = 投票は存在しません
|
||||
|
||||
POLL_OPTION_DOES_NOT_EXIST = 投票オプションが存在しません
|
||||
|
||||
PUBLIC_KEY_UNKNOWN = 不明な公開鍵
|
||||
|
||||
REWARD_SHARE_UNKNOWN = 不明な報酬シェア
|
||||
|
||||
SELF_SHARE_EXISTS = 既に自己シェア(報酬シェア)が存在します
|
||||
|
||||
TIMESTAMP_TOO_NEW = タイムスタンプが新しすぎます
|
||||
|
||||
TIMESTAMP_TOO_OLD = タイムスタンプが古すぎます
|
||||
|
||||
TOO_MANY_UNCONFIRMED = アカウントに保留中の未承認トランザクションが多すぎます
|
||||
|
||||
TRANSACTION_ALREADY_CONFIRMED = 既にトランザクションは承認されています
|
||||
|
||||
TRANSACTION_ALREADY_EXISTS = 既にトランザクションは存在します
|
||||
|
||||
TRANSACTION_UNKNOWN = 不明なトランザクション
|
||||
|
||||
TX_GROUP_ID_MISMATCH = トランザクションのグループIDが一致しません
|
@@ -448,6 +448,10 @@ function getDefaultTimeout(action) {
|
||||
// User may take a long time to accept/deny the popup
|
||||
return 60 * 60 * 1000;
|
||||
|
||||
case "SEARCH_QDN_RESOURCES":
|
||||
// Searching for data can be slow, especially when metadata and statuses are also being included
|
||||
return 30 * 1000;
|
||||
|
||||
case "FETCH_QDN_RESOURCE":
|
||||
// Fetching data can take a while, especially if the status hasn't been checked first
|
||||
return 60 * 1000;
|
||||
@@ -467,6 +471,10 @@ function getDefaultTimeout(action) {
|
||||
// Allow extra time for other actions that create transactions, even if there is no PoW
|
||||
return 5 * 60 * 1000;
|
||||
|
||||
case "GET_WALLET_BALANCE":
|
||||
// Getting a wallet balance can take a while, if there are many transactions
|
||||
return 2 * 60 * 1000;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@@ -212,7 +212,7 @@ public class BootstrapTests extends Common {
|
||||
@Test
|
||||
public void testBootstrapHosts() throws IOException {
|
||||
String[] bootstrapHosts = Settings.getInstance().getBootstrapHosts();
|
||||
String[] bootstrapTypes = { "archive", "toponly" };
|
||||
String[] bootstrapTypes = { "archive" }; // , "toponly"
|
||||
|
||||
for (String host : bootstrapHosts) {
|
||||
for (String type : bootstrapTypes) {
|
||||
|
@@ -456,6 +456,25 @@ public class ArbitraryServiceTests extends Common {
|
||||
assertEquals(ValidationResult.OK, service.validate(filePath));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidPrivateGroupData() throws IOException {
|
||||
String dataString = "qortalGroupEncryptedDatabMx4fELNTV+ifJxmv4+GcuOIJOTo+3qAvbWKNY2L1rfla5UBoEcoxbtjgZ9G7FLPb8V/Qfr0bfKWfvMmN06U/pgUdLuv2mGL2V0D3qYd1011MUzGdNG1qERjaCDz8GAi63+KnHHjfMtPgYt6bcqjs4CNV+ZZ4dIt3xxHYyVEBNc=";
|
||||
|
||||
// Write the data a single file in a temp path
|
||||
Path path = Files.createTempDirectory("testValidPrivateData");
|
||||
Path filePath = Paths.get(path.toString(), "test");
|
||||
filePath.toFile().deleteOnExit();
|
||||
|
||||
BufferedWriter writer = new BufferedWriter(new FileWriter(filePath.toFile()));
|
||||
writer.write(dataString);
|
||||
writer.close();
|
||||
|
||||
Service service = Service.FILE_PRIVATE;
|
||||
assertTrue(service.isValidationRequired());
|
||||
|
||||
assertEquals(ValidationResult.OK, service.validate(filePath));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncryptedData() throws IOException {
|
||||
String dataString = "qortalEncryptedDatabMx4fELNTV+ifJxmv4+GcuOIJOTo+3qAvbWKNY2L1rfla5UBoEcoxbtjgZ9G7FLPb8V/Qfr0bfKWfvMmN06U/pgUdLuv2mGL2V0D3qYd1011MUzGdNG1qERjaCDz8GAi63+KnHHjfMtPgYt6bcqjs4CNV+ZZ4dIt3xxHYyVEBNc=";
|
||||
|
@@ -8,6 +8,7 @@ import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.store.BlockStoreException;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.qortal.crosschain.Bitcoin;
|
||||
import org.qortal.crosschain.ForeignBlockchainException;
|
||||
@@ -32,6 +33,7 @@ public class BitcoinTests extends Common {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Often fails due to unreliable BTC testnet ElectrumX servers")
|
||||
public void testGetMedianBlockTime() throws BlockStoreException, ForeignBlockchainException {
|
||||
System.out.println(String.format("Starting BTC instance..."));
|
||||
System.out.println(String.format("BTC instance started"));
|
||||
@@ -53,6 +55,7 @@ public class BitcoinTests extends Common {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Often fails due to unreliable BTC testnet ElectrumX servers")
|
||||
public void testFindHtlcSecret() throws ForeignBlockchainException {
|
||||
// This actually exists on TEST3 but can take a while to fetch
|
||||
String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
@@ -65,6 +68,7 @@ public class BitcoinTests extends Common {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Often fails due to unreliable BTC testnet ElectrumX servers")
|
||||
public void testBuildSpend() {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
|
||||
@@ -81,6 +85,7 @@ public class BitcoinTests extends Common {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Often fails due to unreliable BTC testnet ElectrumX servers")
|
||||
public void testGetWalletBalance() throws ForeignBlockchainException {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
|
||||
@@ -102,6 +107,7 @@ public class BitcoinTests extends Common {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Often fails due to unreliable BTC testnet ElectrumX servers")
|
||||
public void testGetUnusedReceiveAddress() throws ForeignBlockchainException {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
|
||||
|
@@ -8,6 +8,7 @@ import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.qortal.crosschain.Bitcoin;
|
||||
import org.qortal.crosschain.ForeignBlockchainException;
|
||||
import org.qortal.crosschain.Litecoin;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.crosschain.BitcoinyHTLC;
|
||||
import org.qortal.repository.DataException;
|
||||
@@ -18,17 +19,19 @@ import com.google.common.primitives.Longs;
|
||||
public class HtlcTests extends Common {
|
||||
|
||||
private Bitcoin bitcoin;
|
||||
private Litecoin litecoin;
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings(); // TestNet3
|
||||
bitcoin = Bitcoin.getInstance();
|
||||
litecoin = Litecoin.getInstance();
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterTest() {
|
||||
Bitcoin.resetForTesting();
|
||||
bitcoin = null;
|
||||
litecoin = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -52,12 +55,12 @@ public class HtlcTests extends Common {
|
||||
do {
|
||||
// We need to perform fresh setup for 1st test
|
||||
Bitcoin.resetForTesting();
|
||||
bitcoin = Bitcoin.getInstance();
|
||||
litecoin = Litecoin.getInstance();
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
long timestampBoundary = now / 30_000L;
|
||||
|
||||
byte[] secret1 = BitcoinyHTLC.findHtlcSecret(bitcoin, p2shAddress);
|
||||
byte[] secret1 = BitcoinyHTLC.findHtlcSecret(litecoin, p2shAddress);
|
||||
long executionPeriod1 = System.currentTimeMillis() - now;
|
||||
|
||||
assertNotNull(secret1);
|
||||
@@ -65,7 +68,7 @@ public class HtlcTests extends Common {
|
||||
|
||||
assertTrue("1st execution period should not be instant!", executionPeriod1 > 10);
|
||||
|
||||
byte[] secret2 = BitcoinyHTLC.findHtlcSecret(bitcoin, p2shAddress);
|
||||
byte[] secret2 = BitcoinyHTLC.findHtlcSecret(litecoin, p2shAddress);
|
||||
long executionPeriod2 = System.currentTimeMillis() - now - executionPeriod1;
|
||||
|
||||
assertNotNull(secret2);
|
||||
@@ -86,7 +89,7 @@ public class HtlcTests extends Common {
|
||||
// This actually exists on TEST3 but can take a while to fetch
|
||||
String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
|
||||
BitcoinyHTLC.Status htlcStatus = BitcoinyHTLC.determineHtlcStatus(bitcoin.getBlockchainProvider(), p2shAddress, 1L);
|
||||
BitcoinyHTLC.Status htlcStatus = BitcoinyHTLC.determineHtlcStatus(litecoin.getBlockchainProvider(), p2shAddress, 1L);
|
||||
assertNotNull(htlcStatus);
|
||||
|
||||
System.out.println(String.format("HTLC %s status: %s", p2shAddress, htlcStatus.name()));
|
||||
@@ -97,21 +100,21 @@ public class HtlcTests extends Common {
|
||||
do {
|
||||
// We need to perform fresh setup for 1st test
|
||||
Bitcoin.resetForTesting();
|
||||
bitcoin = Bitcoin.getInstance();
|
||||
litecoin = Litecoin.getInstance();
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
long timestampBoundary = now / 30_000L;
|
||||
|
||||
// Won't ever exist
|
||||
String p2shAddress = bitcoin.deriveP2shAddress(Crypto.hash160(Longs.toByteArray(now)));
|
||||
String p2shAddress = litecoin.deriveP2shAddress(Crypto.hash160(Longs.toByteArray(now)));
|
||||
|
||||
BitcoinyHTLC.Status htlcStatus1 = BitcoinyHTLC.determineHtlcStatus(bitcoin.getBlockchainProvider(), p2shAddress, 1L);
|
||||
BitcoinyHTLC.Status htlcStatus1 = BitcoinyHTLC.determineHtlcStatus(litecoin.getBlockchainProvider(), p2shAddress, 1L);
|
||||
long executionPeriod1 = System.currentTimeMillis() - now;
|
||||
|
||||
assertNotNull(htlcStatus1);
|
||||
assertTrue("1st execution period should not be instant!", executionPeriod1 > 10);
|
||||
|
||||
BitcoinyHTLC.Status htlcStatus2 = BitcoinyHTLC.determineHtlcStatus(bitcoin.getBlockchainProvider(), p2shAddress, 1L);
|
||||
BitcoinyHTLC.Status htlcStatus2 = BitcoinyHTLC.determineHtlcStatus(litecoin.getBlockchainProvider(), p2shAddress, 1L);
|
||||
long executionPeriod2 = System.currentTimeMillis() - now - executionPeriod1;
|
||||
|
||||
assertNotNull(htlcStatus2);
|
||||
|
@@ -5,7 +5,6 @@ import static org.junit.Assert.*;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.bitcoinj.store.BlockStoreException;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
@@ -33,12 +32,12 @@ public class LitecoinTests extends Common {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMedianBlockTime() throws BlockStoreException, ForeignBlockchainException {
|
||||
public void testGetMedianBlockTime() throws ForeignBlockchainException {
|
||||
long before = System.currentTimeMillis();
|
||||
System.out.println(String.format("Bitcoin median blocktime: %d", litecoin.getMedianBlockTime()));
|
||||
System.out.println(String.format("Litecoin median blocktime: %d", litecoin.getMedianBlockTime()));
|
||||
long afterFirst = System.currentTimeMillis();
|
||||
|
||||
System.out.println(String.format("Bitcoin median blocktime: %d", litecoin.getMedianBlockTime()));
|
||||
System.out.println(String.format("Litecoin median blocktime: %d", litecoin.getMedianBlockTime()));
|
||||
long afterSecond = System.currentTimeMillis();
|
||||
|
||||
long firstPeriod = afterFirst - before;
|
||||
|
@@ -51,89 +51,6 @@ public class OnlineAccountsTests extends Common {
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testGetOnlineAccountsV2() throws MessageException {
|
||||
List<OnlineAccountData> onlineAccountsOut = generateOnlineAccounts(false);
|
||||
|
||||
Message messageOut = new GetOnlineAccountsV2Message(onlineAccountsOut);
|
||||
|
||||
byte[] messageBytes = messageOut.toBytes();
|
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(messageBytes);
|
||||
|
||||
GetOnlineAccountsV2Message messageIn = (GetOnlineAccountsV2Message) Message.fromByteBuffer(byteBuffer);
|
||||
|
||||
List<OnlineAccountData> onlineAccountsIn = messageIn.getOnlineAccounts();
|
||||
|
||||
assertEquals("size mismatch", onlineAccountsOut.size(), onlineAccountsIn.size());
|
||||
assertTrue("accounts mismatch", onlineAccountsIn.containsAll(onlineAccountsOut));
|
||||
|
||||
Message oldMessageOut = new GetOnlineAccountsMessage(onlineAccountsOut);
|
||||
byte[] oldMessageBytes = oldMessageOut.toBytes();
|
||||
|
||||
long numTimestamps = onlineAccountsOut.stream().mapToLong(OnlineAccountData::getTimestamp).sorted().distinct().count();
|
||||
|
||||
System.out.println(String.format("For %d accounts split across %d timestamp%s: old size %d vs new size %d",
|
||||
onlineAccountsOut.size(),
|
||||
numTimestamps,
|
||||
numTimestamps != 1 ? "s" : "",
|
||||
oldMessageBytes.length,
|
||||
messageBytes.length));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnlineAccountsV2() throws MessageException {
|
||||
List<OnlineAccountData> onlineAccountsOut = generateOnlineAccounts(true);
|
||||
|
||||
Message messageOut = new OnlineAccountsV2Message(onlineAccountsOut);
|
||||
|
||||
byte[] messageBytes = messageOut.toBytes();
|
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(messageBytes);
|
||||
|
||||
OnlineAccountsV2Message messageIn = (OnlineAccountsV2Message) Message.fromByteBuffer(byteBuffer);
|
||||
|
||||
List<OnlineAccountData> onlineAccountsIn = messageIn.getOnlineAccounts();
|
||||
|
||||
assertEquals("size mismatch", onlineAccountsOut.size(), onlineAccountsIn.size());
|
||||
assertTrue("accounts mismatch", onlineAccountsIn.containsAll(onlineAccountsOut));
|
||||
|
||||
Message oldMessageOut = new OnlineAccountsMessage(onlineAccountsOut);
|
||||
byte[] oldMessageBytes = oldMessageOut.toBytes();
|
||||
|
||||
long numTimestamps = onlineAccountsOut.stream().mapToLong(OnlineAccountData::getTimestamp).sorted().distinct().count();
|
||||
|
||||
System.out.println(String.format("For %d accounts split across %d timestamp%s: old size %d vs new size %d",
|
||||
onlineAccountsOut.size(),
|
||||
numTimestamps,
|
||||
numTimestamps != 1 ? "s" : "",
|
||||
oldMessageBytes.length,
|
||||
messageBytes.length));
|
||||
}
|
||||
|
||||
private List<OnlineAccountData> generateOnlineAccounts(boolean withSignatures) {
|
||||
List<OnlineAccountData> onlineAccounts = new ArrayList<>();
|
||||
|
||||
int numTimestamps = RANDOM.nextInt(2) + 1; // 1 or 2
|
||||
|
||||
for (int t = 0; t < numTimestamps; ++t) {
|
||||
int numAccounts = RANDOM.nextInt(3000);
|
||||
|
||||
for (int a = 0; a < numAccounts; ++a) {
|
||||
byte[] sig = null;
|
||||
if (withSignatures) {
|
||||
sig = new byte[Transformer.SIGNATURE_LENGTH];
|
||||
RANDOM.nextBytes(sig);
|
||||
}
|
||||
|
||||
byte[] pubkey = new byte[Transformer.PUBLIC_KEY_LENGTH];
|
||||
RANDOM.nextBytes(pubkey);
|
||||
|
||||
onlineAccounts.add(new OnlineAccountData(t << 32, sig, pubkey));
|
||||
}
|
||||
}
|
||||
|
||||
return onlineAccounts;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnlineAccountsModulusV1() throws IllegalAccessException, DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
@@ -26,41 +26,6 @@ public class OnlineAccountsV3Tests {
|
||||
Security.insertProviderAt(new BouncyCastleJsseProvider(), 1);
|
||||
}
|
||||
|
||||
@Ignore("For informational use")
|
||||
@Test
|
||||
public void compareV2ToV3() throws MessageException {
|
||||
List<OnlineAccountData> onlineAccounts = generateOnlineAccounts(false);
|
||||
|
||||
// How many of each timestamp and leading byte (of public key)
|
||||
Map<Long, Map<Byte, byte[]>> hashesByTimestampThenByte = convertToHashMaps(onlineAccounts);
|
||||
|
||||
byte[] v3DataBytes = new GetOnlineAccountsV3Message(hashesByTimestampThenByte).toBytes();
|
||||
int v3ByteSize = v3DataBytes.length;
|
||||
|
||||
byte[] v2DataBytes = new GetOnlineAccountsV2Message(onlineAccounts).toBytes();
|
||||
int v2ByteSize = v2DataBytes.length;
|
||||
|
||||
int numTimestamps = hashesByTimestampThenByte.size();
|
||||
System.out.printf("For %d accounts split across %d timestamp%s: V2 size %d vs V3 size %d%n",
|
||||
onlineAccounts.size(),
|
||||
numTimestamps,
|
||||
numTimestamps != 1 ? "s" : "",
|
||||
v2ByteSize,
|
||||
v3ByteSize
|
||||
);
|
||||
|
||||
for (var outerMapEntry : hashesByTimestampThenByte.entrySet()) {
|
||||
long timestamp = outerMapEntry.getKey();
|
||||
|
||||
var innerMap = outerMapEntry.getValue();
|
||||
|
||||
System.out.printf("For timestamp %d: %d / 256 slots used.%n",
|
||||
timestamp,
|
||||
innerMap.size()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<Long, Map<Byte, byte[]>> convertToHashMaps(List<OnlineAccountData> onlineAccounts) {
|
||||
// How many of each timestamp and leading byte (of public key)
|
||||
Map<Long, Map<Byte, byte[]>> hashesByTimestampThenByte = new HashMap<>();
|
||||
@@ -200,7 +165,9 @@ public class OnlineAccountsV3Tests {
|
||||
byte[] pubkey = new byte[Transformer.PUBLIC_KEY_LENGTH];
|
||||
RANDOM.nextBytes(pubkey);
|
||||
|
||||
onlineAccounts.add(new OnlineAccountData(timestamp, sig, pubkey));
|
||||
Integer nonce = RANDOM.nextInt();
|
||||
|
||||
onlineAccounts.add(new OnlineAccountData(timestamp, sig, pubkey, nonce));
|
||||
}
|
||||
}
|
||||
|
||||
|
3
start.sh
3
start.sh
@@ -33,7 +33,8 @@ fi
|
||||
# Limits Java JVM stack size and maximum heap usage.
|
||||
# Comment out for bigger systems, e.g. non-routers
|
||||
# or when API documentation is enabled
|
||||
# JVM_MEMORY_ARGS="-Xss256k -Xmx128m"
|
||||
# Uncomment (remove '#' sign) line below if your system has less than 12GB of RAM for optimal RAM defaults
|
||||
# JVM_MEMORY_ARGS="-Xss1256k -Xmx3128m"
|
||||
|
||||
# Although java.net.preferIPv4Stack is supposed to be false
|
||||
# by default in Java 11, on some platforms (e.g. FreeBSD 12),
|
||||
|
Reference in New Issue
Block a user