mirror of
https://github.com/Qortal/qortal.git
synced 2025-11-04 12:37:02 +00:00
Compare commits
58 Commits
v4.1.3
...
add-create
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3fbcc50503 | ||
| b051f9be89 | |||
| 24ff3ab581 | |||
| 34382b6e69 | |||
|
|
9428f9688f | ||
|
|
9f4a0b7957 | ||
|
|
3c8574a466 | ||
|
|
c428d7ce2e | ||
|
|
4feb8f46c8 | ||
|
|
1f7a60dfd8 | ||
|
|
379b850bbd | ||
|
|
7bb61ec564 | ||
|
|
b0224651c2 | ||
|
|
6bf2b99913 | ||
|
|
fd9d0c4e51 | ||
|
|
c2756a5872 | ||
|
|
ecfb9a7d6d | ||
|
|
dd9d3fdb22 | ||
|
|
eea2884112 | ||
|
|
640a472876 | ||
|
|
e244a5f882 | ||
|
|
278dca75e8 | ||
|
|
897c44ffe8 | ||
|
|
d9147b3af3 | ||
|
|
fe840bbf02 | ||
|
|
e7901a391f | ||
|
|
9574100a08 | ||
|
|
528583fe38 | ||
|
|
33cfd02c49 | ||
|
|
18e880158f | ||
|
|
94d3664cb0 | ||
|
|
f5c8dfe766 | ||
|
|
a3526d84bc | ||
|
|
f7e1f2fca8 | ||
|
|
811b647c88 | ||
|
|
3215bb638d | ||
|
|
8ae7a1d65b | ||
|
|
29dcd53002 | ||
|
|
62908f867a | ||
|
|
ff7a87ab18 | ||
|
|
5f86ecafd9 | ||
|
|
fe999a11f4 | ||
|
|
c14fca5660 | ||
|
|
cc8cdcd93a | ||
|
|
fd8d720946 | ||
|
|
d628b3ab2a | ||
|
|
5928b54a33 | ||
|
|
91dfc5efd0 | ||
|
|
1343a88ee3 | ||
|
|
7f7b02f003 | ||
|
|
5650923805 | ||
|
|
a4bb445f3e | ||
|
|
27afcf12bf | ||
|
|
13da0e8a7a | ||
|
|
d260c0a9a9 | ||
|
|
655073c524 | ||
|
|
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.3</version>
|
||||
<version>4.2.4</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();
|
||||
}
|
||||
|
||||
|
||||
102
src/main/java/org/qortal/api/model/AtCreationRequest.java
Normal file
102
src/main/java/org/qortal/api/model/AtCreationRequest.java
Normal file
@@ -0,0 +1,102 @@
|
||||
package org.qortal.api.model;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlTransient;
|
||||
|
||||
import org.bouncycastle.util.encoders.Base64;
|
||||
import org.bouncycastle.util.encoders.DecoderException;
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class AtCreationRequest {
|
||||
|
||||
@Schema(description = "CIYAM AT version", example = "2")
|
||||
private short ciyamAtVersion;
|
||||
|
||||
@Schema(description = "base64-encoded code bytes", example = "")
|
||||
private String codeBytesBase64;
|
||||
|
||||
@Schema(description = "base64-encoded data bytes", example = "")
|
||||
private String dataBytesBase64;
|
||||
|
||||
private short numCallStackPages;
|
||||
private short numUserStackPages;
|
||||
private long minActivationAmount;
|
||||
|
||||
// Default constructor for JSON deserialization
|
||||
public AtCreationRequest() {}
|
||||
|
||||
// Getters and setters
|
||||
public short getCiyamAtVersion() {
|
||||
return ciyamAtVersion;
|
||||
}
|
||||
|
||||
public void setCiyamAtVersion(short ciyamAtVersion) {
|
||||
this.ciyamAtVersion = ciyamAtVersion;
|
||||
}
|
||||
|
||||
|
||||
public String getCodeBytesBase64() {
|
||||
return this.codeBytesBase64;
|
||||
}
|
||||
|
||||
@XmlTransient
|
||||
@Schema(hidden = true)
|
||||
public byte[] getCodeBytes() {
|
||||
if (this.codeBytesBase64 != null) {
|
||||
try {
|
||||
return Base64.decode(this.codeBytesBase64);
|
||||
}
|
||||
catch (DecoderException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public String getDataBytesBase64() {
|
||||
return this.dataBytesBase64;
|
||||
}
|
||||
|
||||
@XmlTransient
|
||||
@Schema(hidden = true)
|
||||
public byte[] getDataBytes() {
|
||||
if (this.dataBytesBase64 != null) {
|
||||
try {
|
||||
return Base64.decode(this.dataBytesBase64);
|
||||
}
|
||||
catch (DecoderException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public short getNumCallStackPages() {
|
||||
return numCallStackPages;
|
||||
}
|
||||
|
||||
public void setNumCallStackPages(short numCallStackPages) {
|
||||
this.numCallStackPages = numCallStackPages;
|
||||
}
|
||||
|
||||
public short getNumUserStackPages() {
|
||||
return numUserStackPages;
|
||||
}
|
||||
|
||||
public void setNumUserStackPages(short numUserStackPages) {
|
||||
this.numUserStackPages = numUserStackPages;
|
||||
}
|
||||
|
||||
public long getMinActivationAmount() {
|
||||
return minActivationAmount;
|
||||
}
|
||||
|
||||
public void setMinActivationAmount(long minActivationAmount) {
|
||||
this.minActivationAmount = minActivationAmount;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -724,7 +724,7 @@ public class ArbitraryResource {
|
||||
ArbitraryDataResource resource = new ArbitraryDataResource(name, ResourceIdType.NAME, service, identifier);
|
||||
|
||||
try {
|
||||
ArbitraryDataTransactionMetadata transactionMetadata = ArbitraryMetadataManager.getInstance().fetchMetadata(resource, false);
|
||||
ArbitraryDataTransactionMetadata transactionMetadata = ArbitraryMetadataManager.getInstance().fetchMetadata(resource, true);
|
||||
if (transactionMetadata != null) {
|
||||
ArbitraryResourceMetadata resourceMetadata = ArbitraryResourceMetadata.fromTransactionMetadata(transactionMetadata, true);
|
||||
if (resourceMetadata != null) {
|
||||
|
||||
@@ -27,6 +27,7 @@ import org.qortal.api.ApiException;
|
||||
import org.qortal.api.ApiExceptionFactory;
|
||||
import org.qortal.data.at.ATData;
|
||||
import org.qortal.data.at.ATStateData;
|
||||
import org.qortal.api.model.AtCreationRequest;
|
||||
import org.qortal.data.transaction.DeployAtTransactionData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
@@ -38,9 +39,14 @@ import org.qortal.transform.TransformationException;
|
||||
import org.qortal.transform.transaction.DeployAtTransactionTransformer;
|
||||
import org.qortal.utils.Base58;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
||||
@Path("/at")
|
||||
@Tag(name = "Automated Transactions")
|
||||
public class AtResource {
|
||||
private static final Logger logger = LoggerFactory.getLogger(AtResource.class);
|
||||
|
||||
@Context
|
||||
HttpServletRequest request;
|
||||
@@ -156,6 +162,52 @@ public class AtResource {
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/create")
|
||||
@Operation(
|
||||
summary = "Create base58-encoded AT creation bytes from the provided parameters",
|
||||
requestBody = @RequestBody(
|
||||
required = true,
|
||||
content = @Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(
|
||||
implementation = AtCreationRequest.class
|
||||
)
|
||||
)
|
||||
),
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
description = "AT creation bytes suitable for use in a DEPLOY_AT transaction",
|
||||
content = @Content(
|
||||
mediaType = MediaType.TEXT_PLAIN,
|
||||
schema = @Schema(
|
||||
type = "string"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
public String create(AtCreationRequest atCreationRequest) {
|
||||
if (atCreationRequest.getCiyamAtVersion() < 2) {
|
||||
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, "ciyamAtVersion must be at least 2");
|
||||
}
|
||||
if (atCreationRequest.getCodeBytes() == null) {
|
||||
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, "Valid codeBytesBase64 must be supplied");
|
||||
}
|
||||
if (atCreationRequest.getDataBytes() == null) {
|
||||
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, "Valid dataBytesBase64 must be supplied");
|
||||
}
|
||||
|
||||
byte[] creationBytes = MachineState.toCreationBytes(
|
||||
atCreationRequest.getCiyamAtVersion(),
|
||||
atCreationRequest.getCodeBytes(),
|
||||
atCreationRequest.getDataBytes(),
|
||||
atCreationRequest.getNumCallStackPages(),
|
||||
atCreationRequest.getNumUserStackPages(),
|
||||
atCreationRequest.getMinActivationAmount()
|
||||
);
|
||||
return Base58.encode(creationBytes);
|
||||
}
|
||||
@POST
|
||||
@Operation(
|
||||
summary = "Build raw, unsigned, DEPLOY_AT transaction",
|
||||
|
||||
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;
|
||||
@@ -127,6 +127,11 @@ public class ArbitraryDataRenderer {
|
||||
String filename = this.getFilename(unzippedPath, inPath);
|
||||
Path filePath = Paths.get(unzippedPath, filename);
|
||||
boolean usingCustomRouting = false;
|
||||
if (Files.isDirectory(filePath) && (!inPath.endsWith("/"))) {
|
||||
inPath = inPath + "/";
|
||||
filename = this.getFilename(unzippedPath, inPath);
|
||||
filePath = Paths.get(unzippedPath, filename);
|
||||
}
|
||||
|
||||
// If the file doesn't exist, we may need to route the request elsewhere, or cleanup
|
||||
if (!Files.exists(filePath)) {
|
||||
@@ -159,7 +164,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +84,7 @@ public class Block {
|
||||
TRANSACTION_PROCESSING_FAILED(53),
|
||||
TRANSACTION_ALREADY_PROCESSED(54),
|
||||
TRANSACTION_NEEDS_APPROVAL(55),
|
||||
TRANSACTION_NOT_CONFIRMABLE(56),
|
||||
AT_STATES_MISMATCH(61),
|
||||
ONLINE_ACCOUNTS_INVALID(70),
|
||||
ONLINE_ACCOUNT_UNKNOWN(71),
|
||||
@@ -130,6 +131,9 @@ public class Block {
|
||||
/** Locally-generated AT fees */
|
||||
protected long ourAtFees; // Generated locally
|
||||
|
||||
/** Cached online accounts validation decision, to avoid revalidating when true */
|
||||
private boolean onlineAccountsAlreadyValid = false;
|
||||
|
||||
@FunctionalInterface
|
||||
private interface BlockRewardDistributor {
|
||||
long distribute(long amount, Map<String, Long> balanceChanges) throws DataException;
|
||||
@@ -563,6 +567,13 @@ public class Block {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Force online accounts to be revalidated, e.g. at final stage of block minting.
|
||||
*/
|
||||
public void clearOnlineAccountsValidationCache() {
|
||||
this.onlineAccountsAlreadyValid = false;
|
||||
}
|
||||
|
||||
// More information
|
||||
|
||||
/**
|
||||
@@ -1043,6 +1054,10 @@ public class Block {
|
||||
if (this.blockData.getHeight() != null && this.blockData.getHeight() == 1)
|
||||
return ValidationResult.OK;
|
||||
|
||||
// Don't bother revalidating if accounts have already been validated in this block
|
||||
if (this.onlineAccountsAlreadyValid)
|
||||
return ValidationResult.OK;
|
||||
|
||||
// Expand block's online accounts indexes into actual accounts
|
||||
ConciseSet accountIndexes = BlockTransformer.decodeOnlineAccounts(this.blockData.getEncodedOnlineAccounts());
|
||||
// We use count of online accounts to validate decoded account indexes
|
||||
@@ -1130,6 +1145,9 @@ public class Block {
|
||||
// All online accounts valid, so save our list of online accounts for potential later use
|
||||
this.cachedOnlineRewardShares = onlineRewardShares;
|
||||
|
||||
// Remember that the accounts are valid, to speed up subsequent checks
|
||||
this.onlineAccountsAlreadyValid = true;
|
||||
|
||||
return ValidationResult.OK;
|
||||
}
|
||||
|
||||
@@ -1234,6 +1252,13 @@ public class Block {
|
||||
|| transaction.getDeadline() <= this.blockData.getTimestamp())
|
||||
return ValidationResult.TRANSACTION_TIMESTAMP_INVALID;
|
||||
|
||||
// After feature trigger, check that this transaction is confirmable
|
||||
if (transactionData.getTimestamp() >= BlockChain.getInstance().getMemPoWTransactionUpdatesTimestamp()) {
|
||||
if (!transaction.isConfirmable()) {
|
||||
return ValidationResult.TRANSACTION_NOT_CONFIRMABLE;
|
||||
}
|
||||
}
|
||||
|
||||
// Check transaction isn't already included in a block
|
||||
if (this.repository.getTransactionRepository().isConfirmed(transactionData.getSignature()))
|
||||
return ValidationResult.TRANSACTION_ALREADY_PROCESSED;
|
||||
|
||||
@@ -48,9 +48,6 @@ public class BlockChain {
|
||||
/** Transaction expiry period, starting from transaction's timestamp, in milliseconds. */
|
||||
private long transactionExpiryPeriod;
|
||||
|
||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||
private long unitFee;
|
||||
|
||||
private int maxBytesPerUnitFee;
|
||||
|
||||
/** Maximum acceptable timestamp disagreement offset in milliseconds. */
|
||||
@@ -89,6 +86,7 @@ public class BlockChain {
|
||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||
public long fee;
|
||||
}
|
||||
private List<UnitFeesByTimestamp> unitFees;
|
||||
private List<UnitFeesByTimestamp> nameRegistrationUnitFees;
|
||||
|
||||
/** Map of which blockchain features are enabled when (height/timestamp) */
|
||||
@@ -211,6 +209,9 @@ public class BlockChain {
|
||||
/** Snapshot timestamp for self sponsorship algo V1 */
|
||||
private long selfSponsorshipAlgoV1SnapshotTimestamp;
|
||||
|
||||
/** Feature-trigger timestamp to modify behaviour of various transactions that support mempow */
|
||||
private long mempowTransactionUpdatesTimestamp;
|
||||
|
||||
/** Max reward shares by block height */
|
||||
public static class MaxRewardSharesByTimestamp {
|
||||
public long timestamp;
|
||||
@@ -346,10 +347,6 @@ public class BlockChain {
|
||||
return this.isTestChain;
|
||||
}
|
||||
|
||||
public long getUnitFee() {
|
||||
return this.unitFee;
|
||||
}
|
||||
|
||||
public int getMaxBytesPerUnitFee() {
|
||||
return this.maxBytesPerUnitFee;
|
||||
}
|
||||
@@ -376,6 +373,11 @@ public class BlockChain {
|
||||
return this.selfSponsorshipAlgoV1SnapshotTimestamp;
|
||||
}
|
||||
|
||||
// Feature-trigger timestamp to modify behaviour of various transactions that support mempow
|
||||
public long getMemPoWTransactionUpdatesTimestamp() {
|
||||
return this.mempowTransactionUpdatesTimestamp;
|
||||
}
|
||||
|
||||
/** Returns true if approval-needing transaction types require a txGroupId other than NO_GROUP. */
|
||||
public boolean getRequireGroupForApproval() {
|
||||
return this.requireGroupForApproval;
|
||||
@@ -547,13 +549,22 @@ public class BlockChain {
|
||||
throw new IllegalStateException(String.format("No block timing info available for height %d", ourHeight));
|
||||
}
|
||||
|
||||
public long getUnitFeeAtTimestamp(long ourTimestamp) {
|
||||
for (int i = unitFees.size() - 1; i >= 0; --i)
|
||||
if (unitFees.get(i).timestamp <= ourTimestamp)
|
||||
return unitFees.get(i).fee;
|
||||
|
||||
// Shouldn't happen, but set a sensible default just in case
|
||||
return 100000;
|
||||
}
|
||||
|
||||
public long getNameRegistrationUnitFeeAtTimestamp(long ourTimestamp) {
|
||||
for (int i = nameRegistrationUnitFees.size() - 1; i >= 0; --i)
|
||||
if (nameRegistrationUnitFees.get(i).timestamp <= ourTimestamp)
|
||||
return nameRegistrationUnitFees.get(i).fee;
|
||||
|
||||
// Default to system-wide unit fee
|
||||
return this.getUnitFee();
|
||||
// Shouldn't happen, but set a sensible default just in case
|
||||
return 100000;
|
||||
}
|
||||
|
||||
public int getMaxRewardSharesAtTimestamp(long ourTimestamp) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -549,6 +562,9 @@ public class BlockMinter extends Thread {
|
||||
// Sign to create block's signature
|
||||
newBlock.sign();
|
||||
|
||||
// Ensure online accounts are fully re-validated in this final check
|
||||
newBlock.clearOnlineAccountsValidationCache();
|
||||
|
||||
// Is newBlock still valid?
|
||||
ValidationResult validationResult = newBlock.isValid();
|
||||
if (validationResult != ValidationResult.OK)
|
||||
|
||||
@@ -1278,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);
|
||||
|
||||
|
||||
@@ -229,13 +229,6 @@ public class Synchronizer extends Thread {
|
||||
peers.removeIf(Controller.hasOldVersion);
|
||||
|
||||
checkRecoveryModeForPeers(peers);
|
||||
if (recoveryMode) {
|
||||
// Needs a mutable copy of the unmodifiableList
|
||||
peers = new ArrayList<>(Network.getInstance().getImmutableHandshakedPeers());
|
||||
peers.removeIf(Controller.hasOnlyGenesisBlock);
|
||||
peers.removeIf(Controller.hasMisbehaved);
|
||||
peers.removeIf(Controller.hasOldVersion);
|
||||
}
|
||||
|
||||
// Check we have enough peers to potentially synchronize
|
||||
if (peers.size() < Settings.getInstance().getMinBlockchainPeers())
|
||||
@@ -262,10 +255,7 @@ public class Synchronizer extends Thread {
|
||||
peers.removeIf(Controller.hasInferiorChainTip);
|
||||
|
||||
// Remove any peers that are no longer on a recent block since the last check
|
||||
// Except for times when we're in recovery mode, in which case we need to keep them
|
||||
if (!recoveryMode) {
|
||||
peers.removeIf(Controller.hasNoRecentBlock);
|
||||
}
|
||||
peers.removeIf(Controller.hasNoRecentBlock);
|
||||
|
||||
final int peersRemoved = peersBeforeComparison - peers.size();
|
||||
if (peersRemoved > 0 && peers.size() > 0)
|
||||
@@ -1340,8 +1330,8 @@ public class Synchronizer extends Thread {
|
||||
return SynchronizationResult.INVALID_DATA;
|
||||
}
|
||||
|
||||
// Final check to make sure the peer isn't out of date (except for when we're in recovery mode)
|
||||
if (!recoveryMode && peer.getChainTipData() != null) {
|
||||
// Final check to make sure the peer isn't out of date
|
||||
if (peer.getChainTipData() != null) {
|
||||
final Long minLatestBlockTimestamp = Controller.getMinimumLatestBlockTimestamp();
|
||||
final Long peerLastBlockTimestamp = peer.getChainTipData().getTimestamp();
|
||||
if (peerLastBlockTimestamp == null || peerLastBlockTimestamp < minLatestBlockTimestamp) {
|
||||
|
||||
@@ -47,6 +47,9 @@ public class TransactionImporter extends Thread {
|
||||
/** Map of recent invalid unconfirmed transactions. Key is base58 transaction signature, value is do-not-request expiry timestamp. */
|
||||
private final Map<String, Long> invalidUnconfirmedTransactions = Collections.synchronizedMap(new HashMap<>());
|
||||
|
||||
/** Cached list of unconfirmed transactions, used when counting per creator. This is replaced regularly */
|
||||
public static List<TransactionData> unconfirmedTransactionsCache = null;
|
||||
|
||||
|
||||
public static synchronized TransactionImporter getInstance() {
|
||||
if (instance == null) {
|
||||
@@ -215,12 +218,6 @@ public class TransactionImporter extends Thread {
|
||||
LOGGER.debug("Finished validating signatures in incoming transactions queue (valid this round: {}, total pending import: {})...", validatedCount, sigValidTransactions.size());
|
||||
}
|
||||
|
||||
if (!newlyValidSignatures.isEmpty()) {
|
||||
LOGGER.debug("Broadcasting {} newly valid signatures ahead of import", newlyValidSignatures.size());
|
||||
Message newTransactionSignatureMessage = new TransactionSignaturesMessage(newlyValidSignatures);
|
||||
Network.getInstance().broadcast(broadcastPeer -> newTransactionSignatureMessage);
|
||||
}
|
||||
|
||||
} catch (DataException e) {
|
||||
LOGGER.error("Repository issue while processing incoming transactions", e);
|
||||
}
|
||||
@@ -254,6 +251,15 @@ public class TransactionImporter extends Thread {
|
||||
int processedCount = 0;
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
// Use a single copy of the unconfirmed transactions list for each cycle, to speed up constant lookups
|
||||
// when counting unconfirmed transactions by creator.
|
||||
List<TransactionData> unconfirmedTransactions = repository.getTransactionRepository().getUnconfirmedTransactions();
|
||||
unconfirmedTransactions.removeIf(t -> t.getType() == Transaction.TransactionType.CHAT);
|
||||
unconfirmedTransactionsCache = unconfirmedTransactions;
|
||||
|
||||
// A list of signatures were imported in this round
|
||||
List<byte[]> newlyImportedSignatures = new ArrayList<>();
|
||||
|
||||
// Import transactions with valid signatures
|
||||
try {
|
||||
for (int i = 0; i < sigValidTransactions.size(); ++i) {
|
||||
@@ -286,6 +292,15 @@ public class TransactionImporter extends Thread {
|
||||
|
||||
case OK: {
|
||||
LOGGER.debug(() -> String.format("Imported %s transaction %s", transactionData.getType().name(), Base58.encode(transactionData.getSignature())));
|
||||
|
||||
// Add to the unconfirmed transactions cache
|
||||
if (transactionData.getType() != Transaction.TransactionType.CHAT && unconfirmedTransactionsCache != null) {
|
||||
unconfirmedTransactionsCache.add(transactionData);
|
||||
}
|
||||
|
||||
// Signature imported in this round
|
||||
newlyImportedSignatures.add(transactionData.getSignature());
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -314,9 +329,18 @@ public class TransactionImporter extends Thread {
|
||||
// Transaction has been processed, even if only to reject it
|
||||
removeIncomingTransaction(transactionData.getSignature());
|
||||
}
|
||||
|
||||
if (!newlyImportedSignatures.isEmpty()) {
|
||||
LOGGER.debug("Broadcasting {} newly imported signatures", newlyImportedSignatures.size());
|
||||
Message newTransactionSignatureMessage = new TransactionSignaturesMessage(newlyImportedSignatures);
|
||||
Network.getInstance().broadcast(broadcastPeer -> newTransactionSignatureMessage);
|
||||
}
|
||||
} finally {
|
||||
LOGGER.debug("Finished importing {} incoming transaction{}", processedCount, (processedCount == 1 ? "" : "s"));
|
||||
blockchainLock.unlock();
|
||||
|
||||
// Clear the unconfirmed transaction cache so new data can be populated in the next cycle
|
||||
unconfirmedTransactionsCache = null;
|
||||
}
|
||||
} catch (DataException e) {
|
||||
LOGGER.error("Repository issue while importing incoming transactions", e);
|
||||
|
||||
@@ -275,7 +275,10 @@ public class ArbitraryDataManager extends Thread {
|
||||
int offset = 0;
|
||||
|
||||
while (!isStopping) {
|
||||
Thread.sleep(1000L);
|
||||
final int minSeconds = 3;
|
||||
final int maxSeconds = 10;
|
||||
final int randomSleepTime = new Random().nextInt((maxSeconds - minSeconds + 1)) + minSeconds;
|
||||
Thread.sleep(randomSleepTime * 1000L);
|
||||
|
||||
// Any arbitrary transactions we want to fetch data for?
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
@@ -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<>();
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.qortal.data.transaction.MessageTransactionData;
|
||||
import org.qortal.group.Group;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.transaction.DeployAtTransaction;
|
||||
import org.qortal.transaction.MessageTransaction;
|
||||
import org.qortal.transaction.Transaction.ValidationResult;
|
||||
@@ -317,20 +318,36 @@ public class BitcoinACCTv3TradeBot implements AcctTradeBot {
|
||||
|
||||
boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData);
|
||||
if (!isMessageAlreadySent) {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
// Do this in a new thread so caller doesn't have to wait for computeNonce()
|
||||
// In the unlikely event that the transaction doesn't validate then the buy won't happen and eventually Alice's AT will be refunded
|
||||
new Thread(() -> {
|
||||
try (final Repository threadsRepository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(threadsRepository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(threadsRepository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
|
||||
messageTransaction.computeNonce();
|
||||
messageTransaction.sign(sender);
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
messageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty());
|
||||
messageTransaction.sign(sender);
|
||||
|
||||
// reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
// reset repository state to prevent deadlock
|
||||
threadsRepository.discardChanges();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name()));
|
||||
return ResponseResult.NETWORK_ISSUE;
|
||||
}
|
||||
if (messageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: signature invalid", messageRecipient));
|
||||
}
|
||||
} catch (DataException e) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, e.getMessage()));
|
||||
}
|
||||
}, "TradeBot response").start();
|
||||
}
|
||||
|
||||
TradeBot.updateTradeBotState(repository, tradeBotData, () -> String.format("Funding P2SH-A %s. Messaged Bob. Waiting for AT-lock", p2shAddress));
|
||||
@@ -548,15 +565,25 @@ public class BitcoinACCTv3TradeBot implements AcctTradeBot {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction outgoingMessageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, outgoingMessageData, false, false);
|
||||
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", outgoingMessageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
outgoingMessageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) outgoingMessageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), outgoingMessageTransaction.getPoWDifficulty());
|
||||
outgoingMessageTransaction.sign(sender);
|
||||
|
||||
// reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
if (outgoingMessageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -668,15 +695,25 @@ public class BitcoinACCTv3TradeBot implements AcctTradeBot {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
messageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty());
|
||||
messageTransaction.sign(sender);
|
||||
|
||||
// Reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
if (messageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.qortal.data.transaction.MessageTransactionData;
|
||||
import org.qortal.group.Group;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.transaction.DeployAtTransaction;
|
||||
import org.qortal.transaction.MessageTransaction;
|
||||
import org.qortal.transaction.Transaction.ValidationResult;
|
||||
@@ -317,20 +318,36 @@ public class DigibyteACCTv3TradeBot implements AcctTradeBot {
|
||||
|
||||
boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData);
|
||||
if (!isMessageAlreadySent) {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
// Do this in a new thread so caller doesn't have to wait for computeNonce()
|
||||
// In the unlikely event that the transaction doesn't validate then the buy won't happen and eventually Alice's AT will be refunded
|
||||
new Thread(() -> {
|
||||
try (final Repository threadsRepository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(threadsRepository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(threadsRepository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
|
||||
messageTransaction.computeNonce();
|
||||
messageTransaction.sign(sender);
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
messageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty());
|
||||
messageTransaction.sign(sender);
|
||||
|
||||
// reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
// reset repository state to prevent deadlock
|
||||
threadsRepository.discardChanges();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name()));
|
||||
return ResponseResult.NETWORK_ISSUE;
|
||||
}
|
||||
if (messageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: signature invalid", messageRecipient));
|
||||
}
|
||||
} catch (DataException e) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, e.getMessage()));
|
||||
}
|
||||
}, "TradeBot response").start();
|
||||
}
|
||||
|
||||
TradeBot.updateTradeBotState(repository, tradeBotData, () -> String.format("Funding P2SH-A %s. Messaged Bob. Waiting for AT-lock", p2shAddress));
|
||||
@@ -548,15 +565,25 @@ public class DigibyteACCTv3TradeBot implements AcctTradeBot {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction outgoingMessageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, outgoingMessageData, false, false);
|
||||
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", outgoingMessageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
outgoingMessageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) outgoingMessageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), outgoingMessageTransaction.getPoWDifficulty());
|
||||
outgoingMessageTransaction.sign(sender);
|
||||
|
||||
// reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
if (outgoingMessageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -668,15 +695,25 @@ public class DigibyteACCTv3TradeBot implements AcctTradeBot {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
messageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty());
|
||||
messageTransaction.sign(sender);
|
||||
|
||||
// Reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
if (messageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.qortal.data.transaction.MessageTransactionData;
|
||||
import org.qortal.group.Group;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.transaction.DeployAtTransaction;
|
||||
import org.qortal.transaction.MessageTransaction;
|
||||
import org.qortal.transaction.Transaction.ValidationResult;
|
||||
@@ -317,20 +318,36 @@ public class DogecoinACCTv1TradeBot implements AcctTradeBot {
|
||||
|
||||
boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData);
|
||||
if (!isMessageAlreadySent) {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
// Do this in a new thread so caller doesn't have to wait for computeNonce()
|
||||
// In the unlikely event that the transaction doesn't validate then the buy won't happen and eventually Alice's AT will be refunded
|
||||
new Thread(() -> {
|
||||
try (final Repository threadsRepository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(threadsRepository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(threadsRepository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
|
||||
messageTransaction.computeNonce();
|
||||
messageTransaction.sign(sender);
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
messageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty());
|
||||
messageTransaction.sign(sender);
|
||||
|
||||
// reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
// reset repository state to prevent deadlock
|
||||
threadsRepository.discardChanges();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name()));
|
||||
return ResponseResult.NETWORK_ISSUE;
|
||||
}
|
||||
if (messageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: signature invalid", messageRecipient));
|
||||
}
|
||||
} catch (DataException e) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, e.getMessage()));
|
||||
}
|
||||
}, "TradeBot response").start();
|
||||
}
|
||||
|
||||
TradeBot.updateTradeBotState(repository, tradeBotData, () -> String.format("Funding P2SH-A %s. Messaged Bob. Waiting for AT-lock", p2shAddress));
|
||||
@@ -548,15 +565,25 @@ public class DogecoinACCTv1TradeBot implements AcctTradeBot {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction outgoingMessageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, outgoingMessageData, false, false);
|
||||
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", outgoingMessageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
outgoingMessageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) outgoingMessageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), outgoingMessageTransaction.getPoWDifficulty());
|
||||
outgoingMessageTransaction.sign(sender);
|
||||
|
||||
// reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
if (outgoingMessageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -668,15 +695,25 @@ public class DogecoinACCTv1TradeBot implements AcctTradeBot {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
messageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty());
|
||||
messageTransaction.sign(sender);
|
||||
|
||||
// Reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
if (messageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.qortal.data.transaction.MessageTransactionData;
|
||||
import org.qortal.group.Group;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.transaction.DeployAtTransaction;
|
||||
import org.qortal.transaction.MessageTransaction;
|
||||
import org.qortal.transaction.Transaction.ValidationResult;
|
||||
@@ -317,20 +318,36 @@ public class DogecoinACCTv3TradeBot implements AcctTradeBot {
|
||||
|
||||
boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData);
|
||||
if (!isMessageAlreadySent) {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
// Do this in a new thread so caller doesn't have to wait for computeNonce()
|
||||
// In the unlikely event that the transaction doesn't validate then the buy won't happen and eventually Alice's AT will be refunded
|
||||
new Thread(() -> {
|
||||
try (final Repository threadsRepository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(threadsRepository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(threadsRepository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
|
||||
messageTransaction.computeNonce();
|
||||
messageTransaction.sign(sender);
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
messageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty());
|
||||
messageTransaction.sign(sender);
|
||||
|
||||
// reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
// reset repository state to prevent deadlock
|
||||
threadsRepository.discardChanges();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name()));
|
||||
return ResponseResult.NETWORK_ISSUE;
|
||||
}
|
||||
if (messageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: signature invalid", messageRecipient));
|
||||
}
|
||||
} catch (DataException e) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, e.getMessage()));
|
||||
}
|
||||
}, "TradeBot response").start();
|
||||
}
|
||||
|
||||
TradeBot.updateTradeBotState(repository, tradeBotData, () -> String.format("Funding P2SH-A %s. Messaged Bob. Waiting for AT-lock", p2shAddress));
|
||||
@@ -548,15 +565,25 @@ public class DogecoinACCTv3TradeBot implements AcctTradeBot {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction outgoingMessageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, outgoingMessageData, false, false);
|
||||
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", outgoingMessageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
outgoingMessageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) outgoingMessageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), outgoingMessageTransaction.getPoWDifficulty());
|
||||
outgoingMessageTransaction.sign(sender);
|
||||
|
||||
// reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
if (outgoingMessageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -668,15 +695,25 @@ public class DogecoinACCTv3TradeBot implements AcctTradeBot {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
messageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty());
|
||||
messageTransaction.sign(sender);
|
||||
|
||||
// Reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
if (messageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,15 +325,24 @@ public class LitecoinACCTv3TradeBot implements AcctTradeBot {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(threadsRepository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(threadsRepository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
messageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty());
|
||||
messageTransaction.sign(sender);
|
||||
|
||||
// reset repository state to prevent deadlock
|
||||
threadsRepository.discardChanges();
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name()));
|
||||
if (messageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: signature invalid", messageRecipient));
|
||||
}
|
||||
} catch (DataException e) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, e.getMessage()));
|
||||
@@ -556,15 +565,25 @@ public class LitecoinACCTv3TradeBot implements AcctTradeBot {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction outgoingMessageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, outgoingMessageData, false, false);
|
||||
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", outgoingMessageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
outgoingMessageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) outgoingMessageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), outgoingMessageTransaction.getPoWDifficulty());
|
||||
outgoingMessageTransaction.sign(sender);
|
||||
|
||||
// reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
if (outgoingMessageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -676,15 +695,25 @@ public class LitecoinACCTv3TradeBot implements AcctTradeBot {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
messageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty());
|
||||
messageTransaction.sign(sender);
|
||||
|
||||
// Reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
if (messageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.qortal.data.transaction.MessageTransactionData;
|
||||
import org.qortal.group.Group;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.transaction.DeployAtTransaction;
|
||||
import org.qortal.transaction.MessageTransaction;
|
||||
import org.qortal.transaction.Transaction.ValidationResult;
|
||||
@@ -320,20 +321,36 @@ public class PirateChainACCTv3TradeBot implements AcctTradeBot {
|
||||
|
||||
boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData);
|
||||
if (!isMessageAlreadySent) {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
// Do this in a new thread so caller doesn't have to wait for computeNonce()
|
||||
// In the unlikely event that the transaction doesn't validate then the buy won't happen and eventually Alice's AT will be refunded
|
||||
new Thread(() -> {
|
||||
try (final Repository threadsRepository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(threadsRepository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(threadsRepository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
|
||||
messageTransaction.computeNonce();
|
||||
messageTransaction.sign(sender);
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
messageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty());
|
||||
messageTransaction.sign(sender);
|
||||
|
||||
// reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
// reset repository state to prevent deadlock
|
||||
threadsRepository.discardChanges();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name()));
|
||||
return ResponseResult.NETWORK_ISSUE;
|
||||
}
|
||||
if (messageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: signature invalid", messageRecipient));
|
||||
}
|
||||
} catch (DataException e) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, e.getMessage()));
|
||||
}
|
||||
}, "TradeBot response").start();
|
||||
}
|
||||
|
||||
TradeBot.updateTradeBotState(repository, tradeBotData, () -> String.format("Funding P2SH-A %s. Messaged Bob. Waiting for AT-lock", p2shAddressT3));
|
||||
@@ -561,15 +578,25 @@ public class PirateChainACCTv3TradeBot implements AcctTradeBot {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction outgoingMessageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, outgoingMessageData, false, false);
|
||||
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", outgoingMessageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
outgoingMessageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) outgoingMessageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), outgoingMessageTransaction.getPoWDifficulty());
|
||||
outgoingMessageTransaction.sign(sender);
|
||||
|
||||
// reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
if (outgoingMessageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -681,15 +708,25 @@ public class PirateChainACCTv3TradeBot implements AcctTradeBot {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
messageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty());
|
||||
messageTransaction.sign(sender);
|
||||
|
||||
// Reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
if (messageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.qortal.data.transaction.MessageTransactionData;
|
||||
import org.qortal.group.Group;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.transaction.DeployAtTransaction;
|
||||
import org.qortal.transaction.MessageTransaction;
|
||||
import org.qortal.transaction.Transaction.ValidationResult;
|
||||
@@ -317,20 +318,36 @@ public class RavencoinACCTv3TradeBot implements AcctTradeBot {
|
||||
|
||||
boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData);
|
||||
if (!isMessageAlreadySent) {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
// Do this in a new thread so caller doesn't have to wait for computeNonce()
|
||||
// In the unlikely event that the transaction doesn't validate then the buy won't happen and eventually Alice's AT will be refunded
|
||||
new Thread(() -> {
|
||||
try (final Repository threadsRepository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(threadsRepository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(threadsRepository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
|
||||
messageTransaction.computeNonce();
|
||||
messageTransaction.sign(sender);
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
messageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty());
|
||||
messageTransaction.sign(sender);
|
||||
|
||||
// reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
// reset repository state to prevent deadlock
|
||||
threadsRepository.discardChanges();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name()));
|
||||
return ResponseResult.NETWORK_ISSUE;
|
||||
}
|
||||
if (messageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name()));
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: signature invalid", messageRecipient));
|
||||
}
|
||||
} catch (DataException e) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, e.getMessage()));
|
||||
}
|
||||
}, "TradeBot response").start();
|
||||
}
|
||||
|
||||
TradeBot.updateTradeBotState(repository, tradeBotData, () -> String.format("Funding P2SH-A %s. Messaged Bob. Waiting for AT-lock", p2shAddress));
|
||||
@@ -548,15 +565,25 @@ public class RavencoinACCTv3TradeBot implements AcctTradeBot {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction outgoingMessageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, outgoingMessageData, false, false);
|
||||
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", outgoingMessageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
outgoingMessageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) outgoingMessageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), outgoingMessageTransaction.getPoWDifficulty());
|
||||
outgoingMessageTransaction.sign(sender);
|
||||
|
||||
// reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
if (outgoingMessageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -668,15 +695,25 @@ public class RavencoinACCTv3TradeBot implements AcctTradeBot {
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
||||
|
||||
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||
messageTransaction.computeNonce();
|
||||
MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData();
|
||||
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty());
|
||||
messageTransaction.sign(sender);
|
||||
|
||||
// Reset repository state to prevent deadlock
|
||||
repository.discardChanges();
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
if (messageTransaction.isSignatureValid()) {
|
||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||
|
||||
if (result != ValidationResult.OK) {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,7 +333,7 @@ public class TradeBot implements Listener {
|
||||
SysTray.getInstance().showMessage("Trade-Bot", String.format("%s: %s", tradeBotData.getAtAddress(), newState), MessageType.INFO);
|
||||
|
||||
if (logMessageSupplier != null)
|
||||
LOGGER.info(logMessageSupplier);
|
||||
LOGGER.info(logMessageSupplier.get());
|
||||
|
||||
LOGGER.debug(() -> String.format("new state for trade-bot entry based on AT %s: %s", tradeBotData.getAtAddress(), newState));
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -314,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.
|
||||
|
||||
@@ -1429,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 (");
|
||||
@@ -1446,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 = 50;
|
||||
|
||||
/** 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). */
|
||||
@@ -216,7 +227,7 @@ public class Settings {
|
||||
private int maxRetries = 2;
|
||||
|
||||
/** The number of seconds of no activity before recovery mode begins */
|
||||
public long recoveryModeTimeout = 24 * 60 * 60 * 1000L;
|
||||
public long recoveryModeTimeout = 9999999999999L;
|
||||
|
||||
/** Minimum peer version number required in order to sync with them */
|
||||
private String minPeerVersion = "4.1.2";
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.qortal.transaction;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -8,7 +7,6 @@ import java.util.stream.Collectors;
|
||||
import org.qortal.account.Account;
|
||||
import org.qortal.block.BlockChain;
|
||||
import org.qortal.controller.arbitrary.ArbitraryDataManager;
|
||||
import org.qortal.controller.arbitrary.ArbitraryDataStorageManager;
|
||||
import org.qortal.controller.repository.NamesDatabaseIntegrityCheck;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.crypto.MemoryPoW;
|
||||
@@ -88,8 +86,14 @@ public class ArbitraryTransaction extends Transaction {
|
||||
if (this.transactionData.getFee() < 0)
|
||||
return ValidationResult.NEGATIVE_FEE;
|
||||
|
||||
// After the feature trigger, we require the fee to be sufficient if it's not 0.
|
||||
// If the fee is zero, then the nonce is validated in isSignatureValid() as an alternative to a fee
|
||||
// As of the mempow transaction updates timestamp, a nonce is no longer supported, so a valid fee must be included
|
||||
if (this.transactionData.getTimestamp() >= BlockChain.getInstance().getMemPoWTransactionUpdatesTimestamp()) {
|
||||
// Validate the fee
|
||||
return super.isFeeValid();
|
||||
}
|
||||
|
||||
// After the earlier "optional fee" feature trigger, we required the fee to be sufficient if it wasn't 0.
|
||||
// If the fee was zero, then the nonce was validated in isSignatureValid() as an alternative to a fee
|
||||
if (this.arbitraryTransactionData.getTimestamp() >= BlockChain.getInstance().getArbitraryOptionalFeeTimestamp() && this.arbitraryTransactionData.getFee() != 0L) {
|
||||
return super.isFeeValid();
|
||||
}
|
||||
@@ -214,7 +218,13 @@ public class ArbitraryTransaction extends Transaction {
|
||||
// Clear nonce from transactionBytes
|
||||
ArbitraryTransactionTransformer.clearNonce(transactionBytes);
|
||||
|
||||
// As of feature-trigger timestamp, we only require a nonce when the fee is zero
|
||||
// As of the mempow transaction updates timestamp, a nonce is no longer supported, so a fee must be included
|
||||
if (this.transactionData.getTimestamp() >= BlockChain.getInstance().getMemPoWTransactionUpdatesTimestamp()) {
|
||||
// Require that the fee is a positive number. Fee checking itself is performed in isFeeValid()
|
||||
return (this.arbitraryTransactionData.getFee() > 0L);
|
||||
}
|
||||
|
||||
// As of the earlier "optional fee" feature-trigger timestamp, we only required a nonce when the fee was zero
|
||||
boolean beforeFeatureTrigger = this.arbitraryTransactionData.getTimestamp() < BlockChain.getInstance().getArbitraryOptionalFeeTimestamp();
|
||||
if (beforeFeatureTrigger || this.arbitraryTransactionData.getFee() == 0L) {
|
||||
// We only need to check nonce for recent transactions due to PoW verification overhead
|
||||
|
||||
@@ -148,6 +148,12 @@ public class ChatTransaction extends Transaction {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfirmable() {
|
||||
// CHAT transactions can't go into blocks
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValidationResult isValid() throws DataException {
|
||||
// Nonce checking is done via isSignatureValid() as that method is only called once per import
|
||||
|
||||
@@ -33,7 +33,9 @@ public class MessageTransaction extends Transaction {
|
||||
|
||||
public static final int MAX_DATA_SIZE = 4000;
|
||||
public static final int POW_BUFFER_SIZE = 8 * 1024 * 1024; // bytes
|
||||
public static final int POW_DIFFICULTY = 14; // leading zero bits
|
||||
public static final int POW_DIFFICULTY_V1 = 14; // leading zero bits
|
||||
public static final int POW_DIFFICULTY_V2_CONFIRMABLE = 16; // leading zero bits
|
||||
public static final int POW_DIFFICULTY_V2_UNCONFIRMABLE = 12; // leading zero bits
|
||||
|
||||
// Properties
|
||||
|
||||
@@ -109,7 +111,17 @@ public class MessageTransaction extends Transaction {
|
||||
MessageTransactionTransformer.clearNonce(transactionBytes);
|
||||
|
||||
// Calculate nonce
|
||||
this.messageTransactionData.setNonce(MemoryPoW.compute2(transactionBytes, POW_BUFFER_SIZE, POW_DIFFICULTY));
|
||||
this.messageTransactionData.setNonce(MemoryPoW.compute2(transactionBytes, POW_BUFFER_SIZE, getPoWDifficulty()));
|
||||
}
|
||||
|
||||
public int getPoWDifficulty() {
|
||||
// The difficulty changes at the "mempow transactions updates" timestamp
|
||||
if (this.transactionData.getTimestamp() >= BlockChain.getInstance().getMemPoWTransactionUpdatesTimestamp()) {
|
||||
// If this message is confirmable then require a higher difficulty
|
||||
return this.isConfirmable() ? POW_DIFFICULTY_V2_CONFIRMABLE : POW_DIFFICULTY_V2_UNCONFIRMABLE;
|
||||
}
|
||||
// Before feature trigger timestamp, so use existing difficulty value
|
||||
return POW_DIFFICULTY_V1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,6 +195,18 @@ public class MessageTransaction extends Transaction {
|
||||
return super.hasValidReference();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfirmable() {
|
||||
// After feature trigger timestamp, only messages to an AT address can confirm
|
||||
if (this.transactionData.getTimestamp() >= BlockChain.getInstance().getMemPoWTransactionUpdatesTimestamp()) {
|
||||
if (this.messageTransactionData.getRecipient() == null || !this.messageTransactionData.getRecipient().toUpperCase().startsWith("A")) {
|
||||
// Message isn't to an AT address, so this transaction is unconfirmable
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValidationResult isValid() throws DataException {
|
||||
// Nonce checking is done via isSignatureValid() as that method is only called once per import
|
||||
@@ -235,7 +259,7 @@ public class MessageTransaction extends Transaction {
|
||||
MessageTransactionTransformer.clearNonce(transactionBytes);
|
||||
|
||||
// Check nonce
|
||||
return MemoryPoW.verify2(transactionBytes, POW_BUFFER_SIZE, POW_DIFFICULTY, nonce);
|
||||
return MemoryPoW.verify2(transactionBytes, POW_BUFFER_SIZE, getPoWDifficulty(), nonce);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -256,6 +280,11 @@ public class MessageTransaction extends Transaction {
|
||||
|
||||
@Override
|
||||
public void process() throws DataException {
|
||||
// Only certain MESSAGE transactions are able to confirm
|
||||
if (!this.isConfirmable()) {
|
||||
throw new DataException("Unconfirmable MESSAGE transactions should never be processed");
|
||||
}
|
||||
|
||||
// If we have no amount then there's nothing to do
|
||||
if (this.messageTransactionData.getAmount() == 0L)
|
||||
return;
|
||||
@@ -280,6 +309,11 @@ public class MessageTransaction extends Transaction {
|
||||
|
||||
@Override
|
||||
public void orphan() throws DataException {
|
||||
// Only certain MESSAGE transactions are able to confirm
|
||||
if (!this.isConfirmable()) {
|
||||
throw new DataException("Unconfirmable MESSAGE transactions should never be orphaned");
|
||||
}
|
||||
|
||||
// If we have no amount then there's nothing to do
|
||||
if (this.messageTransactionData.getAmount() == 0L)
|
||||
return;
|
||||
|
||||
@@ -155,6 +155,12 @@ public class PresenceTransaction extends Transaction {
|
||||
// Nothing to do
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConfirmable() {
|
||||
// PRESENCE transactions can't go into blocks
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValidationResult isValid() throws DataException {
|
||||
// Nonce checking is done via isSignatureValid() as that method is only called once per import
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.qortal.account.Account;
|
||||
import org.qortal.account.PublicKeyAccount;
|
||||
import org.qortal.api.resource.TransactionsResource.ConfirmationStatus;
|
||||
import org.qortal.asset.Asset;
|
||||
import org.qortal.block.BlockChain;
|
||||
import org.qortal.crypto.MemoryPoW;
|
||||
import org.qortal.data.transaction.PublicizeTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
@@ -89,6 +90,12 @@ public class PublicizeTransaction extends Transaction {
|
||||
|
||||
@Override
|
||||
public ValidationResult isValid() throws DataException {
|
||||
// Disable completely after feature-trigger timestamp, at the same time that mempow difficulties are being increased.
|
||||
// It could be enabled again in the future, but preferably with an enforced minimum fee instead of allowing a mempow nonce.
|
||||
if (this.transactionData.getTimestamp() >= BlockChain.getInstance().getMemPoWTransactionUpdatesTimestamp()) {
|
||||
return ValidationResult.NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
// There can be only one
|
||||
List<byte[]> signatures = this.repository.getTransactionRepository().getSignaturesMatchingCriteria(
|
||||
TransactionType.PUBLICIZE,
|
||||
|
||||
@@ -13,6 +13,7 @@ import org.qortal.account.PublicKeyAccount;
|
||||
import org.qortal.asset.Asset;
|
||||
import org.qortal.block.BlockChain;
|
||||
import org.qortal.controller.Controller;
|
||||
import org.qortal.controller.TransactionImporter;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.data.block.BlockData;
|
||||
import org.qortal.data.group.GroupApprovalData;
|
||||
@@ -247,7 +248,8 @@ public abstract class Transaction {
|
||||
GROUP_APPROVAL_REQUIRED(98),
|
||||
ACCOUNT_NOT_TRANSFERABLE(99),
|
||||
INVALID_BUT_OK(999),
|
||||
NOT_YET_RELEASED(1000);
|
||||
NOT_YET_RELEASED(1000),
|
||||
NOT_SUPPORTED(1001);
|
||||
|
||||
public final int value;
|
||||
|
||||
@@ -377,7 +379,7 @@ public abstract class Transaction {
|
||||
* @return
|
||||
*/
|
||||
public long getUnitFee(Long timestamp) {
|
||||
return BlockChain.getInstance().getUnitFee();
|
||||
return BlockChain.getInstance().getUnitFeeAtTimestamp(timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -617,7 +619,10 @@ public abstract class Transaction {
|
||||
}
|
||||
|
||||
private int countUnconfirmedByCreator(PublicKeyAccount creator) throws DataException {
|
||||
List<TransactionData> unconfirmedTransactions = repository.getTransactionRepository().getUnconfirmedTransactions();
|
||||
List<TransactionData> unconfirmedTransactions = TransactionImporter.getInstance().unconfirmedTransactionsCache;
|
||||
if (unconfirmedTransactions == null) {
|
||||
unconfirmedTransactions = repository.getTransactionRepository().getUnconfirmedTransactions();
|
||||
}
|
||||
|
||||
// We exclude CHAT transactions as they never get included into blocks and
|
||||
// have spam/DoS prevention by requiring proof of work
|
||||
@@ -632,7 +637,7 @@ public abstract class Transaction {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns sorted, unconfirmed transactions, excluding invalid.
|
||||
* Returns sorted, unconfirmed transactions, excluding invalid and unconfirmable.
|
||||
*
|
||||
* @return sorted, unconfirmed transactions
|
||||
* @throws DataException
|
||||
@@ -641,7 +646,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());
|
||||
|
||||
@@ -650,7 +655,8 @@ public abstract class Transaction {
|
||||
TransactionData transactionData = unconfirmedTransactionsIterator.next();
|
||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||
|
||||
if (transaction.isStillValidUnconfirmed(latestBlockData.getTimestamp()) != ValidationResult.OK)
|
||||
// Must be confirmable and valid
|
||||
if (!transaction.isConfirmable() || transaction.isStillValidUnconfirmed(latestBlockData.getTimestamp()) != ValidationResult.OK)
|
||||
unconfirmedTransactionsIterator.remove();
|
||||
}
|
||||
|
||||
@@ -888,6 +894,17 @@ public abstract class Transaction {
|
||||
/* To be optionally overridden */
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether transaction is 'confirmable' - i.e. is of a type that
|
||||
* can be included in a block. Some transactions are 'unconfirmable'
|
||||
* and therefore must remain in the mempool until they expire.
|
||||
* @return
|
||||
*/
|
||||
public boolean isConfirmable() {
|
||||
/* To be optionally overridden */
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether transaction can be added to the blockchain.
|
||||
* <p>
|
||||
|
||||
@@ -3,8 +3,12 @@
|
||||
"transactionExpiryPeriod": 86400000,
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.001",
|
||||
"unitFees": [
|
||||
{ "timestamp": 0, "fee": "0.001" },
|
||||
{ "timestamp": 1692118800000, "fee": "0.01" }
|
||||
],
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 0, "fee": "0.001" },
|
||||
{ "timestamp": 1645372800000, "fee": "5" },
|
||||
{ "timestamp": 1651420800000, "fee": "1.25" }
|
||||
],
|
||||
@@ -25,6 +29,7 @@
|
||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||
"onlineAccountsModulusV2Timestamp": 1659801600000,
|
||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 1670230000000,
|
||||
"mempowTransactionUpdatesTimestamp": 9999999999999,
|
||||
"rewardsByHeight": [
|
||||
{ "height": 1, "reward": 5.00 },
|
||||
{ "height": 259201, "reward": 4.75 },
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package org.qortal.test;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.qortal.crypto.MemoryPoW;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.test.common.Common;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
@@ -39,13 +41,14 @@ public class MemoryPoWTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleComputes() {
|
||||
public void testMultipleComputes() throws DataException {
|
||||
Common.useDefaultSettings();
|
||||
Random random = new Random();
|
||||
|
||||
final int sampleSize = 20;
|
||||
final int sampleSize = 10;
|
||||
final long stddevDivisor = sampleSize * (sampleSize - 1);
|
||||
|
||||
for (int difficulty = 8; difficulty < 16; difficulty += 2) {
|
||||
for (int difficulty = 8; difficulty <= 16; difficulty++) {
|
||||
byte[] data = new byte[256];
|
||||
long[] times = new long[sampleSize];
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.qortal.test;
|
||||
|
||||
import org.apache.commons.lang3.reflect.FieldUtils;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
@@ -14,12 +15,9 @@ import org.qortal.group.Group.ApprovalThreshold;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.test.common.BlockUtils;
|
||||
import org.qortal.test.common.Common;
|
||||
import org.qortal.test.common.GroupUtils;
|
||||
import org.qortal.test.common.TestAccount;
|
||||
import org.qortal.test.common.TransactionUtils;
|
||||
import org.qortal.test.common.*;
|
||||
import org.qortal.test.common.transaction.TestTransaction;
|
||||
import org.qortal.transaction.DeployAtTransaction;
|
||||
import org.qortal.transaction.MessageTransaction;
|
||||
import org.qortal.transaction.Transaction;
|
||||
import org.qortal.transaction.Transaction.TransactionType;
|
||||
@@ -31,6 +29,7 @@ import org.qortal.utils.NTP;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class MessageTests extends Common {
|
||||
@@ -85,7 +84,7 @@ public class MessageTests extends Common {
|
||||
byte[] randomReference = new byte[64];
|
||||
random.nextBytes(randomReference);
|
||||
|
||||
long minimumFee = BlockChain.getInstance().getUnitFee();
|
||||
long minimumFee = BlockChain.getInstance().getUnitFeeAtTimestamp(System.currentTimeMillis());
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
@@ -139,7 +138,7 @@ public class MessageTests extends Common {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withRecipentWithAmount() throws DataException {
|
||||
public void withRecipientWithAmount() throws DataException {
|
||||
testMessage(Group.NO_GROUP, recipient, 123L, Asset.QORT);
|
||||
}
|
||||
|
||||
@@ -153,6 +152,140 @@ public class MessageTests extends Common {
|
||||
testMessage(1, null, 0L, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void atRecipientNoFeeWithNonce() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String atRecipient = deployAt();
|
||||
MessageTransaction transaction = testFeeNonce(repository, false, true, atRecipient, true);
|
||||
|
||||
// Transaction should be confirmable because it's to an AT, and therefore should be present in a block
|
||||
assertTrue(transaction.isConfirmable());
|
||||
TransactionUtils.signAndMint(repository, transaction.getTransactionData(), alice);
|
||||
assertTrue(isTransactionConfirmed(repository, transaction));
|
||||
assertEquals(16, transaction.getPoWDifficulty());
|
||||
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void regularRecipientNoFeeWithNonce() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
|
||||
// Transaction should not be present in db yet
|
||||
List<MessageTransactionData> messageTransactionsData = repository.getMessageRepository().getMessagesByParticipants(null, recipient, null, null, null);
|
||||
assertTrue(messageTransactionsData.isEmpty());
|
||||
|
||||
MessageTransaction transaction = testFeeNonce(repository, false, true, recipient, true);
|
||||
|
||||
// Transaction shouldn't be confirmable because it's not to an AT, and therefore shouldn't be present in a block
|
||||
assertFalse(transaction.isConfirmable());
|
||||
TransactionUtils.signAndMint(repository, transaction.getTransactionData(), alice);
|
||||
assertFalse(isTransactionConfirmed(repository, transaction));
|
||||
assertEquals(12, transaction.getPoWDifficulty());
|
||||
|
||||
// Transaction should be found when trade bot searches for it
|
||||
messageTransactionsData = repository.getMessageRepository().getMessagesByParticipants(null, recipient, null, null, null);
|
||||
assertEquals(1, messageTransactionsData.size());
|
||||
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noRecipientNoFeeWithNonce() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
|
||||
MessageTransaction transaction = testFeeNonce(repository, false, true, null, true);
|
||||
|
||||
// Transaction shouldn't be confirmable because it's not to an AT, and therefore shouldn't be present in a block
|
||||
assertFalse(transaction.isConfirmable());
|
||||
TransactionUtils.signAndMint(repository, transaction.getTransactionData(), alice);
|
||||
assertFalse(isTransactionConfirmed(repository, transaction));
|
||||
assertEquals(12, transaction.getPoWDifficulty());
|
||||
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void atRecipientWithFeeNoNonce() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String atRecipient = deployAt();
|
||||
MessageTransaction transaction = testFeeNonce(repository, true, false, atRecipient, true);
|
||||
|
||||
// Transaction should be confirmable because it's to an AT, and therefore should be present in a block
|
||||
assertTrue(transaction.isConfirmable());
|
||||
TransactionUtils.signAndMint(repository, transaction.getTransactionData(), alice);
|
||||
assertTrue(isTransactionConfirmed(repository, transaction));
|
||||
assertEquals(16, transaction.getPoWDifficulty());
|
||||
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void regularRecipientWithFeeNoNonce() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
|
||||
MessageTransaction transaction = testFeeNonce(repository, true, false, recipient, true);
|
||||
|
||||
// Transaction shouldn't be confirmable because it's not to an AT, and therefore shouldn't be present in a block
|
||||
assertFalse(transaction.isConfirmable());
|
||||
TransactionUtils.signAndMint(repository, transaction.getTransactionData(), alice);
|
||||
assertFalse(isTransactionConfirmed(repository, transaction));
|
||||
assertEquals(12, transaction.getPoWDifficulty());
|
||||
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void atRecipientNoFeeWithNonceLegacyDifficulty() throws DataException, IllegalAccessException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
// Set mempowTransactionUpdatesTimestamp to a high value, so that it hasn't activated key
|
||||
FieldUtils.writeField(BlockChain.getInstance(), "mempowTransactionUpdatesTimestamp", Long.MAX_VALUE, true);
|
||||
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String atRecipient = deployAt();
|
||||
MessageTransaction transaction = testFeeNonce(repository, false, true, atRecipient, true);
|
||||
|
||||
// Transaction should be confirmable because all MESSAGE transactions confirmed prior to the feature trigger
|
||||
assertTrue(transaction.isConfirmable());
|
||||
TransactionUtils.signAndMint(repository, transaction.getTransactionData(), alice);
|
||||
assertTrue(isTransactionConfirmed(repository, transaction));
|
||||
assertEquals(14, transaction.getPoWDifficulty()); // Legacy difficulty was 14 in all cases
|
||||
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void regularRecipientNoFeeWithNonceLegacyDifficulty() throws DataException, IllegalAccessException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
// Set mempowTransactionUpdatesTimestamp to a high value, so that it hasn't activated key
|
||||
FieldUtils.writeField(BlockChain.getInstance(), "mempowTransactionUpdatesTimestamp", Long.MAX_VALUE, true);
|
||||
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
MessageTransaction transaction = testFeeNonce(repository, false, true, recipient, true);
|
||||
|
||||
// Transaction should be confirmable because all MESSAGE transactions confirmed prior to the feature trigger
|
||||
assertTrue(transaction.isConfirmable());
|
||||
TransactionUtils.signAndMint(repository, transaction.getTransactionData(), alice);
|
||||
assertTrue(isTransactionConfirmed(repository, transaction)); // All MESSAGE transactions would confirm before feature trigger
|
||||
assertEquals(14, transaction.getPoWDifficulty()); // Legacy difficulty was 14 in all cases
|
||||
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serializationTests() throws DataException, TransformationException {
|
||||
// with recipient, with amount
|
||||
@@ -165,6 +298,24 @@ public class MessageTests extends Common {
|
||||
testSerialization(null, 0L, null);
|
||||
}
|
||||
|
||||
private String deployAt() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "alice");
|
||||
byte[] creationBytes = AtUtils.buildSimpleAT();
|
||||
long fundingAmount = 1_00000000L;
|
||||
DeployAtTransaction deployAtTransaction = AtUtils.doDeployAT(repository, deployer, creationBytes, fundingAmount);
|
||||
|
||||
String address = deployAtTransaction.getATAccount().getAddress();
|
||||
assertNotNull(address);
|
||||
return address;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isTransactionConfirmed(Repository repository, MessageTransaction transaction) throws DataException {
|
||||
TransactionData queriedTransactionData = repository.getTransactionRepository().fromSignature(transaction.getTransactionData().getSignature());
|
||||
return queriedTransactionData.getBlockHeight() != null && queriedTransactionData.getBlockHeight() > 0;
|
||||
}
|
||||
|
||||
private boolean isValid(int txGroupId, String recipient, long amount, Long assetId) throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
TestAccount alice = Common.getTestAccount(repository, "alice");
|
||||
@@ -195,41 +346,48 @@ public class MessageTests extends Common {
|
||||
return messageTransaction.hasValidReference();
|
||||
}
|
||||
|
||||
private void testFeeNonce(boolean withFee, boolean withNonce, boolean isValid) throws DataException {
|
||||
|
||||
private MessageTransaction testFeeNonce(boolean withFee, boolean withNonce, boolean isValid) throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
TestAccount alice = Common.getTestAccount(repository, "alice");
|
||||
|
||||
int txGroupId = 0;
|
||||
int nonce = 0;
|
||||
long amount = 0;
|
||||
long assetId = Asset.QORT;
|
||||
byte[] data = new byte[1];
|
||||
boolean isText = false;
|
||||
boolean isEncrypted = false;
|
||||
|
||||
MessageTransactionData transactionData = new MessageTransactionData(TestTransaction.generateBase(alice, txGroupId),
|
||||
version, nonce, recipient, amount, assetId, data, isText, isEncrypted);
|
||||
|
||||
MessageTransaction transaction = new MessageTransaction(repository, transactionData);
|
||||
|
||||
if (withFee)
|
||||
transactionData.setFee(transaction.calcRecommendedFee());
|
||||
else
|
||||
transactionData.setFee(0L);
|
||||
|
||||
if (withNonce) {
|
||||
transaction.computeNonce();
|
||||
} else {
|
||||
transactionData.setNonce(-1);
|
||||
}
|
||||
|
||||
transaction.sign(alice);
|
||||
|
||||
assertEquals(isValid, transaction.isSignatureValid());
|
||||
return testFeeNonce(repository, withFee, withNonce, recipient, isValid);
|
||||
}
|
||||
}
|
||||
|
||||
private void testMessage(int txGroupId, String recipient, long amount, Long assetId) throws DataException {
|
||||
private MessageTransaction testFeeNonce(Repository repository, boolean withFee, boolean withNonce, String recipient, boolean isValid) throws DataException {
|
||||
TestAccount alice = Common.getTestAccount(repository, "alice");
|
||||
|
||||
int txGroupId = 0;
|
||||
int nonce = 0;
|
||||
long amount = 0;
|
||||
long assetId = Asset.QORT;
|
||||
byte[] data = new byte[1];
|
||||
boolean isText = false;
|
||||
boolean isEncrypted = false;
|
||||
|
||||
MessageTransactionData transactionData = new MessageTransactionData(TestTransaction.generateBase(alice, txGroupId),
|
||||
version, nonce, recipient, amount, assetId, data, isText, isEncrypted);
|
||||
|
||||
MessageTransaction transaction = new MessageTransaction(repository, transactionData);
|
||||
|
||||
if (withFee)
|
||||
transactionData.setFee(transaction.calcRecommendedFee());
|
||||
else
|
||||
transactionData.setFee(0L);
|
||||
|
||||
if (withNonce) {
|
||||
transaction.computeNonce();
|
||||
} else {
|
||||
transactionData.setNonce(-1);
|
||||
}
|
||||
|
||||
transaction.sign(alice);
|
||||
|
||||
assertEquals(isValid, transaction.isSignatureValid());
|
||||
|
||||
return transaction;
|
||||
}
|
||||
|
||||
private MessageTransaction testMessage(int txGroupId, String recipient, long amount, Long assetId) throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
TestAccount alice = Common.getTestAccount(repository, "alice");
|
||||
|
||||
@@ -244,6 +402,8 @@ public class MessageTests extends Common {
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
|
||||
return new MessageTransaction(repository, transactionData);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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=";
|
||||
|
||||
@@ -11,6 +11,7 @@ import org.qortal.arbitrary.ArbitraryDataReader;
|
||||
import org.qortal.arbitrary.exception.MissingDataException;
|
||||
import org.qortal.arbitrary.misc.Category;
|
||||
import org.qortal.arbitrary.misc.Service;
|
||||
import org.qortal.block.BlockChain;
|
||||
import org.qortal.controller.arbitrary.ArbitraryDataManager;
|
||||
import org.qortal.data.arbitrary.ArbitraryResourceMetadata;
|
||||
import org.qortal.data.transaction.ArbitraryTransactionData;
|
||||
@@ -24,6 +25,7 @@ import org.qortal.test.common.TransactionUtils;
|
||||
import org.qortal.test.common.transaction.TestTransaction;
|
||||
import org.qortal.transaction.RegisterNameTransaction;
|
||||
import org.qortal.utils.Base58;
|
||||
import org.qortal.utils.NTP;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
@@ -106,8 +108,9 @@ public class ArbitraryTransactionMetadataTests extends Common {
|
||||
|
||||
// Create PUT transaction
|
||||
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength);
|
||||
long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime());
|
||||
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name,
|
||||
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, 0L, true,
|
||||
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, fee, false,
|
||||
title, description, tags, category);
|
||||
|
||||
// Check the chunk count is correct
|
||||
@@ -157,8 +160,9 @@ public class ArbitraryTransactionMetadataTests extends Common {
|
||||
|
||||
// Create PUT transaction
|
||||
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength);
|
||||
long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime());
|
||||
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name,
|
||||
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, 0L, true,
|
||||
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, fee, false,
|
||||
title, description, tags, category);
|
||||
|
||||
// Check the chunk count is correct
|
||||
@@ -220,8 +224,9 @@ public class ArbitraryTransactionMetadataTests extends Common {
|
||||
|
||||
// Create PUT transaction
|
||||
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength);
|
||||
long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime());
|
||||
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name,
|
||||
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, 0L, true,
|
||||
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, fee, false,
|
||||
title, description, tags, category);
|
||||
|
||||
// Check the chunk count is correct
|
||||
@@ -272,8 +277,9 @@ public class ArbitraryTransactionMetadataTests extends Common {
|
||||
|
||||
// Create PUT transaction
|
||||
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength);
|
||||
long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime());
|
||||
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name,
|
||||
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, 0L, true,
|
||||
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, fee, false,
|
||||
title, description, tags, category);
|
||||
|
||||
// Check the chunk count is correct
|
||||
@@ -316,8 +322,9 @@ public class ArbitraryTransactionMetadataTests extends Common {
|
||||
|
||||
// Create PUT transaction
|
||||
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength);
|
||||
long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime());
|
||||
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name,
|
||||
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, 0L, true,
|
||||
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, fee, false,
|
||||
title, description, tags, category);
|
||||
|
||||
// Check the metadata is correct
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.qortal.arbitrary.ArbitraryDataTransactionBuilder;
|
||||
import org.qortal.arbitrary.exception.MissingDataException;
|
||||
import org.qortal.arbitrary.misc.Category;
|
||||
import org.qortal.arbitrary.misc.Service;
|
||||
import org.qortal.block.BlockChain;
|
||||
import org.qortal.controller.arbitrary.ArbitraryDataManager;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.data.PaymentData;
|
||||
@@ -50,51 +51,6 @@ public class ArbitraryTransactionTests extends Common {
|
||||
Common.useDefaultSettings();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDifficultyTooLow() throws IllegalAccessException, DataException, IOException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String publicKey58 = Base58.encode(alice.getPublicKey());
|
||||
String name = "TEST"; // Can be anything for this test
|
||||
String identifier = null; // Not used for this test
|
||||
Service service = Service.ARBITRARY_DATA;
|
||||
int chunkSize = 100;
|
||||
int dataLength = 900; // Actual data length will be longer due to encryption
|
||||
|
||||
// Register the name to Alice
|
||||
RegisterNameTransactionData registerNameTransactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, "");
|
||||
registerNameTransactionData.setFee(new RegisterNameTransaction(null, null).getUnitFee(registerNameTransactionData.getTimestamp()));
|
||||
TransactionUtils.signAndMint(repository, registerNameTransactionData, alice);
|
||||
|
||||
// Set difficulty to 1
|
||||
FieldUtils.writeField(ArbitraryDataManager.getInstance(), "powDifficulty", 1, true);
|
||||
|
||||
// Create PUT transaction
|
||||
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength);
|
||||
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name, identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize);
|
||||
|
||||
// Check that nonce validation succeeds
|
||||
byte[] signature = arbitraryDataFile.getSignature();
|
||||
TransactionData transactionData = repository.getTransactionRepository().fromSignature(signature);
|
||||
ArbitraryTransaction transaction = new ArbitraryTransaction(repository, transactionData);
|
||||
assertTrue(transaction.isSignatureValid());
|
||||
|
||||
// Increase difficulty to 15
|
||||
FieldUtils.writeField(ArbitraryDataManager.getInstance(), "powDifficulty", 15, true);
|
||||
|
||||
// Make sure the nonce validation fails
|
||||
// Note: there is a very tiny chance this could succeed due to being extremely lucky
|
||||
// and finding a high difficulty nonce in the first couple of cycles. It will be rare
|
||||
// enough that we shouldn't need to account for it.
|
||||
assertFalse(transaction.isSignatureValid());
|
||||
|
||||
// Reduce difficulty back to 1, to double check
|
||||
FieldUtils.writeField(ArbitraryDataManager.getInstance(), "powDifficulty", 1, true);
|
||||
assertTrue(transaction.isSignatureValid());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonceAndFee() throws IllegalAccessException, DataException, IOException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
@@ -497,8 +453,9 @@ public class ArbitraryTransactionTests extends Common {
|
||||
|
||||
// Create PUT transaction
|
||||
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength, true);
|
||||
long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime());
|
||||
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name,
|
||||
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, 0L, true,
|
||||
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, fee, false,
|
||||
null, null, null, null);
|
||||
|
||||
byte[] signature = arbitraryDataFile.getSignature();
|
||||
@@ -556,8 +513,9 @@ public class ArbitraryTransactionTests extends Common {
|
||||
|
||||
// Create PUT transaction
|
||||
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength, true);
|
||||
long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime());
|
||||
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name,
|
||||
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, 0L, true,
|
||||
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, fee, false,
|
||||
title, description, tags, category);
|
||||
|
||||
byte[] signature = arbitraryDataFile.getSignature();
|
||||
@@ -614,8 +572,9 @@ public class ArbitraryTransactionTests extends Common {
|
||||
|
||||
// Create PUT transaction
|
||||
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength, true);
|
||||
long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime());
|
||||
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name,
|
||||
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, 0L, true,
|
||||
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, fee, false,
|
||||
null, null, null, null);
|
||||
|
||||
byte[] signature = arbitraryDataFile.getSignature();
|
||||
|
||||
@@ -5,10 +5,12 @@ import org.qortal.arbitrary.ArbitraryDataFile;
|
||||
import org.qortal.arbitrary.ArbitraryDataTransactionBuilder;
|
||||
import org.qortal.arbitrary.misc.Category;
|
||||
import org.qortal.arbitrary.misc.Service;
|
||||
import org.qortal.block.BlockChain;
|
||||
import org.qortal.data.transaction.ArbitraryTransactionData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.transaction.Transaction;
|
||||
import org.qortal.utils.NTP;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
@@ -20,16 +22,15 @@ import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class ArbitraryUtils {
|
||||
|
||||
public static ArbitraryDataFile createAndMintTxn(Repository repository, String publicKey58, Path path, String name, String identifier,
|
||||
ArbitraryTransactionData.Method method, Service service, PrivateKeyAccount account,
|
||||
int chunkSize) throws DataException {
|
||||
|
||||
long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime());
|
||||
return ArbitraryUtils.createAndMintTxn(repository, publicKey58, path, name, identifier, method, service,
|
||||
account, chunkSize, 0L, true, null, null, null, null);
|
||||
account, chunkSize, fee, false, null, null, null, null);
|
||||
}
|
||||
|
||||
public static ArbitraryDataFile createAndMintTxn(Repository repository, String publicKey58, Path path, String name, String identifier,
|
||||
@@ -47,7 +48,9 @@ public class ArbitraryUtils {
|
||||
}
|
||||
ArbitraryTransactionData transactionData = txnBuilder.getArbitraryTransactionData();
|
||||
Transaction.ValidationResult result = TransactionUtils.signAndImport(repository, transactionData, account);
|
||||
assertEquals(Transaction.ValidationResult.OK, result);
|
||||
if (result != Transaction.ValidationResult.OK) {
|
||||
throw new DataException(String.format("Arbitrary transaction invalid: %s", result.toString()));
|
||||
}
|
||||
BlockUtils.mintBlock(repository);
|
||||
|
||||
// We need a new ArbitraryDataFile instance because the files will have been moved to the signature's folder
|
||||
|
||||
@@ -7,13 +7,15 @@ import org.qortal.block.BlockChain;
|
||||
import org.qortal.data.transaction.BaseTransactionData;
|
||||
import org.qortal.group.Group;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.utils.NTP;
|
||||
|
||||
public abstract class TestTransaction {
|
||||
|
||||
protected static final Random random = new Random();
|
||||
|
||||
public static BaseTransactionData generateBase(PrivateKeyAccount account, int txGroupId) throws DataException {
|
||||
return new BaseTransactionData(System.currentTimeMillis(), txGroupId, account.getLastReference(), account.getPublicKey(), BlockChain.getInstance().getUnitFee(), null);
|
||||
long timestamp = System.currentTimeMillis();
|
||||
return new BaseTransactionData(timestamp, txGroupId, account.getLastReference(), account.getPublicKey(), BlockChain.getInstance().getUnitFeeAtTimestamp(timestamp), null);
|
||||
}
|
||||
|
||||
public static BaseTransactionData generateBase(PrivateKeyAccount account) throws DataException {
|
||||
|
||||
@@ -1,121 +1,59 @@
|
||||
package org.qortal.test.crosschain;
|
||||
|
||||
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;
|
||||
import org.junit.Test;
|
||||
import org.qortal.crosschain.Bitcoin;
|
||||
import org.qortal.crosschain.ForeignBlockchainException;
|
||||
import org.qortal.crosschain.BitcoinyHTLC;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.test.common.Common;
|
||||
import org.qortal.crosschain.Bitcoiny;
|
||||
|
||||
public class BitcoinTests extends Common {
|
||||
public class BitcoinTests extends BitcoinyTests {
|
||||
|
||||
private Bitcoin bitcoin;
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings(); // TestNet3
|
||||
bitcoin = Bitcoin.getInstance();
|
||||
@Override
|
||||
protected String getCoinName() {
|
||||
return "Bitcoin";
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterTest() {
|
||||
@Override
|
||||
protected String getCoinSymbol() {
|
||||
return "BTC";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitcoiny getCoin() {
|
||||
return Bitcoin.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void resetCoinForTesting() {
|
||||
Bitcoin.resetForTesting();
|
||||
bitcoin = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDeterministicKey58() {
|
||||
return "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getRecipient() {
|
||||
return "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
}
|
||||
|
||||
@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"));
|
||||
|
||||
long before = System.currentTimeMillis();
|
||||
System.out.println(String.format("Bitcoin median blocktime: %d", bitcoin.getMedianBlockTime()));
|
||||
long afterFirst = System.currentTimeMillis();
|
||||
|
||||
System.out.println(String.format("Bitcoin median blocktime: %d", bitcoin.getMedianBlockTime()));
|
||||
long afterSecond = System.currentTimeMillis();
|
||||
|
||||
long firstPeriod = afterFirst - before;
|
||||
long secondPeriod = afterSecond - afterFirst;
|
||||
|
||||
System.out.println(String.format("1st call: %d ms, 2nd call: %d ms", firstPeriod, secondPeriod));
|
||||
|
||||
assertTrue("2nd call should be quicker than 1st", secondPeriod < firstPeriod);
|
||||
assertTrue("2nd call should take less than 5 seconds", secondPeriod < 5000L);
|
||||
}
|
||||
public void testGetMedianBlockTime() {}
|
||||
|
||||
@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";
|
||||
|
||||
byte[] expectedSecret = "This string is exactly 32 bytes!".getBytes();
|
||||
byte[] secret = BitcoinyHTLC.findHtlcSecret(bitcoin, p2shAddress);
|
||||
|
||||
assertNotNull(secret);
|
||||
assertTrue("secret incorrect", Arrays.equals(expectedSecret, secret));
|
||||
}
|
||||
public void testFindHtlcSecret() {}
|
||||
|
||||
@Test
|
||||
@Ignore("Often fails due to unreliable BTC testnet ElectrumX servers")
|
||||
public void testBuildSpend() {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
|
||||
String recipient = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
long amount = 1000L;
|
||||
|
||||
Transaction transaction = bitcoin.buildSpend(xprv58, recipient, amount);
|
||||
assertNotNull(transaction);
|
||||
|
||||
// Check spent key caching doesn't affect outcome
|
||||
|
||||
transaction = bitcoin.buildSpend(xprv58, recipient, amount);
|
||||
assertNotNull(transaction);
|
||||
}
|
||||
public void testBuildSpend() {}
|
||||
|
||||
@Test
|
||||
@Ignore("Often fails due to unreliable BTC testnet ElectrumX servers")
|
||||
public void testGetWalletBalance() throws ForeignBlockchainException {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
|
||||
Long balance = bitcoin.getWalletBalance(xprv58);
|
||||
|
||||
assertNotNull(balance);
|
||||
|
||||
System.out.println(bitcoin.format(balance));
|
||||
|
||||
// Check spent key caching doesn't affect outcome
|
||||
|
||||
Long repeatBalance = bitcoin.getWalletBalance(xprv58);
|
||||
|
||||
assertNotNull(repeatBalance);
|
||||
|
||||
System.out.println(bitcoin.format(repeatBalance));
|
||||
|
||||
assertEquals(balance, repeatBalance);
|
||||
}
|
||||
public void testGetWalletBalance() {}
|
||||
|
||||
@Test
|
||||
@Ignore("Often fails due to unreliable BTC testnet ElectrumX servers")
|
||||
public void testGetUnusedReceiveAddress() throws ForeignBlockchainException {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
|
||||
String address = bitcoin.getUnusedReceiveAddress(xprv58);
|
||||
|
||||
assertNotNull(address);
|
||||
|
||||
System.out.println(address);
|
||||
}
|
||||
|
||||
public void testGetUnusedReceiveAddress() {}
|
||||
}
|
||||
|
||||
130
src/test/java/org/qortal/test/crosschain/BitcoinyTests.java
Normal file
130
src/test/java/org/qortal/test/crosschain/BitcoinyTests.java
Normal file
@@ -0,0 +1,130 @@
|
||||
package org.qortal.test.crosschain;
|
||||
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.qortal.crosschain.Bitcoiny;
|
||||
import org.qortal.crosschain.BitcoinyHTLC;
|
||||
import org.qortal.crosschain.ForeignBlockchainException;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.test.common.Common;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public abstract class BitcoinyTests extends Common {
|
||||
|
||||
protected Bitcoiny bitcoiny;
|
||||
|
||||
protected abstract String getCoinName();
|
||||
|
||||
protected abstract String getCoinSymbol();
|
||||
|
||||
protected abstract Bitcoiny getCoin();
|
||||
|
||||
protected abstract void resetCoinForTesting();
|
||||
|
||||
protected abstract String getDeterministicKey58();
|
||||
|
||||
protected abstract String getRecipient();
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings(); // TestNet3
|
||||
bitcoiny = getCoin();
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterTest() {
|
||||
resetCoinForTesting();
|
||||
bitcoiny = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMedianBlockTime() throws ForeignBlockchainException {
|
||||
System.out.println(String.format("Starting " + getCoinSymbol() + " instance..."));
|
||||
System.out.println(String.format(getCoinSymbol() + " instance started"));
|
||||
|
||||
long before = System.currentTimeMillis();
|
||||
System.out.println(String.format(getCoinName() + " median blocktime: %d", bitcoiny.getMedianBlockTime()));
|
||||
long afterFirst = System.currentTimeMillis();
|
||||
|
||||
System.out.println(String.format(getCoinName() + " median blocktime: %d", bitcoiny.getMedianBlockTime()));
|
||||
long afterSecond = System.currentTimeMillis();
|
||||
|
||||
long firstPeriod = afterFirst - before;
|
||||
long secondPeriod = afterSecond - afterFirst;
|
||||
|
||||
System.out.println(String.format("1st call: %d ms, 2nd call: %d ms", firstPeriod, secondPeriod));
|
||||
|
||||
makeGetMedianBlockTimeAssertions(firstPeriod, secondPeriod);
|
||||
}
|
||||
|
||||
public void makeGetMedianBlockTimeAssertions(long firstPeriod, long secondPeriod) {
|
||||
assertTrue("2nd call should be quicker than 1st", secondPeriod < firstPeriod);
|
||||
assertTrue("2nd call should take less than 5 seconds", secondPeriod < 5000L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFindHtlcSecret() throws ForeignBlockchainException {
|
||||
// This actually exists on TEST3 but can take a while to fetch
|
||||
String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
|
||||
byte[] expectedSecret = "This string is exactly 32 bytes!".getBytes();
|
||||
byte[] secret = BitcoinyHTLC.findHtlcSecret(bitcoiny, p2shAddress);
|
||||
|
||||
assertNotNull(secret);
|
||||
assertTrue("secret incorrect", Arrays.equals(expectedSecret, secret));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildSpend() {
|
||||
String xprv58 = getDeterministicKey58();
|
||||
|
||||
String recipient = getRecipient();
|
||||
long amount = 1000L;
|
||||
|
||||
Transaction transaction = bitcoiny.buildSpend(xprv58, recipient, amount);
|
||||
assertNotNull(transaction);
|
||||
|
||||
// Check spent key caching doesn't affect outcome
|
||||
|
||||
transaction = bitcoiny.buildSpend(xprv58, recipient, amount);
|
||||
assertNotNull(transaction);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetWalletBalance() throws ForeignBlockchainException {
|
||||
String xprv58 = getDeterministicKey58();
|
||||
|
||||
Long balance = bitcoiny.getWalletBalance(xprv58);
|
||||
|
||||
assertNotNull(balance);
|
||||
|
||||
System.out.println(bitcoiny.format(balance));
|
||||
|
||||
// Check spent key caching doesn't affect outcome
|
||||
|
||||
Long repeatBalance = bitcoiny.getWalletBalance(xprv58);
|
||||
|
||||
assertNotNull(repeatBalance);
|
||||
|
||||
System.out.println(bitcoiny.format(repeatBalance));
|
||||
|
||||
assertEquals(balance, repeatBalance);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUnusedReceiveAddress() throws ForeignBlockchainException {
|
||||
String xprv58 = getDeterministicKey58();
|
||||
|
||||
String address = bitcoiny.getUnusedReceiveAddress(xprv58);
|
||||
|
||||
assertNotNull(address);
|
||||
|
||||
System.out.println(address);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,115 +1,48 @@
|
||||
package org.qortal.test.crosschain;
|
||||
|
||||
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;
|
||||
import org.junit.Test;
|
||||
import org.qortal.crosschain.ForeignBlockchainException;
|
||||
import org.qortal.crosschain.Bitcoiny;
|
||||
import org.qortal.crosschain.Digibyte;
|
||||
import org.qortal.crosschain.BitcoinyHTLC;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.test.common.Common;
|
||||
|
||||
public class DigibyteTests extends Common {
|
||||
public class DigibyteTests extends BitcoinyTests {
|
||||
|
||||
private Digibyte digibyte;
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings(); // TestNet3
|
||||
digibyte = Digibyte.getInstance();
|
||||
@Override
|
||||
protected String getCoinName() {
|
||||
return "Digibyte";
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterTest() {
|
||||
@Override
|
||||
protected String getCoinSymbol() {
|
||||
return "DGB";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitcoiny getCoin() {
|
||||
return Digibyte.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void resetCoinForTesting() {
|
||||
Digibyte.resetForTesting();
|
||||
digibyte = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMedianBlockTime() throws BlockStoreException, ForeignBlockchainException {
|
||||
long before = System.currentTimeMillis();
|
||||
System.out.println(String.format("Digibyte median blocktime: %d", digibyte.getMedianBlockTime()));
|
||||
long afterFirst = System.currentTimeMillis();
|
||||
@Override
|
||||
protected String getDeterministicKey58() {
|
||||
return "xpub661MyMwAqRbcEnabTLX5uebYcsE3uG5y7ve9jn1VK8iY1MaU3YLoLJEe8sTu2YVav5Zka5qf2dmMssfxmXJTqZnazZL2kL7M2tNKwEoC34R";
|
||||
}
|
||||
|
||||
System.out.println(String.format("Digibyte median blocktime: %d", digibyte.getMedianBlockTime()));
|
||||
long afterSecond = System.currentTimeMillis();
|
||||
|
||||
long firstPeriod = afterFirst - before;
|
||||
long secondPeriod = afterSecond - afterFirst;
|
||||
|
||||
System.out.println(String.format("1st call: %d ms, 2nd call: %d ms", firstPeriod, secondPeriod));
|
||||
|
||||
assertTrue("2nd call should be quicker than 1st", secondPeriod < firstPeriod);
|
||||
assertTrue("2nd call should take less than 5 seconds", secondPeriod < 5000L);
|
||||
@Override
|
||||
protected String getRecipient() {
|
||||
return "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore(value = "Doesn't work, to be fixed later")
|
||||
public void testFindHtlcSecret() throws ForeignBlockchainException {
|
||||
// This actually exists on TEST3 but can take a while to fetch
|
||||
String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
|
||||
byte[] expectedSecret = "This string is exactly 32 bytes!".getBytes();
|
||||
byte[] secret = BitcoinyHTLC.findHtlcSecret(digibyte, p2shAddress);
|
||||
|
||||
assertNotNull("secret not found", secret);
|
||||
assertTrue("secret incorrect", Arrays.equals(expectedSecret, secret));
|
||||
}
|
||||
public void testFindHtlcSecret() {}
|
||||
|
||||
@Test
|
||||
@Ignore(value = "No testnet nodes available, so we can't regularly test buildSpend yet")
|
||||
public void testBuildSpend() {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
public void testBuildSpend() {}
|
||||
|
||||
String recipient = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
long amount = 1000L;
|
||||
|
||||
Transaction transaction = digibyte.buildSpend(xprv58, recipient, amount);
|
||||
assertNotNull("insufficient funds", transaction);
|
||||
|
||||
// Check spent key caching doesn't affect outcome
|
||||
|
||||
transaction = digibyte.buildSpend(xprv58, recipient, amount);
|
||||
assertNotNull("insufficient funds", transaction);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetWalletBalance() throws ForeignBlockchainException {
|
||||
String xprv58 = "xpub661MyMwAqRbcEnabTLX5uebYcsE3uG5y7ve9jn1VK8iY1MaU3YLoLJEe8sTu2YVav5Zka5qf2dmMssfxmXJTqZnazZL2kL7M2tNKwEoC34R";
|
||||
|
||||
Long balance = digibyte.getWalletBalance(xprv58);
|
||||
|
||||
assertNotNull(balance);
|
||||
|
||||
System.out.println(digibyte.format(balance));
|
||||
|
||||
// Check spent key caching doesn't affect outcome
|
||||
|
||||
Long repeatBalance = digibyte.getWalletBalance(xprv58);
|
||||
|
||||
assertNotNull(repeatBalance);
|
||||
|
||||
System.out.println(digibyte.format(repeatBalance));
|
||||
|
||||
assertEquals(balance, repeatBalance);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUnusedReceiveAddress() throws ForeignBlockchainException {
|
||||
String xprv58 = "xpub661MyMwAqRbcEnabTLX5uebYcsE3uG5y7ve9jn1VK8iY1MaU3YLoLJEe8sTu2YVav5Zka5qf2dmMssfxmXJTqZnazZL2kL7M2tNKwEoC34R";
|
||||
|
||||
String address = digibyte.getUnusedReceiveAddress(xprv58);
|
||||
|
||||
assertNotNull(address);
|
||||
|
||||
System.out.println(address);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,115 +1,47 @@
|
||||
package org.qortal.test.crosschain;
|
||||
|
||||
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.BitcoinyHTLC;
|
||||
import org.qortal.crosschain.ForeignBlockchainException;
|
||||
import org.qortal.crosschain.Bitcoiny;
|
||||
import org.qortal.crosschain.Dogecoin;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.test.common.Common;
|
||||
|
||||
import java.util.Arrays;
|
||||
public class DogecoinTests extends BitcoinyTests {
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class DogecoinTests extends Common {
|
||||
|
||||
private Dogecoin dogecoin;
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings(); // TestNet3
|
||||
dogecoin = Dogecoin.getInstance();
|
||||
@Override
|
||||
protected String getCoinName() {
|
||||
return "Dogecoin";
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterTest() {
|
||||
@Override
|
||||
protected String getCoinSymbol() {
|
||||
return "DOGE";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitcoiny getCoin() {
|
||||
return Dogecoin.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void resetCoinForTesting() {
|
||||
Dogecoin.resetForTesting();
|
||||
dogecoin = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMedianBlockTime() throws BlockStoreException, ForeignBlockchainException {
|
||||
long before = System.currentTimeMillis();
|
||||
System.out.println(String.format("Dogecoin median blocktime: %d", dogecoin.getMedianBlockTime()));
|
||||
long afterFirst = System.currentTimeMillis();
|
||||
@Override
|
||||
protected String getDeterministicKey58() {
|
||||
return "dgpv51eADS3spNJh9drNeW1Tc1P9z2LyaQRXPBortsq6yice1k47C2u2Prvgxycr2ihNBWzKZ2LthcBBGiYkWZ69KUTVkcLVbnjq7pD8mnApEru";
|
||||
}
|
||||
|
||||
System.out.println(String.format("Dogecoin median blocktime: %d", dogecoin.getMedianBlockTime()));
|
||||
long afterSecond = System.currentTimeMillis();
|
||||
|
||||
long firstPeriod = afterFirst - before;
|
||||
long secondPeriod = afterSecond - afterFirst;
|
||||
|
||||
System.out.println(String.format("1st call: %d ms, 2nd call: %d ms", firstPeriod, secondPeriod));
|
||||
|
||||
assertTrue("2nd call should be quicker than 1st", secondPeriod < firstPeriod);
|
||||
assertTrue("2nd call should take less than 5 seconds", secondPeriod < 5000L);
|
||||
@Override
|
||||
protected String getRecipient() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore(value = "Doesn't work, to be fixed later")
|
||||
public void testFindHtlcSecret() throws ForeignBlockchainException {
|
||||
// This actually exists on TEST3 but can take a while to fetch
|
||||
String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
|
||||
byte[] expectedSecret = "This string is exactly 32 bytes!".getBytes();
|
||||
byte[] secret = BitcoinyHTLC.findHtlcSecret(dogecoin, p2shAddress);
|
||||
|
||||
assertNotNull("secret not found", secret);
|
||||
assertTrue("secret incorrect", Arrays.equals(expectedSecret, secret));
|
||||
}
|
||||
public void testFindHtlcSecret() {}
|
||||
|
||||
@Test
|
||||
@Ignore(value = "No testnet nodes available, so we can't regularly test buildSpend yet")
|
||||
public void testBuildSpend() {
|
||||
String xprv58 = "dgpv51eADS3spNJh9drNeW1Tc1P9z2LyaQRXPBortsq6yice1k47C2u2Prvgxycr2ihNBWzKZ2LthcBBGiYkWZ69KUTVkcLVbnjq7pD8mnApEru";
|
||||
|
||||
String recipient = "DP1iFao33xdEPa5vaArpj7sykfzKNeiJeX";
|
||||
long amount = 1000L;
|
||||
|
||||
Transaction transaction = dogecoin.buildSpend(xprv58, recipient, amount);
|
||||
assertNotNull("insufficient funds", transaction);
|
||||
|
||||
// Check spent key caching doesn't affect outcome
|
||||
|
||||
transaction = dogecoin.buildSpend(xprv58, recipient, amount);
|
||||
assertNotNull("insufficient funds", transaction);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetWalletBalance() throws ForeignBlockchainException {
|
||||
String xprv58 = "dgpv51eADS3spNJh9drNeW1Tc1P9z2LyaQRXPBortsq6yice1k47C2u2Prvgxycr2ihNBWzKZ2LthcBBGiYkWZ69KUTVkcLVbnjq7pD8mnApEru";
|
||||
|
||||
Long balance = dogecoin.getWalletBalance(xprv58);
|
||||
|
||||
assertNotNull(balance);
|
||||
|
||||
System.out.println(dogecoin.format(balance));
|
||||
|
||||
// Check spent key caching doesn't affect outcome
|
||||
|
||||
Long repeatBalance = dogecoin.getWalletBalance(xprv58);
|
||||
|
||||
assertNotNull(repeatBalance);
|
||||
|
||||
System.out.println(dogecoin.format(repeatBalance));
|
||||
|
||||
assertEquals(balance, repeatBalance);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUnusedReceiveAddress() throws ForeignBlockchainException {
|
||||
String xprv58 = "dgpv51eADS3spNJh9drNeW1Tc1P9z2LyaQRXPBortsq6yice1k47C2u2Prvgxycr2ihNBWzKZ2LthcBBGiYkWZ69KUTVkcLVbnjq7pD8mnApEru";
|
||||
|
||||
String address = dogecoin.getUnusedReceiveAddress(xprv58);
|
||||
|
||||
assertNotNull(address);
|
||||
|
||||
System.out.println(address);
|
||||
}
|
||||
|
||||
public void testBuildSpend() {}
|
||||
}
|
||||
|
||||
@@ -1,113 +1,43 @@
|
||||
package org.qortal.test.crosschain;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.bitcoinj.core.Transaction;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.qortal.crosschain.ForeignBlockchainException;
|
||||
import org.qortal.crosschain.Bitcoiny;
|
||||
import org.qortal.crosschain.Litecoin;
|
||||
import org.qortal.crosschain.BitcoinyHTLC;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.test.common.Common;
|
||||
|
||||
public class LitecoinTests extends Common {
|
||||
public class LitecoinTests extends BitcoinyTests {
|
||||
|
||||
private Litecoin litecoin;
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings(); // TestNet3
|
||||
litecoin = Litecoin.getInstance();
|
||||
@Override
|
||||
protected String getCoinName() {
|
||||
return "Litecoin";
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterTest() {
|
||||
@Override
|
||||
protected String getCoinSymbol() {
|
||||
return "LTC";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitcoiny getCoin() {
|
||||
return Litecoin.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void resetCoinForTesting() {
|
||||
Litecoin.resetForTesting();
|
||||
litecoin = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMedianBlockTime() throws ForeignBlockchainException {
|
||||
long before = System.currentTimeMillis();
|
||||
System.out.println(String.format("Litecoin median blocktime: %d", litecoin.getMedianBlockTime()));
|
||||
long afterFirst = System.currentTimeMillis();
|
||||
@Override
|
||||
protected String getDeterministicKey58() {
|
||||
return "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
}
|
||||
|
||||
System.out.println(String.format("Litecoin median blocktime: %d", litecoin.getMedianBlockTime()));
|
||||
long afterSecond = System.currentTimeMillis();
|
||||
|
||||
long firstPeriod = afterFirst - before;
|
||||
long secondPeriod = afterSecond - afterFirst;
|
||||
|
||||
System.out.println(String.format("1st call: %d ms, 2nd call: %d ms", firstPeriod, secondPeriod));
|
||||
|
||||
assertTrue("2nd call should be quicker than 1st", secondPeriod < firstPeriod);
|
||||
assertTrue("2nd call should take less than 5 seconds", secondPeriod < 5000L);
|
||||
@Override
|
||||
protected String getRecipient() {
|
||||
return "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore(value = "Doesn't work, to be fixed later")
|
||||
public void testFindHtlcSecret() throws ForeignBlockchainException {
|
||||
// This actually exists on TEST3 but can take a while to fetch
|
||||
String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
|
||||
byte[] expectedSecret = "This string is exactly 32 bytes!".getBytes();
|
||||
byte[] secret = BitcoinyHTLC.findHtlcSecret(litecoin, p2shAddress);
|
||||
|
||||
assertNotNull("secret not found", secret);
|
||||
assertTrue("secret incorrect", Arrays.equals(expectedSecret, secret));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuildSpend() {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
|
||||
String recipient = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
long amount = 1000L;
|
||||
|
||||
Transaction transaction = litecoin.buildSpend(xprv58, recipient, amount);
|
||||
assertNotNull("insufficient funds", transaction);
|
||||
|
||||
// Check spent key caching doesn't affect outcome
|
||||
|
||||
transaction = litecoin.buildSpend(xprv58, recipient, amount);
|
||||
assertNotNull("insufficient funds", transaction);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetWalletBalance() throws ForeignBlockchainException {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
|
||||
Long balance = litecoin.getWalletBalance(xprv58);
|
||||
|
||||
assertNotNull(balance);
|
||||
|
||||
System.out.println(litecoin.format(balance));
|
||||
|
||||
// Check spent key caching doesn't affect outcome
|
||||
|
||||
Long repeatBalance = litecoin.getWalletBalance(xprv58);
|
||||
|
||||
assertNotNull(repeatBalance);
|
||||
|
||||
System.out.println(litecoin.format(repeatBalance));
|
||||
|
||||
assertEquals(balance, repeatBalance);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUnusedReceiveAddress() throws ForeignBlockchainException {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
|
||||
String address = litecoin.getUnusedReceiveAddress(xprv58);
|
||||
|
||||
assertNotNull(address);
|
||||
|
||||
System.out.println(address);
|
||||
}
|
||||
|
||||
public void testFindHtlcSecret() {}
|
||||
}
|
||||
|
||||
@@ -3,57 +3,53 @@ package org.qortal.test.crosschain;
|
||||
import cash.z.wallet.sdk.rpc.CompactFormats.*;
|
||||
import com.google.common.hash.HashCode;
|
||||
import com.google.common.primitives.Bytes;
|
||||
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.controller.tradebot.TradeBot;
|
||||
import org.qortal.crosschain.*;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.test.common.Common;
|
||||
import org.qortal.transform.TransformationException;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.qortal.crosschain.BitcoinyHTLC.Status.*;
|
||||
|
||||
public class PirateChainTests extends Common {
|
||||
public class PirateChainTests extends BitcoinyTests {
|
||||
|
||||
private PirateChain pirateChain;
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings();
|
||||
pirateChain = PirateChain.getInstance();
|
||||
@Override
|
||||
protected String getCoinName() {
|
||||
return "PirateChain";
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterTest() {
|
||||
@Override
|
||||
protected String getCoinSymbol() {
|
||||
return "ARRR";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitcoiny getCoin() {
|
||||
return PirateChain.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void resetCoinForTesting() {
|
||||
Litecoin.resetForTesting();
|
||||
pirateChain = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMedianBlockTime() throws BlockStoreException, ForeignBlockchainException {
|
||||
long before = System.currentTimeMillis();
|
||||
System.out.println(String.format("Pirate Chain median blocktime: %d", pirateChain.getMedianBlockTime()));
|
||||
long afterFirst = System.currentTimeMillis();
|
||||
@Override
|
||||
protected String getDeterministicKey58() {
|
||||
return null;
|
||||
}
|
||||
|
||||
System.out.println(String.format("Pirate Chain median blocktime: %d", pirateChain.getMedianBlockTime()));
|
||||
long afterSecond = System.currentTimeMillis();
|
||||
@Override
|
||||
protected String getRecipient() {
|
||||
return null;
|
||||
}
|
||||
|
||||
long firstPeriod = afterFirst - before;
|
||||
long secondPeriod = afterSecond - afterFirst;
|
||||
|
||||
System.out.println(String.format("1st call: %d ms, 2nd call: %d ms", firstPeriod, secondPeriod));
|
||||
|
||||
assertTrue("1st call should take less than 5 seconds", firstPeriod < 5000L);
|
||||
assertTrue("2nd call should take less than 5 seconds", secondPeriod < 5000L);
|
||||
public void makeGetMedianBlockTimeAssertions(long firstPeriod, long secondPeriod) {
|
||||
assertTrue("1st call should take less than 5 seconds", firstPeriod < 5000L);
|
||||
assertTrue("2nd call should take less than 5 seconds", secondPeriod < 5000L);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -62,7 +58,7 @@ public class PirateChainTests extends Common {
|
||||
int count = 20;
|
||||
|
||||
long before = System.currentTimeMillis();
|
||||
List<CompactBlock> compactBlocks = pirateChain.getCompactBlocks(startHeight, count);
|
||||
List<CompactBlock> compactBlocks = ((PirateChain) bitcoiny).getCompactBlocks(startHeight, count);
|
||||
long after = System.currentTimeMillis();
|
||||
|
||||
System.out.println(String.format("Retrieval took: %d ms", after-before));
|
||||
@@ -82,7 +78,7 @@ public class PirateChainTests extends Common {
|
||||
Bytes.reverse(txBytes);
|
||||
String txHashBE = HashCode.fromBytes(txBytes).toString();
|
||||
|
||||
byte[] rawTransaction = pirateChain.getBlockchainProvider().getRawTransaction(txHashBE);
|
||||
byte[] rawTransaction = bitcoiny.getBlockchainProvider().getRawTransaction(txHashBE);
|
||||
assertNotNull(rawTransaction);
|
||||
}
|
||||
|
||||
@@ -121,7 +117,7 @@ public class PirateChainTests extends Common {
|
||||
String p2shAddress = "ba6Q5HWrWtmfU2WZqQbrFdRYsafA45cUAt";
|
||||
long p2shFee = 10000;
|
||||
final long minimumAmount = 10000 + p2shFee;
|
||||
BitcoinyHTLC.Status htlcStatus = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmount);
|
||||
BitcoinyHTLC.Status htlcStatus = PirateChainHTLC.determineHtlcStatus(bitcoiny.getBlockchainProvider(), p2shAddress, minimumAmount);
|
||||
assertEquals(FUNDED, htlcStatus);
|
||||
}
|
||||
|
||||
@@ -130,7 +126,7 @@ public class PirateChainTests extends Common {
|
||||
String p2shAddress = "bYZrzSSgGp8aEGvihukoMGU8sXYrx19Wka";
|
||||
long p2shFee = 10000;
|
||||
final long minimumAmount = 10000 + p2shFee;
|
||||
BitcoinyHTLC.Status htlcStatus = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmount);
|
||||
BitcoinyHTLC.Status htlcStatus = PirateChainHTLC.determineHtlcStatus(bitcoiny.getBlockchainProvider(), p2shAddress, minimumAmount);
|
||||
assertEquals(REDEEMED, htlcStatus);
|
||||
}
|
||||
|
||||
@@ -139,14 +135,14 @@ public class PirateChainTests extends Common {
|
||||
String p2shAddress = "bE49izfVxz8odhu8c2BcUaVFUnt7NLFRgv";
|
||||
long p2shFee = 10000;
|
||||
final long minimumAmount = 10000 + p2shFee;
|
||||
BitcoinyHTLC.Status htlcStatus = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmount);
|
||||
BitcoinyHTLC.Status htlcStatus = PirateChainHTLC.determineHtlcStatus(bitcoiny.getBlockchainProvider(), p2shAddress, minimumAmount);
|
||||
assertEquals(REFUNDED, htlcStatus);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetTxidForUnspentAddress() throws ForeignBlockchainException {
|
||||
String p2shAddress = "ba6Q5HWrWtmfU2WZqQbrFdRYsafA45cUAt";
|
||||
String txid = PirateChainHTLC.getFundingTxid(pirateChain.getBlockchainProvider(), p2shAddress);
|
||||
String txid = PirateChainHTLC.getFundingTxid(bitcoiny.getBlockchainProvider(), p2shAddress);
|
||||
|
||||
// Reverse the byte order of the txid used by block explorers, to get to big-endian form
|
||||
byte[] expectedTxidLE = HashCode.fromString("fea4b0c1abcf8f0f3ddc2fa2f9438501ee102aad62a9ff18a5ce7d08774755c0").asBytes();
|
||||
@@ -161,7 +157,7 @@ public class PirateChainTests extends Common {
|
||||
String p2shAddress = "ba6Q5HWrWtmfU2WZqQbrFdRYsafA45cUAt";
|
||||
long p2shFee = 10000;
|
||||
final long minimumAmount = 10000 + p2shFee;
|
||||
String txid = PirateChainHTLC.getUnspentFundingTxid(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmount);
|
||||
String txid = PirateChainHTLC.getUnspentFundingTxid(bitcoiny.getBlockchainProvider(), p2shAddress, minimumAmount);
|
||||
|
||||
// Reverse the byte order of the txid used by block explorers, to get to big-endian form
|
||||
byte[] expectedTxidLE = HashCode.fromString("fea4b0c1abcf8f0f3ddc2fa2f9438501ee102aad62a9ff18a5ce7d08774755c0").asBytes();
|
||||
@@ -174,7 +170,7 @@ public class PirateChainTests extends Common {
|
||||
@Test
|
||||
public void testGetTxidForSpentAddress() throws ForeignBlockchainException {
|
||||
String p2shAddress = "bE49izfVxz8odhu8c2BcUaVFUnt7NLFRgv"; //"t3KtVxeEb8srJofo6atMEpMpEP6TjEi8VqA";
|
||||
String txid = PirateChainHTLC.getFundingTxid(pirateChain.getBlockchainProvider(), p2shAddress);
|
||||
String txid = PirateChainHTLC.getFundingTxid(bitcoiny.getBlockchainProvider(), p2shAddress);
|
||||
|
||||
// Reverse the byte order of the txid used by block explorers, to get to big-endian form
|
||||
byte[] expectedTxidLE = HashCode.fromString("fb386fc8eea0fbf3ea37047726b92c39441652b32d8d62a274331687f7a1eca8").asBytes();
|
||||
@@ -187,7 +183,7 @@ public class PirateChainTests extends Common {
|
||||
@Test
|
||||
public void testGetTransactionsForAddress() throws ForeignBlockchainException {
|
||||
String p2shAddress = "bE49izfVxz8odhu8c2BcUaVFUnt7NLFRgv"; //"t3KtVxeEb8srJofo6atMEpMpEP6TjEi8VqA";
|
||||
List<BitcoinyTransaction> transactions = pirateChain.getBlockchainProvider()
|
||||
List<BitcoinyTransaction> transactions = bitcoiny.getBlockchainProvider()
|
||||
.getAddressBitcoinyTransactions(p2shAddress, false);
|
||||
|
||||
assertEquals(2, transactions.size());
|
||||
@@ -232,66 +228,17 @@ public class PirateChainTests extends Common {
|
||||
|
||||
@Test
|
||||
@Ignore(value = "Doesn't work, to be fixed later")
|
||||
public void testFindHtlcSecret() throws ForeignBlockchainException {
|
||||
// This actually exists on TEST3 but can take a while to fetch
|
||||
String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
|
||||
byte[] expectedSecret = "This string is exactly 32 bytes!".getBytes();
|
||||
byte[] secret = BitcoinyHTLC.findHtlcSecret(pirateChain, p2shAddress);
|
||||
|
||||
assertNotNull("secret not found", secret);
|
||||
assertTrue("secret incorrect", Arrays.equals(expectedSecret, secret));
|
||||
}
|
||||
public void testFindHtlcSecret() {}
|
||||
|
||||
@Test
|
||||
@Ignore(value = "Needs adapting for Pirate Chain")
|
||||
public void testBuildSpend() {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
|
||||
String recipient = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
long amount = 1000L;
|
||||
|
||||
Transaction transaction = pirateChain.buildSpend(xprv58, recipient, amount);
|
||||
assertNotNull("insufficient funds", transaction);
|
||||
|
||||
// Check spent key caching doesn't affect outcome
|
||||
|
||||
transaction = pirateChain.buildSpend(xprv58, recipient, amount);
|
||||
assertNotNull("insufficient funds", transaction);
|
||||
}
|
||||
public void testBuildSpend() {}
|
||||
|
||||
@Test
|
||||
@Ignore(value = "Needs adapting for Pirate Chain")
|
||||
public void testGetWalletBalance() throws ForeignBlockchainException {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
|
||||
Long balance = pirateChain.getWalletBalance(xprv58);
|
||||
|
||||
assertNotNull(balance);
|
||||
|
||||
System.out.println(pirateChain.format(balance));
|
||||
|
||||
// Check spent key caching doesn't affect outcome
|
||||
|
||||
Long repeatBalance = pirateChain.getWalletBalance(xprv58);
|
||||
|
||||
assertNotNull(repeatBalance);
|
||||
|
||||
System.out.println(pirateChain.format(repeatBalance));
|
||||
|
||||
assertEquals(balance, repeatBalance);
|
||||
}
|
||||
public void testGetWalletBalance() {}
|
||||
|
||||
@Test
|
||||
@Ignore(value = "Needs adapting for Pirate Chain")
|
||||
public void testGetUnusedReceiveAddress() throws ForeignBlockchainException {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
|
||||
String address = pirateChain.getUnusedReceiveAddress(xprv58);
|
||||
|
||||
assertNotNull(address);
|
||||
|
||||
System.out.println(address);
|
||||
}
|
||||
|
||||
}
|
||||
public void testGetUnusedReceiveAddress() {}
|
||||
}
|
||||
@@ -1,115 +1,47 @@
|
||||
package org.qortal.test.crosschain;
|
||||
|
||||
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;
|
||||
import org.junit.Test;
|
||||
import org.qortal.crosschain.ForeignBlockchainException;
|
||||
import org.qortal.crosschain.Bitcoiny;
|
||||
import org.qortal.crosschain.Ravencoin;
|
||||
import org.qortal.crosschain.BitcoinyHTLC;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.test.common.Common;
|
||||
|
||||
public class RavencoinTests extends Common {
|
||||
public class RavencoinTests extends BitcoinyTests {
|
||||
|
||||
private Ravencoin ravencoin;
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings(); // TestNet3
|
||||
ravencoin = Ravencoin.getInstance();
|
||||
@Override
|
||||
protected String getCoinName() {
|
||||
return "Ravencoin";
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterTest() {
|
||||
@Override
|
||||
protected String getCoinSymbol() {
|
||||
return "RVN";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Bitcoiny getCoin() {
|
||||
return Ravencoin.getInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void resetCoinForTesting() {
|
||||
Ravencoin.resetForTesting();
|
||||
ravencoin = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMedianBlockTime() throws BlockStoreException, ForeignBlockchainException {
|
||||
long before = System.currentTimeMillis();
|
||||
System.out.println(String.format("Ravencoin median blocktime: %d", ravencoin.getMedianBlockTime()));
|
||||
long afterFirst = System.currentTimeMillis();
|
||||
@Override
|
||||
protected String getDeterministicKey58() {
|
||||
return "xpub661MyMwAqRbcEt3Ge1wNmkagyb1J7yTQu4Kquvy77Ycg2iPoh7Urg8s9Jdwp7YmrqGkDKJpUVjsZXSSsQgmAVUC17ZVQQeoWMzm7vDTt1y7";
|
||||
}
|
||||
|
||||
System.out.println(String.format("Ravencoin median blocktime: %d", ravencoin.getMedianBlockTime()));
|
||||
long afterSecond = System.currentTimeMillis();
|
||||
|
||||
long firstPeriod = afterFirst - before;
|
||||
long secondPeriod = afterSecond - afterFirst;
|
||||
|
||||
System.out.println(String.format("1st call: %d ms, 2nd call: %d ms", firstPeriod, secondPeriod));
|
||||
|
||||
assertTrue("2nd call should be quicker than 1st", secondPeriod < firstPeriod);
|
||||
assertTrue("2nd call should take less than 5 seconds", secondPeriod < 5000L);
|
||||
@Override
|
||||
protected String getRecipient() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore(value = "Doesn't work, to be fixed later")
|
||||
public void testFindHtlcSecret() throws ForeignBlockchainException {
|
||||
// This actually exists on TEST3 but can take a while to fetch
|
||||
String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
|
||||
byte[] expectedSecret = "This string is exactly 32 bytes!".getBytes();
|
||||
byte[] secret = BitcoinyHTLC.findHtlcSecret(ravencoin, p2shAddress);
|
||||
|
||||
assertNotNull("secret not found", secret);
|
||||
assertTrue("secret incorrect", Arrays.equals(expectedSecret, secret));
|
||||
}
|
||||
public void testFindHtlcSecret() {}
|
||||
|
||||
@Test
|
||||
@Ignore(value = "No testnet nodes available, so we can't regularly test buildSpend yet")
|
||||
public void testBuildSpend() {
|
||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||
|
||||
String recipient = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||
long amount = 1000L;
|
||||
|
||||
Transaction transaction = ravencoin.buildSpend(xprv58, recipient, amount);
|
||||
assertNotNull("insufficient funds", transaction);
|
||||
|
||||
// Check spent key caching doesn't affect outcome
|
||||
|
||||
transaction = ravencoin.buildSpend(xprv58, recipient, amount);
|
||||
assertNotNull("insufficient funds", transaction);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetWalletBalance() throws ForeignBlockchainException {
|
||||
String xprv58 = "xpub661MyMwAqRbcEt3Ge1wNmkagyb1J7yTQu4Kquvy77Ycg2iPoh7Urg8s9Jdwp7YmrqGkDKJpUVjsZXSSsQgmAVUC17ZVQQeoWMzm7vDTt1y7";
|
||||
|
||||
Long balance = ravencoin.getWalletBalance(xprv58);
|
||||
|
||||
assertNotNull(balance);
|
||||
|
||||
System.out.println(ravencoin.format(balance));
|
||||
|
||||
// Check spent key caching doesn't affect outcome
|
||||
|
||||
Long repeatBalance = ravencoin.getWalletBalance(xprv58);
|
||||
|
||||
assertNotNull(repeatBalance);
|
||||
|
||||
System.out.println(ravencoin.format(repeatBalance));
|
||||
|
||||
assertEquals(balance, repeatBalance);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUnusedReceiveAddress() throws ForeignBlockchainException {
|
||||
String xprv58 = "xpub661MyMwAqRbcEt3Ge1wNmkagyb1J7yTQu4Kquvy77Ycg2iPoh7Urg8s9Jdwp7YmrqGkDKJpUVjsZXSSsQgmAVUC17ZVQQeoWMzm7vDTt1y7";
|
||||
|
||||
String address = ravencoin.getUnusedReceiveAddress(xprv58);
|
||||
|
||||
assertNotNull(address);
|
||||
|
||||
System.out.println(address);
|
||||
}
|
||||
|
||||
public void testBuildSpend() {}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ public class BuySellTests extends Common {
|
||||
bob = Common.getTestAccount(repository, "bob");
|
||||
|
||||
name = "test name" + " " + random.nextInt(1_000_000);
|
||||
price = random.nextInt(1000) * Amounts.MULTIPLIER;
|
||||
price = (random.nextInt(1000) + 1) * Amounts.MULTIPLIER;
|
||||
}
|
||||
|
||||
@After
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.test.common.*;
|
||||
import org.qortal.test.common.transaction.TestTransaction;
|
||||
import org.qortal.transaction.PaymentTransaction;
|
||||
import org.qortal.transaction.RegisterNameTransaction;
|
||||
import org.qortal.transaction.Transaction;
|
||||
import org.qortal.transaction.Transaction.ValidationResult;
|
||||
@@ -329,15 +330,19 @@ public class MiscTests extends Common {
|
||||
public void testRegisterNameFeeIncrease() throws Exception {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
// Set nameRegistrationUnitFeeTimestamp to a time far in the future
|
||||
// Add original fee to nameRegistrationUnitFees
|
||||
UnitFeesByTimestamp originalFee = new UnitFeesByTimestamp();
|
||||
originalFee.timestamp = 0L;
|
||||
originalFee.fee = new AmountTypeAdapter().unmarshal("0.1");
|
||||
|
||||
// Add a time far in the future to nameRegistrationUnitFees
|
||||
UnitFeesByTimestamp futureFeeIncrease = new UnitFeesByTimestamp();
|
||||
futureFeeIncrease.timestamp = 9999999999999L; // 20 Nov 2286
|
||||
futureFeeIncrease.fee = new AmountTypeAdapter().unmarshal("5");
|
||||
FieldUtils.writeField(BlockChain.getInstance(), "nameRegistrationUnitFees", Arrays.asList(futureFeeIncrease), true);
|
||||
FieldUtils.writeField(BlockChain.getInstance(), "nameRegistrationUnitFees", Arrays.asList(originalFee, futureFeeIncrease), true);
|
||||
assertEquals(futureFeeIncrease.fee, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(futureFeeIncrease.timestamp));
|
||||
|
||||
// Validate unit fees pre and post timestamp
|
||||
assertEquals(10000000, BlockChain.getInstance().getUnitFee()); // 0.1 QORT
|
||||
assertEquals(10000000, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(futureFeeIncrease.timestamp - 1)); // 0.1 QORT
|
||||
assertEquals(500000000, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(futureFeeIncrease.timestamp)); // 5 QORT
|
||||
|
||||
@@ -362,7 +367,7 @@ public class MiscTests extends Common {
|
||||
futureFeeIncrease.timestamp = now + (60 * 60 * 1000L); // 1 hour in the future
|
||||
futureFeeIncrease.fee = new AmountTypeAdapter().unmarshal("10");
|
||||
|
||||
FieldUtils.writeField(BlockChain.getInstance(), "nameRegistrationUnitFees", Arrays.asList(pastFeeIncrease, futureFeeIncrease), true);
|
||||
FieldUtils.writeField(BlockChain.getInstance(), "nameRegistrationUnitFees", Arrays.asList(originalFee, pastFeeIncrease, futureFeeIncrease), true);
|
||||
assertEquals(pastFeeIncrease.fee, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(pastFeeIncrease.timestamp));
|
||||
assertEquals(futureFeeIncrease.fee, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(futureFeeIncrease.timestamp));
|
||||
|
||||
@@ -387,4 +392,124 @@ public class MiscTests extends Common {
|
||||
}
|
||||
}
|
||||
|
||||
// test reading the name registration fee schedule from blockchain.json / test-chain-v2.json
|
||||
@Test
|
||||
public void testRegisterNameFeeScheduleInTestchainData() throws Exception {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
final long expectedFutureFeeIncreaseTimestamp = 9999999999999L; // 20 Nov 2286, as per test-chain-v2.json
|
||||
final long expectedFutureFeeIncreaseValue = new AmountTypeAdapter().unmarshal("5");
|
||||
|
||||
assertEquals(expectedFutureFeeIncreaseValue, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(expectedFutureFeeIncreaseTimestamp));
|
||||
|
||||
// Validate unit fees pre and post timestamp
|
||||
assertEquals(10000000, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(expectedFutureFeeIncreaseTimestamp - 1)); // 0.1 QORT
|
||||
assertEquals(500000000, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(expectedFutureFeeIncreaseTimestamp)); // 5 QORT
|
||||
|
||||
// Register-name
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
String name = "test-name";
|
||||
String data = "{\"age\":30}";
|
||||
|
||||
RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, data);
|
||||
transactionData.setFee(new RegisterNameTransaction(null, null).getUnitFee(transactionData.getTimestamp()));
|
||||
assertEquals(10000000L, transactionData.getFee().longValue());
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// test general unit fee increase
|
||||
@Test
|
||||
public void testUnitFeeIncrease() throws Exception {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
// Add original fee to unitFees
|
||||
UnitFeesByTimestamp originalFee = new UnitFeesByTimestamp();
|
||||
originalFee.timestamp = 0L;
|
||||
originalFee.fee = new AmountTypeAdapter().unmarshal("0.1");
|
||||
|
||||
// Add a time far in the future to unitFees
|
||||
UnitFeesByTimestamp futureFeeIncrease = new UnitFeesByTimestamp();
|
||||
futureFeeIncrease.timestamp = 9999999999999L; // 20 Nov 2286
|
||||
futureFeeIncrease.fee = new AmountTypeAdapter().unmarshal("1");
|
||||
FieldUtils.writeField(BlockChain.getInstance(), "unitFees", Arrays.asList(originalFee, futureFeeIncrease), true);
|
||||
assertEquals(futureFeeIncrease.fee, BlockChain.getInstance().getUnitFeeAtTimestamp(futureFeeIncrease.timestamp));
|
||||
|
||||
// Validate unit fees pre and post timestamp
|
||||
assertEquals(10000000, BlockChain.getInstance().getUnitFeeAtTimestamp(futureFeeIncrease.timestamp - 1)); // 0.1 QORT
|
||||
assertEquals(100000000, BlockChain.getInstance().getUnitFeeAtTimestamp(futureFeeIncrease.timestamp)); // 1 QORT
|
||||
|
||||
// Payment
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
PrivateKeyAccount bob = Common.getTestAccount(repository, "bob");
|
||||
PrivateKeyAccount chloe = Common.getTestAccount(repository, "chloe");
|
||||
|
||||
PaymentTransactionData transactionData = new PaymentTransactionData(TestTransaction.generateBase(alice), bob.getAddress(), 100000);
|
||||
transactionData.setFee(new PaymentTransaction(null, null).getUnitFee(transactionData.getTimestamp()));
|
||||
assertEquals(10000000L, transactionData.getFee().longValue());
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
|
||||
// Set fee increase to a time in the past
|
||||
Long now = NTP.getTime();
|
||||
UnitFeesByTimestamp pastFeeIncrease = new UnitFeesByTimestamp();
|
||||
pastFeeIncrease.timestamp = now - 1000L; // 1 second ago
|
||||
pastFeeIncrease.fee = new AmountTypeAdapter().unmarshal("3");
|
||||
|
||||
// Set another increase in the future
|
||||
futureFeeIncrease = new UnitFeesByTimestamp();
|
||||
futureFeeIncrease.timestamp = now + (60 * 60 * 1000L); // 1 hour in the future
|
||||
futureFeeIncrease.fee = new AmountTypeAdapter().unmarshal("10");
|
||||
|
||||
FieldUtils.writeField(BlockChain.getInstance(), "unitFees", Arrays.asList(originalFee, pastFeeIncrease, futureFeeIncrease), true);
|
||||
assertEquals(originalFee.fee, BlockChain.getInstance().getUnitFeeAtTimestamp(originalFee.timestamp));
|
||||
assertEquals(pastFeeIncrease.fee, BlockChain.getInstance().getUnitFeeAtTimestamp(pastFeeIncrease.timestamp));
|
||||
assertEquals(futureFeeIncrease.fee, BlockChain.getInstance().getUnitFeeAtTimestamp(futureFeeIncrease.timestamp));
|
||||
|
||||
// Send another payment transaction
|
||||
// Fee should be determined automatically
|
||||
transactionData = new PaymentTransactionData(TestTransaction.generateBase(alice), bob.getAddress(), 50000);
|
||||
assertEquals(300000000L, transactionData.getFee().longValue());
|
||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||
transaction.sign(alice);
|
||||
ValidationResult result = transaction.importAsUnconfirmed();
|
||||
assertEquals("Transaction should be valid", ValidationResult.OK, result);
|
||||
|
||||
// Now try fetching and setting fee manually
|
||||
transactionData = new PaymentTransactionData(TestTransaction.generateBase(alice), chloe.getAddress(), 50000);
|
||||
transactionData.setFee(new PaymentTransaction(null, null).getUnitFee(transactionData.getTimestamp()));
|
||||
assertEquals(300000000L, transactionData.getFee().longValue());
|
||||
transaction = Transaction.fromData(repository, transactionData);
|
||||
transaction.sign(alice);
|
||||
result = transaction.importAsUnconfirmed();
|
||||
assertEquals("Transaction should be valid", ValidationResult.OK, result);
|
||||
}
|
||||
}
|
||||
|
||||
// test reading the fee schedule from blockchain.json / test-chain-v2.json
|
||||
@Test
|
||||
public void testFeeScheduleInTestchainData() throws Exception {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
final long expectedFutureFeeIncreaseTimestamp = 9999999999999L; // 20 Nov 2286, as per test-chain-v2.json
|
||||
final long expectedFutureFeeIncreaseValue = new AmountTypeAdapter().unmarshal("1");
|
||||
|
||||
assertEquals(expectedFutureFeeIncreaseValue, BlockChain.getInstance().getUnitFeeAtTimestamp(expectedFutureFeeIncreaseTimestamp));
|
||||
|
||||
// Validate unit fees pre and post timestamp
|
||||
assertEquals(10000000, BlockChain.getInstance().getUnitFeeAtTimestamp(expectedFutureFeeIncreaseTimestamp - 1)); // 0.1 QORT
|
||||
assertEquals(100000000, BlockChain.getInstance().getUnitFeeAtTimestamp(expectedFutureFeeIncreaseTimestamp)); // 1 QORT
|
||||
|
||||
// Payment
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
PrivateKeyAccount bob = Common.getTestAccount(repository, "bob");
|
||||
|
||||
PaymentTransactionData transactionData = new PaymentTransactionData(TestTransaction.generateBase(alice), bob.getAddress(), 100000);
|
||||
transactionData.setFee(new PaymentTransaction(null, null).getUnitFee(transactionData.getTimestamp()));
|
||||
assertEquals(10000000L, transactionData.getFee().longValue());
|
||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,11 @@
|
||||
"transactionExpiryPeriod": 86400000,
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"unitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" }
|
||||
],
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" },
|
||||
{ "timestamp": 1645372800000, "fee": "5" }
|
||||
],
|
||||
"requireGroupForApproval": false,
|
||||
@@ -15,6 +18,7 @@
|
||||
"onlineAccountSignaturesMinLifetime": 3600000,
|
||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||
"mempowTransactionUpdatesTimestamp": 0,
|
||||
"rewardsByHeight": [
|
||||
{ "height": 1, "reward": 100 },
|
||||
{ "height": 11, "reward": 10 },
|
||||
|
||||
@@ -4,8 +4,11 @@
|
||||
"transactionExpiryPeriod": 86400000,
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"unitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" }
|
||||
],
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" },
|
||||
{ "timestamp": 1645372800000, "fee": "5" }
|
||||
],
|
||||
"requireGroupForApproval": false,
|
||||
@@ -19,6 +22,7 @@
|
||||
"onlineAccountSignaturesMinLifetime": 3600000,
|
||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||
"mempowTransactionUpdatesTimestamp": 0,
|
||||
"rewardsByHeight": [
|
||||
{ "height": 1, "reward": 100 },
|
||||
{ "height": 11, "reward": 10 },
|
||||
|
||||
@@ -4,8 +4,11 @@
|
||||
"transactionExpiryPeriod": 86400000,
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"unitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" }
|
||||
],
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" },
|
||||
{ "timestamp": 1645372800000, "fee": "5" }
|
||||
],
|
||||
"requireGroupForApproval": false,
|
||||
@@ -20,6 +23,7 @@
|
||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||
"mempowTransactionUpdatesTimestamp": 0,
|
||||
"rewardsByHeight": [
|
||||
{ "height": 1, "reward": 100 },
|
||||
{ "height": 11, "reward": 10 },
|
||||
|
||||
@@ -4,8 +4,11 @@
|
||||
"transactionExpiryPeriod": 86400000,
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"unitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" }
|
||||
],
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" },
|
||||
{ "timestamp": 1645372800000, "fee": "5" }
|
||||
],
|
||||
"requireGroupForApproval": false,
|
||||
@@ -20,6 +23,7 @@
|
||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||
"mempowTransactionUpdatesTimestamp": 0,
|
||||
"rewardsByHeight": [
|
||||
{ "height": 1, "reward": 100 },
|
||||
{ "height": 11, "reward": 10 },
|
||||
|
||||
@@ -4,8 +4,11 @@
|
||||
"transactionExpiryPeriod": 86400000,
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"unitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" }
|
||||
],
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" },
|
||||
{ "timestamp": 1645372800000, "fee": "5" }
|
||||
],
|
||||
"requireGroupForApproval": false,
|
||||
@@ -20,6 +23,7 @@
|
||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||
"mempowTransactionUpdatesTimestamp": 9999999999999,
|
||||
"rewardsByHeight": [
|
||||
{ "height": 1, "reward": 100 },
|
||||
{ "height": 11, "reward": 10 },
|
||||
|
||||
@@ -4,8 +4,11 @@
|
||||
"transactionExpiryPeriod": 86400000,
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"unitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" }
|
||||
],
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" },
|
||||
{ "timestamp": 1645372800000, "fee": "5" }
|
||||
],
|
||||
"requireGroupForApproval": false,
|
||||
@@ -20,6 +23,7 @@
|
||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||
"mempowTransactionUpdatesTimestamp": 0,
|
||||
"rewardsByHeight": [
|
||||
{ "height": 1, "reward": 100 },
|
||||
{ "height": 11, "reward": 10 },
|
||||
|
||||
@@ -4,8 +4,11 @@
|
||||
"transactionExpiryPeriod": 86400000,
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"unitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" }
|
||||
],
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" },
|
||||
{ "timestamp": 1645372800000, "fee": "5" }
|
||||
],
|
||||
"requireGroupForApproval": false,
|
||||
@@ -20,6 +23,7 @@
|
||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||
"mempowTransactionUpdatesTimestamp": 0,
|
||||
"rewardsByHeight": [
|
||||
{ "height": 1, "reward": 100 },
|
||||
{ "height": 11, "reward": 10 },
|
||||
|
||||
@@ -4,8 +4,11 @@
|
||||
"transactionExpiryPeriod": 86400000,
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"unitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" }
|
||||
],
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" },
|
||||
{ "timestamp": 1645372800000, "fee": "5" }
|
||||
],
|
||||
"requireGroupForApproval": false,
|
||||
@@ -20,6 +23,7 @@
|
||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||
"mempowTransactionUpdatesTimestamp": 0,
|
||||
"rewardsByHeight": [
|
||||
{ "height": 1, "reward": 100 },
|
||||
{ "height": 11, "reward": 10 },
|
||||
|
||||
@@ -4,8 +4,11 @@
|
||||
"transactionExpiryPeriod": 86400000,
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"unitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" }
|
||||
],
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" },
|
||||
{ "timestamp": 1645372800000, "fee": "5" }
|
||||
],
|
||||
"requireGroupForApproval": false,
|
||||
@@ -20,6 +23,7 @@
|
||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||
"mempowTransactionUpdatesTimestamp": 0,
|
||||
"rewardsByHeight": [
|
||||
{ "height": 1, "reward": 100 },
|
||||
{ "height": 11, "reward": 10 },
|
||||
|
||||
@@ -4,8 +4,11 @@
|
||||
"transactionExpiryPeriod": 86400000,
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"unitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" }
|
||||
],
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" },
|
||||
{ "timestamp": 1645372800000, "fee": "5" }
|
||||
],
|
||||
"requireGroupForApproval": false,
|
||||
@@ -20,6 +23,7 @@
|
||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||
"mempowTransactionUpdatesTimestamp": 0,
|
||||
"rewardsByHeight": [
|
||||
{ "height": 1, "reward": 100 },
|
||||
{ "height": 11, "reward": 10 },
|
||||
|
||||
@@ -4,8 +4,11 @@
|
||||
"transactionExpiryPeriod": 86400000,
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"unitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" }
|
||||
],
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" },
|
||||
{ "timestamp": 1645372800000, "fee": "5" }
|
||||
],
|
||||
"requireGroupForApproval": false,
|
||||
@@ -19,6 +22,7 @@
|
||||
"onlineAccountSignaturesMinLifetime": 3600000,
|
||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||
"mempowTransactionUpdatesTimestamp": 0,
|
||||
"rewardsByHeight": [
|
||||
{ "height": 1, "reward": 100 },
|
||||
{ "height": 11, "reward": 10 },
|
||||
|
||||
@@ -4,8 +4,11 @@
|
||||
"transactionExpiryPeriod": 86400000,
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 0,
|
||||
"unitFee": "0.00000001",
|
||||
"unitFees": [
|
||||
{ "timestamp": 0, "fee": "0.00000001" }
|
||||
],
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 0, "fee": "0.00000001" },
|
||||
{ "timestamp": 1645372800000, "fee": "5" }
|
||||
],
|
||||
"requireGroupForApproval": false,
|
||||
@@ -20,6 +23,7 @@
|
||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||
"mempowTransactionUpdatesTimestamp": 0,
|
||||
"rewardsByHeight": [
|
||||
{ "height": 1, "reward": 100 },
|
||||
{ "height": 11, "reward": 10 },
|
||||
|
||||
@@ -4,9 +4,13 @@
|
||||
"transactionExpiryPeriod": 86400000,
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"unitFees": [
|
||||
{ "timestamp": 0, "fee": "0.1" },
|
||||
{ "timestamp": 9999999999999, "fee": "1" }
|
||||
],
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 1645372800000, "fee": "5" }
|
||||
{ "timestamp": 0, "fee": "0.1" },
|
||||
{ "timestamp": 9999999999999, "fee": "5" }
|
||||
],
|
||||
"requireGroupForApproval": false,
|
||||
"minAccountLevelToRewardShare": 5,
|
||||
@@ -20,6 +24,7 @@
|
||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||
"mempowTransactionUpdatesTimestamp": 0,
|
||||
"rewardsByHeight": [
|
||||
{ "height": 1, "reward": 100 },
|
||||
{ "height": 11, "reward": 10 },
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
"transactionExpiryPeriod": 86400000,
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.001",
|
||||
"unitFees": [
|
||||
{ "timestamp": 0, "fee": "0.001" }
|
||||
],
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 0, "fee": "1.25" }
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user