mirror of
https://github.com/Qortal/qortal.git
synced 2025-03-26 23:44:34 +00:00
Merge branch 'master' into arbitrary-resources-cache
This commit is contained in:
commit
1aab7d8d8f
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"java.compile.nullAnalysis.mode": "automatic"
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
rootLogger.level = info
|
rootLogger.level = info
|
||||||
# On Windows, uncomment next line to set dirname:
|
# On Windows, uncomment next line to set dirname:
|
||||||
# property.dirname = ${sys:user.home}\\AppData\\Local\\qortal\\
|
# property.dirname = ${sys:user.home}\\AppData\\Local\\qortal\\
|
||||||
property.filename = ${sys:log4j2.filenameTemplate:-log.txt}
|
# property.filename = ${sys:log4j2.filenameTemplate:-log.txt}
|
||||||
|
|
||||||
rootLogger.appenderRef.console.ref = stdout
|
rootLogger.appenderRef.console.ref = stdout
|
||||||
rootLogger.appenderRef.rolling.ref = FILE
|
rootLogger.appenderRef.rolling.ref = FILE
|
||||||
@ -59,11 +59,14 @@ appender.console.filter.threshold.level = error
|
|||||||
|
|
||||||
appender.rolling.type = RollingFile
|
appender.rolling.type = RollingFile
|
||||||
appender.rolling.name = FILE
|
appender.rolling.name = FILE
|
||||||
|
appender.rolling.fileName = qortal.log
|
||||||
|
appender.rolling.filePattern = qortal.%d{dd-MMM}.log.gz
|
||||||
appender.rolling.layout.type = PatternLayout
|
appender.rolling.layout.type = PatternLayout
|
||||||
appender.rolling.layout.pattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
|
appender.rolling.layout.pattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
|
||||||
appender.rolling.filePattern = ./${filename}.%i
|
|
||||||
appender.rolling.policy.type = SizeBasedTriggeringPolicy
|
appender.rolling.policy.type = SizeBasedTriggeringPolicy
|
||||||
appender.rolling.policy.size = 4MB
|
appender.rolling.policy.size = 10MB
|
||||||
|
appender.rolling.strategy.type = DefaultRolloverStrategy
|
||||||
|
appender.rolling.strategy.max = 7
|
||||||
# Set the immediate flush to true (default)
|
# Set the immediate flush to true (default)
|
||||||
# appender.rolling.immediateFlush = true
|
# appender.rolling.immediateFlush = true
|
||||||
# Set the append to true (default), should not overwrite
|
# Set the append to true (default), should not overwrite
|
||||||
|
BIN
lib/org/ciyam/AT/1.4.1/AT-1.4.1.jar
Normal file
BIN
lib/org/ciyam/AT/1.4.1/AT-1.4.1.jar
Normal file
Binary file not shown.
124
lib/org/ciyam/AT/1.4.1/AT-1.4.1.pom
Normal file
124
lib/org/ciyam/AT/1.4.1/AT-1.4.1.pom
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>org.ciyam</groupId>
|
||||||
|
<artifactId>AT</artifactId>
|
||||||
|
<version>1.4.1</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<skipTests>false</skipTests>
|
||||||
|
|
||||||
|
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
|
||||||
|
<maven-source-plugin.version>3.2.0</maven-source-plugin.version>
|
||||||
|
<maven-javadoc-plugin.version>3.3.1</maven-javadoc-plugin.version>
|
||||||
|
<maven-surefire-plugin.version>3.0.0-M4</maven-surefire-plugin.version>
|
||||||
|
<maven-jar-plugin.version>3.2.0</maven-jar-plugin.version>
|
||||||
|
|
||||||
|
<bouncycastle.version>1.64</bouncycastle.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<sourceDirectory>src/main/java</sourceDirectory>
|
||||||
|
<testSourceDirectory>src/test/java</testSourceDirectory>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>${maven-compiler-plugin.version}</version>
|
||||||
|
<configuration>
|
||||||
|
<source>11</source>
|
||||||
|
<target>11</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>${maven-surefire-plugin.version}</version>
|
||||||
|
<configuration>
|
||||||
|
<skipTests>${skipTests}</skipTests>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-source-plugin</artifactId>
|
||||||
|
<version>${maven-source-plugin.version}</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>attach-sources</id>
|
||||||
|
<goals>
|
||||||
|
<goal>jar</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-javadoc-plugin</artifactId>
|
||||||
|
<version>${maven-javadoc-plugin.version}</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>attach-javadoc</id>
|
||||||
|
<goals>
|
||||||
|
<goal>jar</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<version>${maven-jar-plugin.version}</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>test-jar</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>${maven-compiler-plugin.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>${maven-surefire-plugin.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-source-plugin</artifactId>
|
||||||
|
<version>${maven-source-plugin.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-javadoc-plugin</artifactId>
|
||||||
|
<version>${maven-javadoc-plugin.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<version>${maven-jar-plugin.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcprov-jdk15on</artifactId>
|
||||||
|
<version>${bouncycastle.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<version>4.13</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
@ -3,7 +3,7 @@
|
|||||||
<groupId>org.ciyam</groupId>
|
<groupId>org.ciyam</groupId>
|
||||||
<artifactId>AT</artifactId>
|
<artifactId>AT</artifactId>
|
||||||
<versioning>
|
<versioning>
|
||||||
<release>1.4.0</release>
|
<release>1.4.1</release>
|
||||||
<versions>
|
<versions>
|
||||||
<version>1.3.4</version>
|
<version>1.3.4</version>
|
||||||
<version>1.3.5</version>
|
<version>1.3.5</version>
|
||||||
@ -11,7 +11,8 @@
|
|||||||
<version>1.3.7</version>
|
<version>1.3.7</version>
|
||||||
<version>1.3.8</version>
|
<version>1.3.8</version>
|
||||||
<version>1.4.0</version>
|
<version>1.4.0</version>
|
||||||
|
<version>1.4.1</version>
|
||||||
</versions>
|
</versions>
|
||||||
<lastUpdated>20221105114346</lastUpdated>
|
<lastUpdated>20230821074325</lastUpdated>
|
||||||
</versioning>
|
</versioning>
|
||||||
</metadata>
|
</metadata>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
rootLogger.level = info
|
rootLogger.level = info
|
||||||
# On Windows, uncomment next line to set dirname:
|
# On Windows, uncomment next line to set dirname:
|
||||||
# property.dirname = ${sys:user.home}\\AppData\\Local\\qortal\\
|
# property.dirname = ${sys:user.home}\\AppData\\Local\\qortal\\
|
||||||
property.filename = ${sys:log4j2.filenameTemplate:-log.txt}
|
# property.filename = ${sys:log4j2.filenameTemplate:-log.txt}
|
||||||
|
|
||||||
rootLogger.appenderRef.console.ref = stdout
|
rootLogger.appenderRef.console.ref = stdout
|
||||||
rootLogger.appenderRef.rolling.ref = FILE
|
rootLogger.appenderRef.rolling.ref = FILE
|
||||||
@ -59,11 +59,14 @@ appender.console.filter.threshold.level = error
|
|||||||
|
|
||||||
appender.rolling.type = RollingFile
|
appender.rolling.type = RollingFile
|
||||||
appender.rolling.name = FILE
|
appender.rolling.name = FILE
|
||||||
|
appender.rolling.fileName = qortal.log
|
||||||
|
appender.rolling.filePattern = qortal.%d{dd-MMM}.log.gz
|
||||||
appender.rolling.layout.type = PatternLayout
|
appender.rolling.layout.type = PatternLayout
|
||||||
appender.rolling.layout.pattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
|
appender.rolling.layout.pattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
|
||||||
appender.rolling.filePattern = ./${filename}.%i
|
|
||||||
appender.rolling.policy.type = SizeBasedTriggeringPolicy
|
appender.rolling.policy.type = SizeBasedTriggeringPolicy
|
||||||
appender.rolling.policy.size = 4MB
|
appender.rolling.policy.size = 10MB
|
||||||
|
appender.rolling.strategy.type = DefaultRolloverStrategy
|
||||||
|
appender.rolling.strategy.max = 7
|
||||||
# Set the immediate flush to true (default)
|
# Set the immediate flush to true (default)
|
||||||
# appender.rolling.immediateFlush = true
|
# appender.rolling.immediateFlush = true
|
||||||
# Set the append to true (default), should not overwrite
|
# Set the append to true (default), should not overwrite
|
||||||
|
132
pom.xml
132
pom.xml
@ -3,7 +3,7 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>org.qortal</groupId>
|
<groupId>org.qortal</groupId>
|
||||||
<artifactId>qortal</artifactId>
|
<artifactId>qortal</artifactId>
|
||||||
<version>4.2.2</version>
|
<version>4.3.1</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<properties>
|
<properties>
|
||||||
<skipTests>true</skipTests>
|
<skipTests>true</skipTests>
|
||||||
@ -11,32 +11,52 @@
|
|||||||
<bitcoinj.version>0.15.10</bitcoinj.version>
|
<bitcoinj.version>0.15.10</bitcoinj.version>
|
||||||
<bouncycastle.version>1.69</bouncycastle.version>
|
<bouncycastle.version>1.69</bouncycastle.version>
|
||||||
<build.timestamp>${maven.build.timestamp}</build.timestamp>
|
<build.timestamp>${maven.build.timestamp}</build.timestamp>
|
||||||
<ciyam-at.version>1.4.0</ciyam-at.version>
|
<ciyam-at.version>1.4.1</ciyam-at.version>
|
||||||
<commons-net.version>3.6</commons-net.version>
|
<commons-net.version>3.8.0</commons-net.version>
|
||||||
<commons-text.version>1.8</commons-text.version>
|
<commons-text.version>1.10.0</commons-text.version>
|
||||||
<commons-io.version>2.6</commons-io.version>
|
<commons-io.version>2.11.0</commons-io.version>
|
||||||
<commons-compress.version>1.21</commons-compress.version>
|
<commons-compress.version>1.24.0</commons-compress.version>
|
||||||
<commons-lang3.version>3.12.0</commons-lang3.version>
|
<commons-lang3.version>3.13.0</commons-lang3.version>
|
||||||
<xz.version>1.9</xz.version>
|
<xz.version>1.9</xz.version>
|
||||||
<dagger.version>1.2.2</dagger.version>
|
<dagger.version>1.2.2</dagger.version>
|
||||||
<guava.version>28.1-jre</guava.version>
|
<guava.version>32.1.3-jre</guava.version>
|
||||||
<hsqldb.version>2.5.1</hsqldb.version>
|
<hsqldb.version>2.5.1</hsqldb.version>
|
||||||
<homoglyph.version>1.2.1</homoglyph.version>
|
<homoglyph.version>1.2.1</homoglyph.version>
|
||||||
<icu4j.version>70.1</icu4j.version>
|
<icu4j.version>73.2</icu4j.version>
|
||||||
<upnp.version>1.1</upnp.version>
|
<upnp.version>1.1</upnp.version>
|
||||||
<jersey.version>2.29.1</jersey.version>
|
<jaxb-runtime.version>2.3.3</jaxb-runtime.version>
|
||||||
<jetty.version>9.4.29.v20200521</jetty.version>
|
<jersey.version>2.40</jersey.version>
|
||||||
<log4j.version>2.17.1</log4j.version>
|
<jetty.version>9.4.53.v20231009</jetty.version>
|
||||||
|
<log4j.version>2.20.0</log4j.version>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<slf4j.version>1.7.12</slf4j.version>
|
<slf4j.version>1.7.36</slf4j.version>
|
||||||
<swagger-api.version>2.0.9</swagger-api.version>
|
<swagger-api.version>2.0.10</swagger-api.version>
|
||||||
<swagger-ui.version>3.23.8</swagger-ui.version>
|
<swagger-ui.version>3.52.5</swagger-ui.version>
|
||||||
<package-info-maven-plugin.version>1.1.0</package-info-maven-plugin.version>
|
<package-info-maven-plugin.version>1.1.0</package-info-maven-plugin.version>
|
||||||
<jsoup.version>1.13.1</jsoup.version>
|
<jsoup.version>1.16.1</jsoup.version>
|
||||||
<java-diff-utils.version>4.10</java-diff-utils.version>
|
<java-diff-utils.version>4.12</java-diff-utils.version>
|
||||||
<grpc.version>1.45.1</grpc.version>
|
<grpc.version>1.58.0</grpc.version>
|
||||||
<protobuf.version>3.19.4</protobuf.version>
|
<protobuf.version>3.24.4</protobuf.version>
|
||||||
<simplemagic.version>1.17</simplemagic.version>
|
<simplemagic.version>1.17</simplemagic.version>
|
||||||
|
<versions-maven-plugin.version>2.16.1</versions-maven-plugin.version>
|
||||||
|
<maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
|
||||||
|
<git-commit-id-plugin.version>4.9.10</git-commit-id-plugin.version>
|
||||||
|
<replacer.version>1.5.3</replacer.version>
|
||||||
|
<maven-resources-plugin.version>3.3.1</maven-resources-plugin.version>
|
||||||
|
<build-helper-maven-plugin.version>3.4.0</build-helper-maven-plugin.version>
|
||||||
|
<maven-jar-plugin.version>3.3.0</maven-jar-plugin.version>
|
||||||
|
<maven-shade-plugin.version>3.5.1</maven-shade-plugin.version>
|
||||||
|
<reproducible-build-maven-plugin.version>0.16</reproducible-build-maven-plugin.version>
|
||||||
|
<maven-surefire-plugin.version>3.1.2</maven-surefire-plugin.version>
|
||||||
|
<lifecycle-mapping.version>1.0.0</lifecycle-mapping.version>
|
||||||
|
<build-helper-maven-plugin.version>3.4.0</build-helper-maven-plugin.version>
|
||||||
|
<json-simple.version>1.1.1</json-simple.version>
|
||||||
|
<json.version>20231013</json.version>
|
||||||
|
<extendedset.version>0.12.3</extendedset.version>
|
||||||
|
<javax.servlet-api.version>4.0.1</javax.servlet-api.version>
|
||||||
|
<mail.version>1.5.0-b01</mail.version>
|
||||||
|
<junit-jupiter-engine.version>5.3.1</junit-jupiter-engine.version>
|
||||||
|
<hamcrest-library.version>1.3</hamcrest-library.version>
|
||||||
</properties>
|
</properties>
|
||||||
<build>
|
<build>
|
||||||
<sourceDirectory>src/main/java</sourceDirectory>
|
<sourceDirectory>src/main/java</sourceDirectory>
|
||||||
@ -51,14 +71,14 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.codehaus.mojo</groupId>
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
<artifactId>versions-maven-plugin</artifactId>
|
<artifactId>versions-maven-plugin</artifactId>
|
||||||
<version>2.5</version>
|
<version>${versions-maven-plugin.version}</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<generateBackupPoms>false</generateBackupPoms>
|
<generateBackupPoms>false</generateBackupPoms>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<version>3.8.0</version>
|
<version>${maven-compiler-plugin.version}</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<release>11</release>
|
<release>11</release>
|
||||||
</configuration>
|
</configuration>
|
||||||
@ -89,7 +109,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>pl.project13.maven</groupId>
|
<groupId>pl.project13.maven</groupId>
|
||||||
<artifactId>git-commit-id-plugin</artifactId>
|
<artifactId>git-commit-id-plugin</artifactId>
|
||||||
<version>4.0.0</version>
|
<version>${git-commit-id-plugin.version}</version>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<id>get-the-git-infos</id>
|
<id>get-the-git-infos</id>
|
||||||
@ -121,7 +141,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>com.google.code.maven-replacer-plugin</groupId>
|
<groupId>com.google.code.maven-replacer-plugin</groupId>
|
||||||
<artifactId>replacer</artifactId>
|
<artifactId>replacer</artifactId>
|
||||||
<version>1.5.3</version>
|
<version>${replacer.version}</version>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<id>replace-swagger-ui</id>
|
<id>replace-swagger-ui</id>
|
||||||
@ -164,11 +184,13 @@
|
|||||||
<configuration>
|
<configuration>
|
||||||
<file>${project.build.outputDirectory}/git.properties</file>
|
<file>${project.build.outputDirectory}/git.properties</file>
|
||||||
<regex>true</regex>
|
<regex>true</regex>
|
||||||
<regexFlags><regexFlag>MULTILINE</regexFlag></regexFlags>
|
<regexFlags>
|
||||||
|
<regexFlag>MULTILINE</regexFlag>
|
||||||
|
</regexFlags>
|
||||||
<replacements>
|
<replacements>
|
||||||
<replacement>
|
<replacement>
|
||||||
<token>^(#.*$[\n\r]*)</token>
|
<token>^(#.*$[\n\r]*)</token>
|
||||||
<value></value>
|
<value/>
|
||||||
</replacement>
|
</replacement>
|
||||||
</replacements>
|
</replacements>
|
||||||
</configuration>
|
</configuration>
|
||||||
@ -178,7 +200,10 @@
|
|||||||
<!-- add swagger-ui as resource to output package -->
|
<!-- add swagger-ui as resource to output package -->
|
||||||
<plugin>
|
<plugin>
|
||||||
<artifactId>maven-resources-plugin</artifactId>
|
<artifactId>maven-resources-plugin</artifactId>
|
||||||
<version>3.1.0</version>
|
<version>${maven-resources-plugin.version}</version>
|
||||||
|
<configuration>
|
||||||
|
<propertiesEncoding>ISO-8859-1</propertiesEncoding>
|
||||||
|
</configuration>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<id>copy-resources</id>
|
<id>copy-resources</id>
|
||||||
@ -232,7 +257,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.codehaus.mojo</groupId>
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
<artifactId>build-helper-maven-plugin</artifactId>
|
<artifactId>build-helper-maven-plugin</artifactId>
|
||||||
<version>3.0.0</version>
|
<version>${build-helper-maven-plugin.version}</version>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<phase>generate-sources</phase>
|
<phase>generate-sources</phase>
|
||||||
@ -250,7 +275,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-jar-plugin</artifactId>
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
<version>3.2.0</version>
|
<version>${maven-jar-plugin.version}</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<archive>
|
<archive>
|
||||||
<manifest>
|
<manifest>
|
||||||
@ -268,13 +293,12 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-shade-plugin</artifactId>
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
<version>2.4.3</version>
|
<version>${maven-shade-plugin.version}</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||||
<artifactSet>
|
<artifactSet>
|
||||||
<excludes>
|
<excludes>
|
||||||
<!-- Don't include original swagger-UI as we're including our own
|
<!-- Don't include original swagger-UI as we're including our own modified version -->
|
||||||
modified version -->
|
|
||||||
<exclude>org.webjars:swagger-ui</exclude>
|
<exclude>org.webjars:swagger-ui</exclude>
|
||||||
<!-- Don't include JUnit as it's for testing only! -->
|
<!-- Don't include JUnit as it's for testing only! -->
|
||||||
<exclude>junit:junit</exclude>
|
<exclude>junit:junit</exclude>
|
||||||
@ -318,7 +342,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>io.github.zlika</groupId>
|
<groupId>io.github.zlika</groupId>
|
||||||
<artifactId>reproducible-build-maven-plugin</artifactId>
|
<artifactId>reproducible-build-maven-plugin</artifactId>
|
||||||
<version>0.11</version>
|
<version>${reproducible-build-maven-plugin.version}</version>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<phase>package</phase>
|
<phase>package</phase>
|
||||||
@ -335,7 +359,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
<version>2.22.2</version>
|
<version>${maven-surefire-plugin.version}</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<skipTests>${skipTests}</skipTests>
|
<skipTests>${skipTests}</skipTests>
|
||||||
</configuration>
|
</configuration>
|
||||||
@ -347,7 +371,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.eclipse.m2e</groupId>
|
<groupId>org.eclipse.m2e</groupId>
|
||||||
<artifactId>lifecycle-mapping</artifactId>
|
<artifactId>lifecycle-mapping</artifactId>
|
||||||
<version>1.0.0</version>
|
<version>${lifecycle-mapping.version}</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<lifecycleMappingMetadata>
|
<lifecycleMappingMetadata>
|
||||||
<pluginExecutions>
|
<pluginExecutions>
|
||||||
@ -360,14 +384,14 @@
|
|||||||
maven-dependency-plugin
|
maven-dependency-plugin
|
||||||
</artifactId>
|
</artifactId>
|
||||||
<versionRange>
|
<versionRange>
|
||||||
[2.8,)
|
[3.6.0,)
|
||||||
</versionRange>
|
</versionRange>
|
||||||
<goals>
|
<goals>
|
||||||
<goal>unpack</goal>
|
<goal>unpack</goal>
|
||||||
</goals>
|
</goals>
|
||||||
</pluginExecutionFilter>
|
</pluginExecutionFilter>
|
||||||
<action>
|
<action>
|
||||||
<execute></execute>
|
<execute/>
|
||||||
</action>
|
</action>
|
||||||
</pluginExecution>
|
</pluginExecution>
|
||||||
<pluginExecution>
|
<pluginExecution>
|
||||||
@ -386,7 +410,7 @@
|
|||||||
</goals>
|
</goals>
|
||||||
</pluginExecutionFilter>
|
</pluginExecutionFilter>
|
||||||
<action>
|
<action>
|
||||||
<execute></execute>
|
<execute/>
|
||||||
</action>
|
</action>
|
||||||
</pluginExecution>
|
</pluginExecution>
|
||||||
</pluginExecutions>
|
</pluginExecutions>
|
||||||
@ -413,15 +437,17 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.codehaus.mojo</groupId>
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
<artifactId>build-helper-maven-plugin</artifactId>
|
<artifactId>build-helper-maven-plugin</artifactId>
|
||||||
<version>3.0.0</version>
|
<version>${build-helper-maven-plugin.version}</version>
|
||||||
<scope>provided</scope><!-- needed for build, not for runtime -->
|
<scope>provided</scope>
|
||||||
|
<!-- needed for build, not for runtime -->
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- https://mvnrepository.com/artifact/com.github.bohnman/package-info-maven-plugin -->
|
<!-- https://mvnrepository.com/artifact/com.github.bohnman/package-info-maven-plugin -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.bohnman</groupId>
|
<groupId>com.github.bohnman</groupId>
|
||||||
<artifactId>package-info-maven-plugin</artifactId>
|
<artifactId>package-info-maven-plugin</artifactId>
|
||||||
<version>${package-info-maven-plugin.version}</version>
|
<version>${package-info-maven-plugin.version}</version>
|
||||||
<scope>provided</scope><!-- needed for build, not for runtime -->
|
<scope>provided</scope>
|
||||||
|
<!-- needed for build, not for runtime -->
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- HSQLDB for repository -->
|
<!-- HSQLDB for repository -->
|
||||||
<dependency>
|
<dependency>
|
||||||
@ -457,12 +483,12 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.googlecode.json-simple</groupId>
|
<groupId>com.googlecode.json-simple</groupId>
|
||||||
<artifactId>json-simple</artifactId>
|
<artifactId>json-simple</artifactId>
|
||||||
<version>1.1.1</version>
|
<version>${json-simple.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.json</groupId>
|
<groupId>org.json</groupId>
|
||||||
<artifactId>json</artifactId>
|
<artifactId>json</artifactId>
|
||||||
<version>20210307</version>
|
<version>${json.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.commons</groupId>
|
<groupId>org.apache.commons</groupId>
|
||||||
@ -493,7 +519,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.druid</groupId>
|
<groupId>io.druid</groupId>
|
||||||
<artifactId>extendedset</artifactId>
|
<artifactId>extendedset</artifactId>
|
||||||
<version>0.12.3</version>
|
<version>${extendedset.version}</version>
|
||||||
<exclusions>
|
<exclusions>
|
||||||
<!-- exclude old versions of jackson-annotations / jackson-core -->
|
<!-- exclude old versions of jackson-annotations / jackson-core -->
|
||||||
<exclusion>
|
<exclusion>
|
||||||
@ -564,12 +590,12 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>javax.servlet</groupId>
|
<groupId>javax.servlet</groupId>
|
||||||
<artifactId>javax.servlet-api</artifactId>
|
<artifactId>javax.servlet-api</artifactId>
|
||||||
<version>4.0.1</version>
|
<version>${javax.servlet-api.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>javax.mail</groupId>
|
<groupId>javax.mail</groupId>
|
||||||
<artifactId>mail</artifactId>
|
<artifactId>mail</artifactId>
|
||||||
<version>1.5.0-b01</version>
|
<version>${mail.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- Unicode homoglyph utilities -->
|
<!-- Unicode homoglyph utilities -->
|
||||||
<dependency>
|
<dependency>
|
||||||
@ -638,7 +664,8 @@
|
|||||||
<artifactId>jersey-hk2</artifactId>
|
<artifactId>jersey-hk2</artifactId>
|
||||||
<version>${jersey.version}</version>
|
<version>${jersey.version}</version>
|
||||||
<exclusions>
|
<exclusions>
|
||||||
<exclusion><!-- exclude javax.inject-1.jar because other jersey modules include javax.inject v2+ -->
|
<exclusion>
|
||||||
|
<!-- exclude javax.inject-1.jar because other jersey modules include javax.inject v2+ -->
|
||||||
<groupId>javax.inject</groupId>
|
<groupId>javax.inject</groupId>
|
||||||
<artifactId>javax.inject</artifactId>
|
<artifactId>javax.inject</artifactId>
|
||||||
</exclusion>
|
</exclusion>
|
||||||
@ -665,7 +692,8 @@
|
|||||||
<artifactId>swagger-jaxrs2-servlet-initializer</artifactId>
|
<artifactId>swagger-jaxrs2-servlet-initializer</artifactId>
|
||||||
<version>${swagger-api.version}</version>
|
<version>${swagger-api.version}</version>
|
||||||
<exclusions>
|
<exclusions>
|
||||||
<exclusion><!-- excluded because included in swagger-jaxrs2-servlet-initializer -->
|
<exclusion>
|
||||||
|
<!-- excluded because included in swagger-jaxrs2-servlet-initializer -->
|
||||||
<groupId>io.swagger.core.v3</groupId>
|
<groupId>io.swagger.core.v3</groupId>
|
||||||
<artifactId>swagger-integration</artifactId>
|
<artifactId>swagger-integration</artifactId>
|
||||||
</exclusion>
|
</exclusion>
|
||||||
@ -681,12 +709,12 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.junit.jupiter</groupId>
|
<groupId>org.junit.jupiter</groupId>
|
||||||
<artifactId>junit-jupiter-engine</artifactId>
|
<artifactId>junit-jupiter-engine</artifactId>
|
||||||
<version>5.3.1</version>
|
<version>${junit-jupiter-engine.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.hamcrest</groupId>
|
<groupId>org.hamcrest</groupId>
|
||||||
<artifactId>hamcrest-library</artifactId>
|
<artifactId>hamcrest-library</artifactId>
|
||||||
<version>1.3</version>
|
<version>${hamcrest-library.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
-->
|
-->
|
||||||
<!-- BouncyCastle for crypto, including TLS secure networking -->
|
<!-- BouncyCastle for crypto, including TLS secure networking -->
|
||||||
@ -735,5 +763,11 @@
|
|||||||
<artifactId>simplemagic</artifactId>
|
<artifactId>simplemagic</artifactId>
|
||||||
<version>${simplemagic.version}</version>
|
<version>${simplemagic.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- JAXB runtime for WADL support -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.glassfish.jaxb</groupId>
|
||||||
|
<artifactId>jaxb-runtime</artifactId>
|
||||||
|
<version>${jaxb-runtime.version}</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -706,7 +706,7 @@ public class ArbitraryResource {
|
|||||||
ArbitraryDataResource resource = new ArbitraryDataResource(name, ResourceIdType.NAME, service, identifier);
|
ArbitraryDataResource resource = new ArbitraryDataResource(name, ResourceIdType.NAME, service, identifier);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ArbitraryDataTransactionMetadata transactionMetadata = ArbitraryMetadataManager.getInstance().fetchMetadata(resource, false);
|
ArbitraryDataTransactionMetadata transactionMetadata = ArbitraryMetadataManager.getInstance().fetchMetadata(resource, true);
|
||||||
if (transactionMetadata != null) {
|
if (transactionMetadata != null) {
|
||||||
ArbitraryResourceMetadata resourceMetadata = ArbitraryResourceMetadata.fromTransactionMetadata(transactionMetadata, true);
|
ArbitraryResourceMetadata resourceMetadata = ArbitraryResourceMetadata.fromTransactionMetadata(transactionMetadata, true);
|
||||||
if (resourceMetadata != null) {
|
if (resourceMetadata != null) {
|
||||||
|
@ -27,6 +27,7 @@ import org.qortal.api.ApiException;
|
|||||||
import org.qortal.api.ApiExceptionFactory;
|
import org.qortal.api.ApiExceptionFactory;
|
||||||
import org.qortal.data.at.ATData;
|
import org.qortal.data.at.ATData;
|
||||||
import org.qortal.data.at.ATStateData;
|
import org.qortal.data.at.ATStateData;
|
||||||
|
import org.qortal.api.model.AtCreationRequest;
|
||||||
import org.qortal.data.transaction.DeployAtTransactionData;
|
import org.qortal.data.transaction.DeployAtTransactionData;
|
||||||
import org.qortal.repository.DataException;
|
import org.qortal.repository.DataException;
|
||||||
import org.qortal.repository.Repository;
|
import org.qortal.repository.Repository;
|
||||||
@ -38,9 +39,14 @@ import org.qortal.transform.TransformationException;
|
|||||||
import org.qortal.transform.transaction.DeployAtTransactionTransformer;
|
import org.qortal.transform.transaction.DeployAtTransactionTransformer;
|
||||||
import org.qortal.utils.Base58;
|
import org.qortal.utils.Base58;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
|
||||||
@Path("/at")
|
@Path("/at")
|
||||||
@Tag(name = "Automated Transactions")
|
@Tag(name = "Automated Transactions")
|
||||||
public class AtResource {
|
public class AtResource {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(AtResource.class);
|
||||||
|
|
||||||
@Context
|
@Context
|
||||||
HttpServletRequest request;
|
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
|
@POST
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Build raw, unsigned, DEPLOY_AT transaction",
|
summary = "Build raw, unsigned, DEPLOY_AT transaction",
|
||||||
|
@ -16,11 +16,6 @@ import java.net.InetSocketAddress;
|
|||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.time.LocalDate;
|
|
||||||
import java.time.LocalTime;
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.time.ZoneOffset;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
@ -38,7 +33,6 @@ import org.apache.logging.log4j.Logger;
|
|||||||
import org.apache.logging.log4j.core.LoggerContext;
|
import org.apache.logging.log4j.core.LoggerContext;
|
||||||
import org.apache.logging.log4j.core.appender.RollingFileAppender;
|
import org.apache.logging.log4j.core.appender.RollingFileAppender;
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONObject;
|
|
||||||
import org.qortal.account.Account;
|
import org.qortal.account.Account;
|
||||||
import org.qortal.account.PrivateKeyAccount;
|
import org.qortal.account.PrivateKeyAccount;
|
||||||
import org.qortal.api.*;
|
import org.qortal.api.*;
|
||||||
@ -269,7 +263,7 @@ public class AdminResource {
|
|||||||
@GET
|
@GET
|
||||||
@Path("/summary")
|
@Path("/summary")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Summary of activity since midnight, UTC",
|
summary = "Summary of activity past 24 hours",
|
||||||
responses = {
|
responses = {
|
||||||
@ApiResponse(
|
@ApiResponse(
|
||||||
content = @Content(schema = @Schema(implementation = ActivitySummary.class))
|
content = @Content(schema = @Schema(implementation = ActivitySummary.class))
|
||||||
@ -282,23 +276,21 @@ public class AdminResource {
|
|||||||
Security.checkApiCallAllowed(request);
|
Security.checkApiCallAllowed(request);
|
||||||
|
|
||||||
ActivitySummary summary = new ActivitySummary();
|
ActivitySummary summary = new ActivitySummary();
|
||||||
|
|
||||||
LocalDate date = LocalDate.now();
|
long now = NTP.getTime();
|
||||||
LocalTime time = LocalTime.of(0, 0);
|
long oneday = now - 24 * 60 * 60 * 1000L;
|
||||||
ZoneOffset offset = ZoneOffset.UTC;
|
|
||||||
long start = OffsetDateTime.of(date, time, offset).toInstant().toEpochMilli();
|
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
int startHeight = repository.getBlockRepository().getHeightFromTimestamp(start);
|
int startHeight = repository.getBlockRepository().getHeightFromTimestamp(oneday);
|
||||||
int endHeight = repository.getBlockRepository().getBlockchainHeight();
|
int endHeight = repository.getBlockRepository().getBlockchainHeight();
|
||||||
|
|
||||||
summary.setBlockCount(endHeight - startHeight);
|
summary.setBlockCount(endHeight - startHeight);
|
||||||
|
|
||||||
summary.setTransactionCountByType(repository.getTransactionRepository().getTransactionSummary(startHeight + 1, endHeight));
|
summary.setTransactionCountByType(repository.getTransactionRepository().getTransactionSummary(startHeight + 1, endHeight));
|
||||||
|
|
||||||
summary.setAssetsIssued(repository.getAssetRepository().getRecentAssetIds(start).size());
|
summary.setAssetsIssued(repository.getAssetRepository().getRecentAssetIds(oneday).size());
|
||||||
|
|
||||||
summary.setNamesRegistered (repository.getNameRepository().getRecentNames(start).size());
|
summary.setNamesRegistered (repository.getNameRepository().getRecentNames(oneday).size());
|
||||||
|
|
||||||
return summary;
|
return summary;
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
|
@ -129,6 +129,11 @@ public class ArbitraryDataRenderer {
|
|||||||
String filename = this.getFilename(unzippedPath, inPath);
|
String filename = this.getFilename(unzippedPath, inPath);
|
||||||
Path filePath = Paths.get(unzippedPath, filename);
|
Path filePath = Paths.get(unzippedPath, filename);
|
||||||
boolean usingCustomRouting = false;
|
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 the file doesn't exist, we may need to route the request elsewhere, or cleanup
|
||||||
if (!Files.exists(filePath)) {
|
if (!Files.exists(filePath)) {
|
||||||
|
@ -84,6 +84,7 @@ public class Block {
|
|||||||
TRANSACTION_PROCESSING_FAILED(53),
|
TRANSACTION_PROCESSING_FAILED(53),
|
||||||
TRANSACTION_ALREADY_PROCESSED(54),
|
TRANSACTION_ALREADY_PROCESSED(54),
|
||||||
TRANSACTION_NEEDS_APPROVAL(55),
|
TRANSACTION_NEEDS_APPROVAL(55),
|
||||||
|
TRANSACTION_NOT_CONFIRMABLE(56),
|
||||||
AT_STATES_MISMATCH(61),
|
AT_STATES_MISMATCH(61),
|
||||||
ONLINE_ACCOUNTS_INVALID(70),
|
ONLINE_ACCOUNTS_INVALID(70),
|
||||||
ONLINE_ACCOUNT_UNKNOWN(71),
|
ONLINE_ACCOUNT_UNKNOWN(71),
|
||||||
@ -130,6 +131,9 @@ public class Block {
|
|||||||
/** Locally-generated AT fees */
|
/** Locally-generated AT fees */
|
||||||
protected long ourAtFees; // Generated locally
|
protected long ourAtFees; // Generated locally
|
||||||
|
|
||||||
|
/** Cached online accounts validation decision, to avoid revalidating when true */
|
||||||
|
private boolean onlineAccountsAlreadyValid = false;
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
private interface BlockRewardDistributor {
|
private interface BlockRewardDistributor {
|
||||||
long distribute(long amount, Map<String, Long> balanceChanges) throws DataException;
|
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
|
// More information
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1043,6 +1054,10 @@ public class Block {
|
|||||||
if (this.blockData.getHeight() != null && this.blockData.getHeight() == 1)
|
if (this.blockData.getHeight() != null && this.blockData.getHeight() == 1)
|
||||||
return ValidationResult.OK;
|
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
|
// Expand block's online accounts indexes into actual accounts
|
||||||
ConciseSet accountIndexes = BlockTransformer.decodeOnlineAccounts(this.blockData.getEncodedOnlineAccounts());
|
ConciseSet accountIndexes = BlockTransformer.decodeOnlineAccounts(this.blockData.getEncodedOnlineAccounts());
|
||||||
// We use count of online accounts to validate decoded account indexes
|
// 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
|
// All online accounts valid, so save our list of online accounts for potential later use
|
||||||
this.cachedOnlineRewardShares = onlineRewardShares;
|
this.cachedOnlineRewardShares = onlineRewardShares;
|
||||||
|
|
||||||
|
// Remember that the accounts are valid, to speed up subsequent checks
|
||||||
|
this.onlineAccountsAlreadyValid = true;
|
||||||
|
|
||||||
return ValidationResult.OK;
|
return ValidationResult.OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1234,6 +1252,13 @@ public class Block {
|
|||||||
|| transaction.getDeadline() <= this.blockData.getTimestamp())
|
|| transaction.getDeadline() <= this.blockData.getTimestamp())
|
||||||
return ValidationResult.TRANSACTION_TIMESTAMP_INVALID;
|
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
|
// Check transaction isn't already included in a block
|
||||||
if (this.repository.getTransactionRepository().isConfirmed(transactionData.getSignature()))
|
if (this.repository.getTransactionRepository().isConfirmed(transactionData.getSignature()))
|
||||||
return ValidationResult.TRANSACTION_ALREADY_PROCESSED;
|
return ValidationResult.TRANSACTION_ALREADY_PROCESSED;
|
||||||
|
@ -48,9 +48,6 @@ public class BlockChain {
|
|||||||
/** Transaction expiry period, starting from transaction's timestamp, in milliseconds. */
|
/** Transaction expiry period, starting from transaction's timestamp, in milliseconds. */
|
||||||
private long transactionExpiryPeriod;
|
private long transactionExpiryPeriod;
|
||||||
|
|
||||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
|
||||||
private long unitFee;
|
|
||||||
|
|
||||||
private int maxBytesPerUnitFee;
|
private int maxBytesPerUnitFee;
|
||||||
|
|
||||||
/** Maximum acceptable timestamp disagreement offset in milliseconds. */
|
/** Maximum acceptable timestamp disagreement offset in milliseconds. */
|
||||||
@ -89,6 +86,7 @@ public class BlockChain {
|
|||||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||||
public long fee;
|
public long fee;
|
||||||
}
|
}
|
||||||
|
private List<UnitFeesByTimestamp> unitFees;
|
||||||
private List<UnitFeesByTimestamp> nameRegistrationUnitFees;
|
private List<UnitFeesByTimestamp> nameRegistrationUnitFees;
|
||||||
|
|
||||||
/** Map of which blockchain features are enabled when (height/timestamp) */
|
/** Map of which blockchain features are enabled when (height/timestamp) */
|
||||||
@ -211,6 +209,9 @@ public class BlockChain {
|
|||||||
/** Snapshot timestamp for self sponsorship algo V1 */
|
/** Snapshot timestamp for self sponsorship algo V1 */
|
||||||
private long selfSponsorshipAlgoV1SnapshotTimestamp;
|
private long selfSponsorshipAlgoV1SnapshotTimestamp;
|
||||||
|
|
||||||
|
/** Feature-trigger timestamp to modify behaviour of various transactions that support mempow */
|
||||||
|
private long mempowTransactionUpdatesTimestamp;
|
||||||
|
|
||||||
/** Max reward shares by block height */
|
/** Max reward shares by block height */
|
||||||
public static class MaxRewardSharesByTimestamp {
|
public static class MaxRewardSharesByTimestamp {
|
||||||
public long timestamp;
|
public long timestamp;
|
||||||
@ -346,10 +347,6 @@ public class BlockChain {
|
|||||||
return this.isTestChain;
|
return this.isTestChain;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getUnitFee() {
|
|
||||||
return this.unitFee;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMaxBytesPerUnitFee() {
|
public int getMaxBytesPerUnitFee() {
|
||||||
return this.maxBytesPerUnitFee;
|
return this.maxBytesPerUnitFee;
|
||||||
}
|
}
|
||||||
@ -376,6 +373,11 @@ public class BlockChain {
|
|||||||
return this.selfSponsorshipAlgoV1SnapshotTimestamp;
|
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. */
|
/** Returns true if approval-needing transaction types require a txGroupId other than NO_GROUP. */
|
||||||
public boolean getRequireGroupForApproval() {
|
public boolean getRequireGroupForApproval() {
|
||||||
return this.requireGroupForApproval;
|
return this.requireGroupForApproval;
|
||||||
@ -547,13 +549,22 @@ public class BlockChain {
|
|||||||
throw new IllegalStateException(String.format("No block timing info available for height %d", ourHeight));
|
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) {
|
public long getNameRegistrationUnitFeeAtTimestamp(long ourTimestamp) {
|
||||||
for (int i = nameRegistrationUnitFees.size() - 1; i >= 0; --i)
|
for (int i = nameRegistrationUnitFees.size() - 1; i >= 0; --i)
|
||||||
if (nameRegistrationUnitFees.get(i).timestamp <= ourTimestamp)
|
if (nameRegistrationUnitFees.get(i).timestamp <= ourTimestamp)
|
||||||
return nameRegistrationUnitFees.get(i).fee;
|
return nameRegistrationUnitFees.get(i).fee;
|
||||||
|
|
||||||
// Default to system-wide unit fee
|
// Shouldn't happen, but set a sensible default just in case
|
||||||
return this.getUnitFee();
|
return 100000;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getMaxRewardSharesAtTimestamp(long ourTimestamp) {
|
public int getMaxRewardSharesAtTimestamp(long ourTimestamp) {
|
||||||
|
@ -562,6 +562,9 @@ public class BlockMinter extends Thread {
|
|||||||
// Sign to create block's signature
|
// Sign to create block's signature
|
||||||
newBlock.sign();
|
newBlock.sign();
|
||||||
|
|
||||||
|
// Ensure online accounts are fully re-validated in this final check
|
||||||
|
newBlock.clearOnlineAccountsValidationCache();
|
||||||
|
|
||||||
// Is newBlock still valid?
|
// Is newBlock still valid?
|
||||||
ValidationResult validationResult = newBlock.isValid();
|
ValidationResult validationResult = newBlock.isValid();
|
||||||
if (validationResult != ValidationResult.OK)
|
if (validationResult != ValidationResult.OK)
|
||||||
|
@ -229,13 +229,6 @@ public class Synchronizer extends Thread {
|
|||||||
peers.removeIf(Controller.hasOldVersion);
|
peers.removeIf(Controller.hasOldVersion);
|
||||||
|
|
||||||
checkRecoveryModeForPeers(peers);
|
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
|
// Check we have enough peers to potentially synchronize
|
||||||
if (peers.size() < Settings.getInstance().getMinBlockchainPeers())
|
if (peers.size() < Settings.getInstance().getMinBlockchainPeers())
|
||||||
@ -262,10 +255,7 @@ public class Synchronizer extends Thread {
|
|||||||
peers.removeIf(Controller.hasInferiorChainTip);
|
peers.removeIf(Controller.hasInferiorChainTip);
|
||||||
|
|
||||||
// Remove any peers that are no longer on a recent block since the last check
|
// 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
|
peers.removeIf(Controller.hasNoRecentBlock);
|
||||||
if (!recoveryMode) {
|
|
||||||
peers.removeIf(Controller.hasNoRecentBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
final int peersRemoved = peersBeforeComparison - peers.size();
|
final int peersRemoved = peersBeforeComparison - peers.size();
|
||||||
if (peersRemoved > 0 && peers.size() > 0)
|
if (peersRemoved > 0 && peers.size() > 0)
|
||||||
@ -1340,8 +1330,8 @@ public class Synchronizer extends Thread {
|
|||||||
return SynchronizationResult.INVALID_DATA;
|
return SynchronizationResult.INVALID_DATA;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Final check to make sure the peer isn't out of date (except for when we're in recovery mode)
|
// Final check to make sure the peer isn't out of date
|
||||||
if (!recoveryMode && peer.getChainTipData() != null) {
|
if (peer.getChainTipData() != null) {
|
||||||
final Long minLatestBlockTimestamp = Controller.getMinimumLatestBlockTimestamp();
|
final Long minLatestBlockTimestamp = Controller.getMinimumLatestBlockTimestamp();
|
||||||
final Long peerLastBlockTimestamp = peer.getChainTipData().getTimestamp();
|
final Long peerLastBlockTimestamp = peer.getChainTipData().getTimestamp();
|
||||||
if (peerLastBlockTimestamp == null || peerLastBlockTimestamp < minLatestBlockTimestamp) {
|
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. */
|
/** 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<>());
|
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() {
|
public static synchronized TransactionImporter getInstance() {
|
||||||
if (instance == null) {
|
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());
|
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) {
|
} catch (DataException e) {
|
||||||
LOGGER.error("Repository issue while processing incoming transactions", e);
|
LOGGER.error("Repository issue while processing incoming transactions", e);
|
||||||
}
|
}
|
||||||
@ -254,6 +251,15 @@ public class TransactionImporter extends Thread {
|
|||||||
int processedCount = 0;
|
int processedCount = 0;
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
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
|
// Import transactions with valid signatures
|
||||||
try {
|
try {
|
||||||
for (int i = 0; i < sigValidTransactions.size(); ++i) {
|
for (int i = 0; i < sigValidTransactions.size(); ++i) {
|
||||||
@ -286,6 +292,15 @@ public class TransactionImporter extends Thread {
|
|||||||
|
|
||||||
case OK: {
|
case OK: {
|
||||||
LOGGER.debug(() -> String.format("Imported %s transaction %s", transactionData.getType().name(), Base58.encode(transactionData.getSignature())));
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -314,9 +329,18 @@ public class TransactionImporter extends Thread {
|
|||||||
// Transaction has been processed, even if only to reject it
|
// Transaction has been processed, even if only to reject it
|
||||||
removeIncomingTransaction(transactionData.getSignature());
|
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 {
|
} finally {
|
||||||
LOGGER.debug("Finished importing {} incoming transaction{}", processedCount, (processedCount == 1 ? "" : "s"));
|
LOGGER.debug("Finished importing {} incoming transaction{}", processedCount, (processedCount == 1 ? "" : "s"));
|
||||||
blockchainLock.unlock();
|
blockchainLock.unlock();
|
||||||
|
|
||||||
|
// Clear the unconfirmed transaction cache so new data can be populated in the next cycle
|
||||||
|
unconfirmedTransactionsCache = null;
|
||||||
}
|
}
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
LOGGER.error("Repository issue while importing incoming transactions", e);
|
LOGGER.error("Repository issue while importing incoming transactions", e);
|
||||||
|
@ -276,7 +276,10 @@ public class ArbitraryDataManager extends Thread {
|
|||||||
int offset = 0;
|
int offset = 0;
|
||||||
|
|
||||||
while (!isStopping) {
|
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?
|
// Any arbitrary transactions we want to fetch data for?
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
@ -19,6 +19,7 @@ import org.qortal.data.transaction.MessageTransactionData;
|
|||||||
import org.qortal.group.Group;
|
import org.qortal.group.Group;
|
||||||
import org.qortal.repository.DataException;
|
import org.qortal.repository.DataException;
|
||||||
import org.qortal.repository.Repository;
|
import org.qortal.repository.Repository;
|
||||||
|
import org.qortal.repository.RepositoryManager;
|
||||||
import org.qortal.transaction.DeployAtTransaction;
|
import org.qortal.transaction.DeployAtTransaction;
|
||||||
import org.qortal.transaction.MessageTransaction;
|
import org.qortal.transaction.MessageTransaction;
|
||||||
import org.qortal.transaction.Transaction.ValidationResult;
|
import org.qortal.transaction.Transaction.ValidationResult;
|
||||||
@ -317,20 +318,36 @@ public class BitcoinACCTv3TradeBot implements AcctTradeBot {
|
|||||||
|
|
||||||
boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData);
|
boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData);
|
||||||
if (!isMessageAlreadySent) {
|
if (!isMessageAlreadySent) {
|
||||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
// Do this in a new thread so caller doesn't have to wait for computeNonce()
|
||||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
// 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();
|
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||||
messageTransaction.sign(sender);
|
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
|
// reset repository state to prevent deadlock
|
||||||
repository.discardChanges();
|
threadsRepository.discardChanges();
|
||||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
|
||||||
|
|
||||||
if (result != ValidationResult.OK) {
|
if (messageTransaction.isSignatureValid()) {
|
||||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name()));
|
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||||
return ResponseResult.NETWORK_ISSUE;
|
|
||||||
}
|
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));
|
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());
|
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||||
MessageTransaction outgoingMessageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, outgoingMessageData, false, false);
|
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();
|
outgoingMessageTransaction.computeNonce();
|
||||||
|
MessageTransactionData newMessageTransactionData = (MessageTransactionData) outgoingMessageTransaction.getTransactionData();
|
||||||
|
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), outgoingMessageTransaction.getPoWDifficulty());
|
||||||
outgoingMessageTransaction.sign(sender);
|
outgoingMessageTransaction.sign(sender);
|
||||||
|
|
||||||
// reset repository state to prevent deadlock
|
// reset repository state to prevent deadlock
|
||||||
repository.discardChanges();
|
repository.discardChanges();
|
||||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
|
||||||
|
|
||||||
if (result != ValidationResult.OK) {
|
if (outgoingMessageTransaction.isSignatureValid()) {
|
||||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -668,15 +695,25 @@ public class BitcoinACCTv3TradeBot implements AcctTradeBot {
|
|||||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
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();
|
messageTransaction.computeNonce();
|
||||||
|
MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData();
|
||||||
|
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty());
|
||||||
messageTransaction.sign(sender);
|
messageTransaction.sign(sender);
|
||||||
|
|
||||||
// Reset repository state to prevent deadlock
|
// Reset repository state to prevent deadlock
|
||||||
repository.discardChanges();
|
repository.discardChanges();
|
||||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
|
||||||
|
|
||||||
if (result != ValidationResult.OK) {
|
if (messageTransaction.isSignatureValid()) {
|
||||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import org.qortal.data.transaction.MessageTransactionData;
|
|||||||
import org.qortal.group.Group;
|
import org.qortal.group.Group;
|
||||||
import org.qortal.repository.DataException;
|
import org.qortal.repository.DataException;
|
||||||
import org.qortal.repository.Repository;
|
import org.qortal.repository.Repository;
|
||||||
|
import org.qortal.repository.RepositoryManager;
|
||||||
import org.qortal.transaction.DeployAtTransaction;
|
import org.qortal.transaction.DeployAtTransaction;
|
||||||
import org.qortal.transaction.MessageTransaction;
|
import org.qortal.transaction.MessageTransaction;
|
||||||
import org.qortal.transaction.Transaction.ValidationResult;
|
import org.qortal.transaction.Transaction.ValidationResult;
|
||||||
@ -317,20 +318,36 @@ public class DigibyteACCTv3TradeBot implements AcctTradeBot {
|
|||||||
|
|
||||||
boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData);
|
boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData);
|
||||||
if (!isMessageAlreadySent) {
|
if (!isMessageAlreadySent) {
|
||||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
// Do this in a new thread so caller doesn't have to wait for computeNonce()
|
||||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
// 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();
|
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||||
messageTransaction.sign(sender);
|
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
|
// reset repository state to prevent deadlock
|
||||||
repository.discardChanges();
|
threadsRepository.discardChanges();
|
||||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
|
||||||
|
|
||||||
if (result != ValidationResult.OK) {
|
if (messageTransaction.isSignatureValid()) {
|
||||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name()));
|
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||||
return ResponseResult.NETWORK_ISSUE;
|
|
||||||
}
|
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));
|
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());
|
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||||
MessageTransaction outgoingMessageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, outgoingMessageData, false, false);
|
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();
|
outgoingMessageTransaction.computeNonce();
|
||||||
|
MessageTransactionData newMessageTransactionData = (MessageTransactionData) outgoingMessageTransaction.getTransactionData();
|
||||||
|
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), outgoingMessageTransaction.getPoWDifficulty());
|
||||||
outgoingMessageTransaction.sign(sender);
|
outgoingMessageTransaction.sign(sender);
|
||||||
|
|
||||||
// reset repository state to prevent deadlock
|
// reset repository state to prevent deadlock
|
||||||
repository.discardChanges();
|
repository.discardChanges();
|
||||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
|
||||||
|
|
||||||
if (result != ValidationResult.OK) {
|
if (outgoingMessageTransaction.isSignatureValid()) {
|
||||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -668,15 +695,25 @@ public class DigibyteACCTv3TradeBot implements AcctTradeBot {
|
|||||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
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();
|
messageTransaction.computeNonce();
|
||||||
|
MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData();
|
||||||
|
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty());
|
||||||
messageTransaction.sign(sender);
|
messageTransaction.sign(sender);
|
||||||
|
|
||||||
// Reset repository state to prevent deadlock
|
// Reset repository state to prevent deadlock
|
||||||
repository.discardChanges();
|
repository.discardChanges();
|
||||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
|
||||||
|
|
||||||
if (result != ValidationResult.OK) {
|
if (messageTransaction.isSignatureValid()) {
|
||||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import org.qortal.data.transaction.MessageTransactionData;
|
|||||||
import org.qortal.group.Group;
|
import org.qortal.group.Group;
|
||||||
import org.qortal.repository.DataException;
|
import org.qortal.repository.DataException;
|
||||||
import org.qortal.repository.Repository;
|
import org.qortal.repository.Repository;
|
||||||
|
import org.qortal.repository.RepositoryManager;
|
||||||
import org.qortal.transaction.DeployAtTransaction;
|
import org.qortal.transaction.DeployAtTransaction;
|
||||||
import org.qortal.transaction.MessageTransaction;
|
import org.qortal.transaction.MessageTransaction;
|
||||||
import org.qortal.transaction.Transaction.ValidationResult;
|
import org.qortal.transaction.Transaction.ValidationResult;
|
||||||
@ -317,20 +318,36 @@ public class DogecoinACCTv1TradeBot implements AcctTradeBot {
|
|||||||
|
|
||||||
boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData);
|
boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData);
|
||||||
if (!isMessageAlreadySent) {
|
if (!isMessageAlreadySent) {
|
||||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
// Do this in a new thread so caller doesn't have to wait for computeNonce()
|
||||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
// 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();
|
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||||
messageTransaction.sign(sender);
|
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
|
// reset repository state to prevent deadlock
|
||||||
repository.discardChanges();
|
threadsRepository.discardChanges();
|
||||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
|
||||||
|
|
||||||
if (result != ValidationResult.OK) {
|
if (messageTransaction.isSignatureValid()) {
|
||||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name()));
|
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||||
return ResponseResult.NETWORK_ISSUE;
|
|
||||||
}
|
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));
|
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());
|
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||||
MessageTransaction outgoingMessageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, outgoingMessageData, false, false);
|
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();
|
outgoingMessageTransaction.computeNonce();
|
||||||
|
MessageTransactionData newMessageTransactionData = (MessageTransactionData) outgoingMessageTransaction.getTransactionData();
|
||||||
|
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), outgoingMessageTransaction.getPoWDifficulty());
|
||||||
outgoingMessageTransaction.sign(sender);
|
outgoingMessageTransaction.sign(sender);
|
||||||
|
|
||||||
// reset repository state to prevent deadlock
|
// reset repository state to prevent deadlock
|
||||||
repository.discardChanges();
|
repository.discardChanges();
|
||||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
|
||||||
|
|
||||||
if (result != ValidationResult.OK) {
|
if (outgoingMessageTransaction.isSignatureValid()) {
|
||||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -668,15 +695,25 @@ public class DogecoinACCTv1TradeBot implements AcctTradeBot {
|
|||||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
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();
|
messageTransaction.computeNonce();
|
||||||
|
MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData();
|
||||||
|
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty());
|
||||||
messageTransaction.sign(sender);
|
messageTransaction.sign(sender);
|
||||||
|
|
||||||
// Reset repository state to prevent deadlock
|
// Reset repository state to prevent deadlock
|
||||||
repository.discardChanges();
|
repository.discardChanges();
|
||||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
|
||||||
|
|
||||||
if (result != ValidationResult.OK) {
|
if (messageTransaction.isSignatureValid()) {
|
||||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import org.qortal.data.transaction.MessageTransactionData;
|
|||||||
import org.qortal.group.Group;
|
import org.qortal.group.Group;
|
||||||
import org.qortal.repository.DataException;
|
import org.qortal.repository.DataException;
|
||||||
import org.qortal.repository.Repository;
|
import org.qortal.repository.Repository;
|
||||||
|
import org.qortal.repository.RepositoryManager;
|
||||||
import org.qortal.transaction.DeployAtTransaction;
|
import org.qortal.transaction.DeployAtTransaction;
|
||||||
import org.qortal.transaction.MessageTransaction;
|
import org.qortal.transaction.MessageTransaction;
|
||||||
import org.qortal.transaction.Transaction.ValidationResult;
|
import org.qortal.transaction.Transaction.ValidationResult;
|
||||||
@ -317,20 +318,36 @@ public class DogecoinACCTv3TradeBot implements AcctTradeBot {
|
|||||||
|
|
||||||
boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData);
|
boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData);
|
||||||
if (!isMessageAlreadySent) {
|
if (!isMessageAlreadySent) {
|
||||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
// Do this in a new thread so caller doesn't have to wait for computeNonce()
|
||||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
// 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();
|
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||||
messageTransaction.sign(sender);
|
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
|
// reset repository state to prevent deadlock
|
||||||
repository.discardChanges();
|
threadsRepository.discardChanges();
|
||||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
|
||||||
|
|
||||||
if (result != ValidationResult.OK) {
|
if (messageTransaction.isSignatureValid()) {
|
||||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name()));
|
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||||
return ResponseResult.NETWORK_ISSUE;
|
|
||||||
}
|
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));
|
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());
|
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||||
MessageTransaction outgoingMessageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, outgoingMessageData, false, false);
|
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();
|
outgoingMessageTransaction.computeNonce();
|
||||||
|
MessageTransactionData newMessageTransactionData = (MessageTransactionData) outgoingMessageTransaction.getTransactionData();
|
||||||
|
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), outgoingMessageTransaction.getPoWDifficulty());
|
||||||
outgoingMessageTransaction.sign(sender);
|
outgoingMessageTransaction.sign(sender);
|
||||||
|
|
||||||
// reset repository state to prevent deadlock
|
// reset repository state to prevent deadlock
|
||||||
repository.discardChanges();
|
repository.discardChanges();
|
||||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
|
||||||
|
|
||||||
if (result != ValidationResult.OK) {
|
if (outgoingMessageTransaction.isSignatureValid()) {
|
||||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -668,15 +695,25 @@ public class DogecoinACCTv3TradeBot implements AcctTradeBot {
|
|||||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
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();
|
messageTransaction.computeNonce();
|
||||||
|
MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData();
|
||||||
|
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty());
|
||||||
messageTransaction.sign(sender);
|
messageTransaction.sign(sender);
|
||||||
|
|
||||||
// Reset repository state to prevent deadlock
|
// Reset repository state to prevent deadlock
|
||||||
repository.discardChanges();
|
repository.discardChanges();
|
||||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
|
||||||
|
|
||||||
if (result != ValidationResult.OK) {
|
if (messageTransaction.isSignatureValid()) {
|
||||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -325,15 +325,24 @@ public class LitecoinACCTv3TradeBot implements AcctTradeBot {
|
|||||||
PrivateKeyAccount sender = new PrivateKeyAccount(threadsRepository, tradeBotData.getTradePrivateKey());
|
PrivateKeyAccount sender = new PrivateKeyAccount(threadsRepository, tradeBotData.getTradePrivateKey());
|
||||||
MessageTransaction messageTransaction = MessageTransaction.build(threadsRepository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
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();
|
messageTransaction.computeNonce();
|
||||||
|
MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData();
|
||||||
|
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty());
|
||||||
messageTransaction.sign(sender);
|
messageTransaction.sign(sender);
|
||||||
|
|
||||||
// reset repository state to prevent deadlock
|
// reset repository state to prevent deadlock
|
||||||
threadsRepository.discardChanges();
|
threadsRepository.discardChanges();
|
||||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
|
||||||
|
|
||||||
if (result != ValidationResult.OK) {
|
if (messageTransaction.isSignatureValid()) {
|
||||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name()));
|
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) {
|
} catch (DataException e) {
|
||||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, e.getMessage()));
|
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());
|
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||||
MessageTransaction outgoingMessageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, outgoingMessageData, false, false);
|
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();
|
outgoingMessageTransaction.computeNonce();
|
||||||
|
MessageTransactionData newMessageTransactionData = (MessageTransactionData) outgoingMessageTransaction.getTransactionData();
|
||||||
|
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), outgoingMessageTransaction.getPoWDifficulty());
|
||||||
outgoingMessageTransaction.sign(sender);
|
outgoingMessageTransaction.sign(sender);
|
||||||
|
|
||||||
// reset repository state to prevent deadlock
|
// reset repository state to prevent deadlock
|
||||||
repository.discardChanges();
|
repository.discardChanges();
|
||||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
|
||||||
|
|
||||||
if (result != ValidationResult.OK) {
|
if (outgoingMessageTransaction.isSignatureValid()) {
|
||||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -676,15 +695,25 @@ public class LitecoinACCTv3TradeBot implements AcctTradeBot {
|
|||||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
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();
|
messageTransaction.computeNonce();
|
||||||
|
MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData();
|
||||||
|
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty());
|
||||||
messageTransaction.sign(sender);
|
messageTransaction.sign(sender);
|
||||||
|
|
||||||
// Reset repository state to prevent deadlock
|
// Reset repository state to prevent deadlock
|
||||||
repository.discardChanges();
|
repository.discardChanges();
|
||||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
|
||||||
|
|
||||||
if (result != ValidationResult.OK) {
|
if (messageTransaction.isSignatureValid()) {
|
||||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import org.qortal.data.transaction.MessageTransactionData;
|
|||||||
import org.qortal.group.Group;
|
import org.qortal.group.Group;
|
||||||
import org.qortal.repository.DataException;
|
import org.qortal.repository.DataException;
|
||||||
import org.qortal.repository.Repository;
|
import org.qortal.repository.Repository;
|
||||||
|
import org.qortal.repository.RepositoryManager;
|
||||||
import org.qortal.transaction.DeployAtTransaction;
|
import org.qortal.transaction.DeployAtTransaction;
|
||||||
import org.qortal.transaction.MessageTransaction;
|
import org.qortal.transaction.MessageTransaction;
|
||||||
import org.qortal.transaction.Transaction.ValidationResult;
|
import org.qortal.transaction.Transaction.ValidationResult;
|
||||||
@ -320,20 +321,36 @@ public class PirateChainACCTv3TradeBot implements AcctTradeBot {
|
|||||||
|
|
||||||
boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData);
|
boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData);
|
||||||
if (!isMessageAlreadySent) {
|
if (!isMessageAlreadySent) {
|
||||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
// Do this in a new thread so caller doesn't have to wait for computeNonce()
|
||||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
// 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();
|
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||||
messageTransaction.sign(sender);
|
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
|
// reset repository state to prevent deadlock
|
||||||
repository.discardChanges();
|
threadsRepository.discardChanges();
|
||||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
|
||||||
|
|
||||||
if (result != ValidationResult.OK) {
|
if (messageTransaction.isSignatureValid()) {
|
||||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name()));
|
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||||
return ResponseResult.NETWORK_ISSUE;
|
|
||||||
}
|
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));
|
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());
|
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||||
MessageTransaction outgoingMessageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, outgoingMessageData, false, false);
|
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();
|
outgoingMessageTransaction.computeNonce();
|
||||||
|
MessageTransactionData newMessageTransactionData = (MessageTransactionData) outgoingMessageTransaction.getTransactionData();
|
||||||
|
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), outgoingMessageTransaction.getPoWDifficulty());
|
||||||
outgoingMessageTransaction.sign(sender);
|
outgoingMessageTransaction.sign(sender);
|
||||||
|
|
||||||
// reset repository state to prevent deadlock
|
// reset repository state to prevent deadlock
|
||||||
repository.discardChanges();
|
repository.discardChanges();
|
||||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
|
||||||
|
|
||||||
if (result != ValidationResult.OK) {
|
if (outgoingMessageTransaction.isSignatureValid()) {
|
||||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -681,15 +708,25 @@ public class PirateChainACCTv3TradeBot implements AcctTradeBot {
|
|||||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
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();
|
messageTransaction.computeNonce();
|
||||||
|
MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData();
|
||||||
|
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty());
|
||||||
messageTransaction.sign(sender);
|
messageTransaction.sign(sender);
|
||||||
|
|
||||||
// Reset repository state to prevent deadlock
|
// Reset repository state to prevent deadlock
|
||||||
repository.discardChanges();
|
repository.discardChanges();
|
||||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
|
||||||
|
|
||||||
if (result != ValidationResult.OK) {
|
if (messageTransaction.isSignatureValid()) {
|
||||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import org.qortal.data.transaction.MessageTransactionData;
|
|||||||
import org.qortal.group.Group;
|
import org.qortal.group.Group;
|
||||||
import org.qortal.repository.DataException;
|
import org.qortal.repository.DataException;
|
||||||
import org.qortal.repository.Repository;
|
import org.qortal.repository.Repository;
|
||||||
|
import org.qortal.repository.RepositoryManager;
|
||||||
import org.qortal.transaction.DeployAtTransaction;
|
import org.qortal.transaction.DeployAtTransaction;
|
||||||
import org.qortal.transaction.MessageTransaction;
|
import org.qortal.transaction.MessageTransaction;
|
||||||
import org.qortal.transaction.Transaction.ValidationResult;
|
import org.qortal.transaction.Transaction.ValidationResult;
|
||||||
@ -317,20 +318,36 @@ public class RavencoinACCTv3TradeBot implements AcctTradeBot {
|
|||||||
|
|
||||||
boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData);
|
boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData);
|
||||||
if (!isMessageAlreadySent) {
|
if (!isMessageAlreadySent) {
|
||||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
// Do this in a new thread so caller doesn't have to wait for computeNonce()
|
||||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
// 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();
|
LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient);
|
||||||
messageTransaction.sign(sender);
|
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
|
// reset repository state to prevent deadlock
|
||||||
repository.discardChanges();
|
threadsRepository.discardChanges();
|
||||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
|
||||||
|
|
||||||
if (result != ValidationResult.OK) {
|
if (messageTransaction.isSignatureValid()) {
|
||||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name()));
|
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
||||||
return ResponseResult.NETWORK_ISSUE;
|
|
||||||
}
|
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));
|
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());
|
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||||
MessageTransaction outgoingMessageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, outgoingMessageData, false, false);
|
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();
|
outgoingMessageTransaction.computeNonce();
|
||||||
|
MessageTransactionData newMessageTransactionData = (MessageTransactionData) outgoingMessageTransaction.getTransactionData();
|
||||||
|
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), outgoingMessageTransaction.getPoWDifficulty());
|
||||||
outgoingMessageTransaction.sign(sender);
|
outgoingMessageTransaction.sign(sender);
|
||||||
|
|
||||||
// reset repository state to prevent deadlock
|
// reset repository state to prevent deadlock
|
||||||
repository.discardChanges();
|
repository.discardChanges();
|
||||||
ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed();
|
|
||||||
|
|
||||||
if (result != ValidationResult.OK) {
|
if (outgoingMessageTransaction.isSignatureValid()) {
|
||||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -668,15 +695,25 @@ public class RavencoinACCTv3TradeBot implements AcctTradeBot {
|
|||||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey());
|
||||||
MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false);
|
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();
|
messageTransaction.computeNonce();
|
||||||
|
MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData();
|
||||||
|
LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty());
|
||||||
messageTransaction.sign(sender);
|
messageTransaction.sign(sender);
|
||||||
|
|
||||||
// Reset repository state to prevent deadlock
|
// Reset repository state to prevent deadlock
|
||||||
repository.discardChanges();
|
repository.discardChanges();
|
||||||
ValidationResult result = messageTransaction.importAsUnconfirmed();
|
|
||||||
|
|
||||||
if (result != ValidationResult.OK) {
|
if (messageTransaction.isSignatureValid()) {
|
||||||
LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name()));
|
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -333,7 +333,7 @@ public class TradeBot implements Listener {
|
|||||||
SysTray.getInstance().showMessage("Trade-Bot", String.format("%s: %s", tradeBotData.getAtAddress(), newState), MessageType.INFO);
|
SysTray.getInstance().showMessage("Trade-Bot", String.format("%s: %s", tradeBotData.getAtAddress(), newState), MessageType.INFO);
|
||||||
|
|
||||||
if (logMessageSupplier != null)
|
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));
|
LOGGER.debug(() -> String.format("new state for trade-bot entry based on AT %s: %s", tradeBotData.getAtAddress(), newState));
|
||||||
|
|
||||||
@ -712,30 +712,16 @@ public class TradeBot implements Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
List<byte[]> signatures = repository.getTransactionRepository().getSignaturesMatchingCriteria(null, null, null, Arrays.asList(Transaction.TransactionType.MESSAGE), null, null, crossChainTradeData.qortalCreatorTradeAddress, TransactionsResource.ConfirmationStatus.CONFIRMED, null, null, null);
|
List<TransactionData> transactions = repository.getTransactionRepository().getUnconfirmedTransactions(Arrays.asList(Transaction.TransactionType.MESSAGE), null, null, null, null);
|
||||||
if (signatures.size() < getMaxTradeOfferAttempts) {
|
|
||||||
// Less than 3 (or user-specified number of) MESSAGE transactions relate to this trade, so assume it is ok
|
|
||||||
validTrades.put(crossChainTradeData.qortalAtAddress, now);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<TransactionData> transactions = new ArrayList<>(signatures.size());
|
for (TransactionData transactionData : transactions) {
|
||||||
for (byte[] signature : signatures) {
|
// Treat as failed if buy attempt was more than 60 mins ago (as it's still in the OFFERING state)
|
||||||
transactions.add(repository.getTransactionRepository().fromSignature(signature));
|
if (transactionData.getRecipient().equals(crossChainTradeData.qortalCreatorTradeAddress) && now - transactionData.getTimestamp() > 60*60*1000L) {
|
||||||
}
|
failedTrades.put(crossChainTradeData.qortalAtAddress, now);
|
||||||
transactions.sort(Transaction.getDataComparator());
|
updatedCrossChainTrades.remove(crossChainTradeData);
|
||||||
|
} else {
|
||||||
// Get timestamp of the first MESSAGE transaction
|
validTrades.put(crossChainTradeData.qortalAtAddress, now);
|
||||||
long firstMessageTimestamp = transactions.get(0).getTimestamp();
|
}
|
||||||
|
|
||||||
// Treat as failed if first buy attempt was more than 60 mins ago (as it's still in the OFFERING state)
|
|
||||||
boolean isFailed = (now - firstMessageTimestamp > 60*60*1000L);
|
|
||||||
if (isFailed) {
|
|
||||||
failedTrades.put(crossChainTradeData.qortalAtAddress, now);
|
|
||||||
updatedCrossChainTrades.remove(crossChainTradeData);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
validTrades.put(crossChainTradeData.qortalAtAddress, now);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
|
@ -44,89 +44,78 @@ public class Bitcoin extends Bitcoiny {
|
|||||||
@Override
|
@Override
|
||||||
public Collection<ElectrumX.Server> getServers() {
|
public Collection<ElectrumX.Server> getServers() {
|
||||||
return Arrays.asList(
|
return Arrays.asList(
|
||||||
// Servers chosen on NO BASIS WHATSOEVER from various sources!
|
// Servers chosen on NO BASIS WHATSOEVER from various sources!
|
||||||
// Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=btc
|
// Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=btc
|
||||||
//CLOSED new Server("bitcoin.grey.pw", Server.ConnectionType.SSL, 50002),
|
new Server("104.248.139.211", Server.ConnectionType.SSL, 50002),
|
||||||
//CLOSED new Server("btc.litepay.ch", Server.ConnectionType.SSL, 50002),
|
new Server("128.0.190.26", Server.ConnectionType.SSL, 50002),
|
||||||
//CLOSED new Server("electrum.pabu.io", Server.ConnectionType.SSL, 50002),
|
new Server("142.93.6.38", Server.ConnectionType.SSL, 50002),
|
||||||
//CLOSED new Server("electrumx.dev", Server.ConnectionType.SSL, 50002),
|
new Server("157.245.172.236", Server.ConnectionType.SSL, 50002),
|
||||||
//CLOSED new Server("electrumx.hodlwallet.com", Server.ConnectionType.SSL, 50002),
|
new Server("167.172.226.175", Server.ConnectionType.SSL, 50002),
|
||||||
//CLOSED new Server("gd42.org", Server.ConnectionType.SSL, 50002),
|
new Server("167.172.42.31", Server.ConnectionType.SSL, 50002),
|
||||||
//CLOSED new Server("korea.electrum-server.com", Server.ConnectionType.SSL, 50002),
|
new Server("178.62.80.20", Server.ConnectionType.SSL, 50002),
|
||||||
//CLOSED new Server("prospero.bitsrc.net", Server.ConnectionType.SSL, 50002),
|
new Server("185.64.116.15", Server.ConnectionType.SSL, 50002),
|
||||||
//1.15.0 new Server("alviss.coinjoined.com", Server.ConnectionType.SSL, 50002),
|
new Server("188.165.206.215", Server.ConnectionType.SSL, 50002),
|
||||||
//1.15.0 new Server("electrum.acinq.co", Server.ConnectionType.SSL, 50002),
|
new Server("188.165.211.112", Server.ConnectionType.SSL, 50002),
|
||||||
//1.14.0 new Server("electrum.coinext.com.br", Server.ConnectionType.SSL, 50002),
|
new Server("2azzarita.hopto.org", Server.ConnectionType.SSL, 50002),
|
||||||
//F1.7.0 new Server("btc.lastingcoin.net", Server.ConnectionType.SSL, 50002),
|
new Server("2electrumx.hopto.me", Server.ConnectionType.SSL, 56022),
|
||||||
new Server("104.248.139.211", Server.ConnectionType.SSL, 50002),
|
new Server("2ex.digitaleveryware.com", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("128.0.190.26", Server.ConnectionType.SSL, 50002),
|
new Server("65.39.140.37", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("142.93.6.38", Server.ConnectionType.SSL, 50002),
|
new Server("68.183.188.105", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("157.245.172.236", Server.ConnectionType.SSL, 50002),
|
new Server("71.73.14.254", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("167.172.226.175", Server.ConnectionType.SSL, 50002),
|
new Server("94.23.247.135", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("167.172.42.31", Server.ConnectionType.SSL, 50002),
|
new Server("assuredly.not.fyi", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("178.62.80.20", Server.ConnectionType.SSL, 50002),
|
new Server("ax101.blockeng.ch", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("185.64.116.15", Server.ConnectionType.SSL, 50002),
|
new Server("ax102.blockeng.ch", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("188.165.206.215", Server.ConnectionType.SSL, 50002),
|
new Server("b.1209k.com", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("188.165.211.112", Server.ConnectionType.SSL, 50002),
|
new Server("b6.1209k.com", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("2azzarita.hopto.org", Server.ConnectionType.SSL, 50002),
|
new Server("bitcoin.dermichi.com", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("2electrumx.hopto.me", Server.ConnectionType.SSL, 56022),
|
new Server("bitcoin.lu.ke", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("2ex.digitaleveryware.com", Server.ConnectionType.SSL, 50002),
|
new Server("bitcoin.lukechilds.co", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("65.39.140.37", Server.ConnectionType.SSL, 50002),
|
new Server("blkhub.net", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("68.183.188.105", Server.ConnectionType.SSL, 50002),
|
new Server("btc.electroncash.dk", Server.ConnectionType.SSL, 60002),
|
||||||
new Server("71.73.14.254", Server.ConnectionType.SSL, 50002),
|
new Server("btc.ocf.sh", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("94.23.247.135", Server.ConnectionType.SSL, 50002),
|
new Server("btce.iiiiiii.biz", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("assuredly.not.fyi", Server.ConnectionType.SSL, 50002),
|
new Server("caleb.vegas", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("ax101.blockeng.ch", Server.ConnectionType.SSL, 50002),
|
new Server("eai.coincited.net", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("ax102.blockeng.ch", Server.ConnectionType.SSL, 50002),
|
new Server("electrum.bhoovd.com", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("b.1209k.com", Server.ConnectionType.SSL, 50002),
|
new Server("electrum.bitaroo.net", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("b6.1209k.com", Server.ConnectionType.SSL, 50002),
|
new Server("electrum.bitcoinlizard.net", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("bitcoin.dermichi.com", Server.ConnectionType.SSL, 50002),
|
new Server("electrum.blockstream.info", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("bitcoin.lu.ke", Server.ConnectionType.SSL, 50002),
|
new Server("electrum.emzy.de", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("bitcoin.lukechilds.co", Server.ConnectionType.SSL, 50002),
|
new Server("electrum.exan.tech", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("blkhub.net", Server.ConnectionType.SSL, 50002),
|
new Server("electrum.kendigisland.xyz", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("btc.electroncash.dk", Server.ConnectionType.SSL, 60002),
|
new Server("electrum.mmitech.info", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("btc.ocf.sh", Server.ConnectionType.SSL, 50002),
|
new Server("electrum.petrkr.net", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("btce.iiiiiii.biz", Server.ConnectionType.SSL, 50002),
|
new Server("electrum.stippy.com", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("caleb.vegas", Server.ConnectionType.SSL, 50002),
|
new Server("electrum.thomasfischbach.de", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("eai.coincited.net", Server.ConnectionType.SSL, 50002),
|
new Server("electrum0.snel.it", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("electrum.bhoovd.com", Server.ConnectionType.SSL, 50002),
|
new Server("electrum1.cipig.net", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("electrum.bitaroo.net", Server.ConnectionType.SSL, 50002),
|
new Server("electrum2.cipig.net", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("electrum.bitcoinlizard.net", Server.ConnectionType.SSL, 50002),
|
new Server("electrum3.cipig.net", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("electrum.blockstream.info", Server.ConnectionType.SSL, 50002),
|
new Server("electrumx.alexridevski.net", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("electrum.emzy.de", Server.ConnectionType.SSL, 50002),
|
new Server("electrumx-core.1209k.com", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("electrum.exan.tech", Server.ConnectionType.SSL, 50002),
|
new Server("elx.bitske.com", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("electrum.kendigisland.xyz", Server.ConnectionType.SSL, 50002),
|
new Server("ex03.axalgo.com", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("electrum.mmitech.info", Server.ConnectionType.SSL, 50002),
|
new Server("ex05.axalgo.com", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("electrum.petrkr.net", Server.ConnectionType.SSL, 50002),
|
new Server("ex07.axalgo.com", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("electrum.stippy.com", Server.ConnectionType.SSL, 50002),
|
new Server("fortress.qtornado.com", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("electrum.thomasfischbach.de", Server.ConnectionType.SSL, 50002),
|
new Server("fulcrum.grey.pw", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("electrum0.snel.it", Server.ConnectionType.SSL, 50002),
|
new Server("fulcrum.sethforprivacy.com", Server.ConnectionType.SSL, 51002),
|
||||||
new Server("electrum1.cipig.net", Server.ConnectionType.SSL, 50002),
|
new Server("guichet.centure.cc", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("electrum2.cipig.net", Server.ConnectionType.SSL, 50002),
|
new Server("hodlers.beer", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("electrum3.cipig.net", Server.ConnectionType.SSL, 50002),
|
new Server("kareoke.qoppa.org", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("electrumx.alexridevski.net", Server.ConnectionType.SSL, 50002),
|
new Server("kirsche.emzy.de", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("electrumx-core.1209k.com", Server.ConnectionType.SSL, 50002),
|
new Server("node1.btccuracao.com", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("elx.bitske.com", Server.ConnectionType.SSL, 50002),
|
new Server("osr1ex1.compumundohipermegared.one", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("ex03.axalgo.com", Server.ConnectionType.SSL, 50002),
|
new Server("smmalis37.ddns.net", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("ex05.axalgo.com", Server.ConnectionType.SSL, 50002),
|
new Server("ulrichard.ch", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("ex07.axalgo.com", Server.ConnectionType.SSL, 50002),
|
new Server("vmd104012.contaboserver.net", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("fortress.qtornado.com", Server.ConnectionType.SSL, 50002),
|
new Server("vmd104014.contaboserver.net", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("fulcrum.grey.pw", Server.ConnectionType.SSL, 50002),
|
new Server("vmd63185.contaboserver.net", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("fulcrum.sethforprivacy.com", Server.ConnectionType.SSL, 51002),
|
new Server("vmd71287.contaboserver.net", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("guichet.centure.cc", Server.ConnectionType.SSL, 50002),
|
new Server("vmd84592.contaboserver.net", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("hodlers.beer", Server.ConnectionType.SSL, 50002),
|
new Server("xtrum.com", Server.ConnectionType.SSL, 50002)
|
||||||
new Server("kareoke.qoppa.org", Server.ConnectionType.SSL, 50002),
|
);
|
||||||
new Server("kirsche.emzy.de", Server.ConnectionType.SSL, 50002),
|
|
||||||
new Server("node1.btccuracao.com", Server.ConnectionType.SSL, 50002),
|
|
||||||
new Server("osr1ex1.compumundohipermegared.one", Server.ConnectionType.SSL, 50002),
|
|
||||||
new Server("smmalis37.ddns.net", Server.ConnectionType.SSL, 50002),
|
|
||||||
new Server("ulrichard.ch", Server.ConnectionType.SSL, 50002),
|
|
||||||
new Server("vmd104012.contaboserver.net", Server.ConnectionType.SSL, 50002),
|
|
||||||
new Server("vmd104014.contaboserver.net", Server.ConnectionType.SSL, 50002),
|
|
||||||
new Server("vmd63185.contaboserver.net", Server.ConnectionType.SSL, 50002),
|
|
||||||
new Server("vmd71287.contaboserver.net", Server.ConnectionType.SSL, 50002),
|
|
||||||
new Server("vmd84592.contaboserver.net", Server.ConnectionType.SSL, 50002),
|
|
||||||
new Server("xtrum.com", Server.ConnectionType.SSL, 50002));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -152,12 +141,13 @@ public class Bitcoin extends Bitcoiny {
|
|||||||
@Override
|
@Override
|
||||||
public Collection<ElectrumX.Server> getServers() {
|
public Collection<ElectrumX.Server> getServers() {
|
||||||
return Arrays.asList(
|
return Arrays.asList(
|
||||||
new Server("tn.not.fyi", Server.ConnectionType.SSL, 55002),
|
new Server("tn.not.fyi", Server.ConnectionType.SSL, 55002),
|
||||||
new Server("electrumx-test.1209k.com", Server.ConnectionType.SSL, 50002),
|
new Server("electrumx-test.1209k.com", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("testnet.qtornado.com", Server.ConnectionType.SSL, 51002),
|
new Server("testnet.qtornado.com", Server.ConnectionType.SSL, 51002),
|
||||||
new Server("testnet.aranguren.org", Server.ConnectionType.TCP, 51001),
|
new Server("testnet.aranguren.org", Server.ConnectionType.TCP, 51001),
|
||||||
new Server("testnet.aranguren.org", Server.ConnectionType.SSL, 51002),
|
new Server("testnet.aranguren.org", Server.ConnectionType.SSL, 51002),
|
||||||
new Server("testnet.hsmiths.com", Server.ConnectionType.SSL, 53012));
|
new Server("testnet.hsmiths.com", Server.ConnectionType.SSL, 53012)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -179,8 +169,9 @@ public class Bitcoin extends Bitcoiny {
|
|||||||
@Override
|
@Override
|
||||||
public Collection<ElectrumX.Server> getServers() {
|
public Collection<ElectrumX.Server> getServers() {
|
||||||
return Arrays.asList(
|
return Arrays.asList(
|
||||||
new Server("localhost", Server.ConnectionType.TCP, 50001),
|
new Server("localhost", Server.ConnectionType.TCP, 50001),
|
||||||
new Server("localhost", Server.ConnectionType.SSL, 50002));
|
new Server("localhost", Server.ConnectionType.SSL, 50002)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -43,14 +43,17 @@ public class Digibyte extends Bitcoiny {
|
|||||||
@Override
|
@Override
|
||||||
public Collection<Server> getServers() {
|
public Collection<Server> getServers() {
|
||||||
return Arrays.asList(
|
return Arrays.asList(
|
||||||
// Servers chosen on NO BASIS WHATSOEVER from various sources!
|
// Servers chosen on NO BASIS WHATSOEVER from various sources!
|
||||||
// Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=dgb
|
// Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=dgb
|
||||||
new Server("electrum.qortal.link", Server.ConnectionType.SSL, 55002),
|
new Server("electrum.qortal.link", Server.ConnectionType.SSL, 55002),
|
||||||
new Server("electrum-dgb.qortal.online", ConnectionType.SSL, 50002),
|
new Server("electrum1-dgb.qortal.online", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("electrum1-dgb.qortal.online", ConnectionType.SSL, 50002),
|
new Server("electrum2-dgb.qortal.online", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("electrum1.cipig.net", ConnectionType.SSL, 20059),
|
new Server("electrum3-dgb.qortal.online", Server.ConnectionType.SSL, 40002),
|
||||||
new Server("electrum2.cipig.net", ConnectionType.SSL, 20059),
|
new Server("electrum4-dgb.qortal.online", Server.ConnectionType.SSL, 40002),
|
||||||
new Server("electrum3.cipig.net", ConnectionType.SSL, 20059));
|
new Server("electrum1.cipig.net", Server.ConnectionType.SSL, 20059),
|
||||||
|
new Server("electrum2.cipig.net", Server.ConnectionType.SSL, 20059),
|
||||||
|
new Server("electrum3.cipig.net", Server.ConnectionType.SSL, 20059)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -94,8 +97,9 @@ public class Digibyte extends Bitcoiny {
|
|||||||
@Override
|
@Override
|
||||||
public Collection<Server> getServers() {
|
public Collection<Server> getServers() {
|
||||||
return Arrays.asList(
|
return Arrays.asList(
|
||||||
new Server("localhost", ConnectionType.TCP, 50001),
|
new Server("localhost", Server.ConnectionType.TCP, 50001),
|
||||||
new Server("localhost", ConnectionType.SSL, 50002));
|
new Server("localhost", Server.ConnectionType.SSL, 50002)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -4,7 +4,6 @@ import org.bitcoinj.core.Coin;
|
|||||||
import org.bitcoinj.core.Context;
|
import org.bitcoinj.core.Context;
|
||||||
import org.bitcoinj.core.NetworkParameters;
|
import org.bitcoinj.core.NetworkParameters;
|
||||||
import org.libdohj.params.DogecoinMainNetParams;
|
import org.libdohj.params.DogecoinMainNetParams;
|
||||||
//import org.libdohj.params.DogecoinRegTestParams;
|
|
||||||
import org.libdohj.params.DogecoinTestNet3Params;
|
import org.libdohj.params.DogecoinTestNet3Params;
|
||||||
import org.qortal.crosschain.ElectrumX.Server;
|
import org.qortal.crosschain.ElectrumX.Server;
|
||||||
import org.qortal.crosschain.ElectrumX.Server.ConnectionType;
|
import org.qortal.crosschain.ElectrumX.Server.ConnectionType;
|
||||||
@ -44,14 +43,17 @@ public class Dogecoin extends Bitcoiny {
|
|||||||
@Override
|
@Override
|
||||||
public Collection<Server> getServers() {
|
public Collection<Server> getServers() {
|
||||||
return Arrays.asList(
|
return Arrays.asList(
|
||||||
// Servers chosen on NO BASIS WHATSOEVER from various sources!
|
// Servers chosen on NO BASIS WHATSOEVER from various sources!
|
||||||
// Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=doge
|
// Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=doge
|
||||||
new Server("electrum.qortal.link", Server.ConnectionType.SSL, 54002),
|
new Server("electrum.qortal.link", Server.ConnectionType.SSL, 54002),
|
||||||
new Server("electrum-doge.qortal.online", ConnectionType.SSL, 50002),
|
new Server("electrum1-doge.qortal.online", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("electrum1-doge.qortal.online", ConnectionType.SSL, 50002),
|
new Server("electrum2-doge.qortal.online", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("electrum1.cipig.net", ConnectionType.SSL, 20060),
|
new Server("electrum3-doge.qortal.online", Server.ConnectionType.SSL, 30002),
|
||||||
new Server("electrum2.cipig.net", ConnectionType.SSL, 20060),
|
new Server("electrum4-doge.qortal.online", Server.ConnectionType.SSL, 30002),
|
||||||
new Server("electrum3.cipig.net", ConnectionType.SSL, 20060));
|
new Server("electrum1.cipig.net", Server.ConnectionType.SSL, 20060),
|
||||||
|
new Server("electrum2.cipig.net", Server.ConnectionType.SSL, 20060),
|
||||||
|
new Server("electrum3.cipig.net", Server.ConnectionType.SSL, 20060)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -95,8 +97,9 @@ public class Dogecoin extends Bitcoiny {
|
|||||||
@Override
|
@Override
|
||||||
public Collection<Server> getServers() {
|
public Collection<Server> getServers() {
|
||||||
return Arrays.asList(
|
return Arrays.asList(
|
||||||
new Server("localhost", ConnectionType.TCP, 50001),
|
new Server("localhost", Server.ConnectionType.TCP, 50001),
|
||||||
new Server("localhost", ConnectionType.SSL, 50002));
|
new Server("localhost", Server.ConnectionType.SSL, 50002)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -43,22 +43,21 @@ public class Litecoin extends Bitcoiny {
|
|||||||
@Override
|
@Override
|
||||||
public Collection<ElectrumX.Server> getServers() {
|
public Collection<ElectrumX.Server> getServers() {
|
||||||
return Arrays.asList(
|
return Arrays.asList(
|
||||||
// Servers chosen on NO BASIS WHATSOEVER from various sources!
|
// Servers chosen on NO BASIS WHATSOEVER from various sources!
|
||||||
// Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=ltc
|
// Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=ltc
|
||||||
//CLOSED new Server("electrum-ltc.someguy123.net", Server.ConnectionType.SSL, 50002),
|
new Server("electrum.qortal.link", Server.ConnectionType.SSL, 50002),
|
||||||
//CLOSED new Server("ltc.litepay.ch", Server.ConnectionType.SSL, 50022),
|
new Server("electrum1-ltc.qortal.online", Server.ConnectionType.SSL, 50002),
|
||||||
//BEHIND new Server("62.171.169.176", Server.ConnectionType.SSL, 50002),
|
new Server("electrum2-ltc.qortal.online", Server.ConnectionType.SSL, 50002),
|
||||||
//PHISHY new Server("electrum-ltc.bysh.me", Server.ConnectionType.SSL, 50002),
|
new Server("electrum3-ltc.qortal.online", Server.ConnectionType.SSL, 20002),
|
||||||
new Server("backup.electrum-ltc.org", Server.ConnectionType.SSL, 443),
|
new Server("electrum4-ltc.qortal.online", Server.ConnectionType.SSL, 20002),
|
||||||
new Server("electrum.qortal.link", Server.ConnectionType.SSL, 50002),
|
new Server("backup.electrum-ltc.org", Server.ConnectionType.SSL, 443),
|
||||||
new Server("electrum.ltc.xurious.com", Server.ConnectionType.SSL, 50002),
|
new Server("electrum.ltc.xurious.com", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("electrum-ltc.petrkr.net", Server.ConnectionType.SSL, 60002),
|
new Server("electrum-ltc.petrkr.net", Server.ConnectionType.SSL, 60002),
|
||||||
new Server("electrum-ltc.qortal.online", Server.ConnectionType.SSL, 50002),
|
new Server("electrum1.cipig.net", Server.ConnectionType.SSL, 20063),
|
||||||
new Server("electrum1-ltc.qortal.online", Server.ConnectionType.SSL, 50002),
|
new Server("electrum2.cipig.net", Server.ConnectionType.SSL, 20063),
|
||||||
new Server("electrum1.cipig.net", Server.ConnectionType.SSL, 20063),
|
new Server("electrum3.cipig.net", Server.ConnectionType.SSL, 20063),
|
||||||
new Server("electrum2.cipig.net", Server.ConnectionType.SSL, 20063),
|
new Server("ltc.rentonrisk.com", Server.ConnectionType.SSL, 50002)
|
||||||
new Server("electrum3.cipig.net", Server.ConnectionType.SSL, 20063),
|
);
|
||||||
new Server("ltc.rentonrisk.com", Server.ConnectionType.SSL, 50002));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -81,10 +80,11 @@ public class Litecoin extends Bitcoiny {
|
|||||||
@Override
|
@Override
|
||||||
public Collection<ElectrumX.Server> getServers() {
|
public Collection<ElectrumX.Server> getServers() {
|
||||||
return Arrays.asList(
|
return Arrays.asList(
|
||||||
new Server("electrum-ltc.bysh.me", Server.ConnectionType.TCP, 51001),
|
new Server("electrum-ltc.bysh.me", Server.ConnectionType.TCP, 51001),
|
||||||
new Server("electrum-ltc.bysh.me", Server.ConnectionType.SSL, 51002),
|
new Server("electrum-ltc.bysh.me", Server.ConnectionType.SSL, 51002),
|
||||||
new Server("electrum.ltc.xurious.com", Server.ConnectionType.TCP, 51001),
|
new Server("electrum.ltc.xurious.com", Server.ConnectionType.TCP, 51001),
|
||||||
new Server("electrum.ltc.xurious.com", Server.ConnectionType.SSL, 51002));
|
new Server("electrum.ltc.xurious.com", Server.ConnectionType.SSL, 51002)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -106,8 +106,9 @@ public class Litecoin extends Bitcoiny {
|
|||||||
@Override
|
@Override
|
||||||
public Collection<ElectrumX.Server> getServers() {
|
public Collection<ElectrumX.Server> getServers() {
|
||||||
return Arrays.asList(
|
return Arrays.asList(
|
||||||
new Server("localhost", Server.ConnectionType.TCP, 50001),
|
new Server("localhost", Server.ConnectionType.TCP, 50001),
|
||||||
new Server("localhost", Server.ConnectionType.SSL, 50002));
|
new Server("localhost", Server.ConnectionType.SSL, 50002)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -56,11 +56,14 @@ public class PirateChain extends Bitcoiny {
|
|||||||
@Override
|
@Override
|
||||||
public Collection<Server> getServers() {
|
public Collection<Server> getServers() {
|
||||||
return Arrays.asList(
|
return Arrays.asList(
|
||||||
// Servers chosen on NO BASIS WHATSOEVER from various sources!
|
// Servers chosen on NO BASIS WHATSOEVER from various sources!
|
||||||
new Server("wallet-arrr1.qortal.online", ConnectionType.SSL, 443),
|
new Server("wallet-arrr1.qortal.online", Server.ConnectionType.SSL, 443),
|
||||||
new Server("wallet-arrr2.qortal.online", ConnectionType.SSL, 443),
|
new Server("wallet-arrr2.qortal.online", Server.ConnectionType.SSL, 443),
|
||||||
new Server("wallet-arrr3.qortal.online", ConnectionType.SSL, 443),
|
new Server("wallet-arrr3.qortal.online", Server.ConnectionType.SSL, 443),
|
||||||
new Server("lightd.pirate.black", ConnectionType.SSL, 443));
|
new Server("wallet-arrr4.qortal.online", Server.ConnectionType.SSL, 443),
|
||||||
|
new Server("wallet-arrr5.qortal.online", Server.ConnectionType.SSL, 443),
|
||||||
|
new Server("lightd.pirate.black", Server.ConnectionType.SSL, 443)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -104,8 +107,9 @@ public class PirateChain extends Bitcoiny {
|
|||||||
@Override
|
@Override
|
||||||
public Collection<Server> getServers() {
|
public Collection<Server> getServers() {
|
||||||
return Arrays.asList(
|
return Arrays.asList(
|
||||||
new Server("localhost", ConnectionType.TCP, 9067),
|
new Server("localhost", Server.ConnectionType.TCP, 9067),
|
||||||
new Server("localhost", ConnectionType.SSL, 443));
|
new Server("localhost", Server.ConnectionType.SSL, 443)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -43,19 +43,19 @@ public class Ravencoin extends Bitcoiny {
|
|||||||
@Override
|
@Override
|
||||||
public Collection<Server> getServers() {
|
public Collection<Server> getServers() {
|
||||||
return Arrays.asList(
|
return Arrays.asList(
|
||||||
// Servers chosen on NO BASIS WHATSOEVER from various sources!
|
// Servers chosen on NO BASIS WHATSOEVER from various sources!
|
||||||
// Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=rvn
|
// Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=rvn
|
||||||
//CLOSED new Server("aethyn.com", ConnectionType.SSL, 50002),
|
new Server("electrum.qortal.link", Server.ConnectionType.SSL, 56002),
|
||||||
//CLOSED new Server("electrum2.rvn.rocks", ConnectionType.SSL, 50002),
|
new Server("electrum1-rvn.qortal.online", Server.ConnectionType.SSL, 50002),
|
||||||
//BEHIND new Server("electrum3.rvn.rocks", ConnectionType.SSL, 50002),
|
new Server("electrum2-rvn.qortal.online", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("electrum.qortal.link", Server.ConnectionType.SSL, 56002),
|
new Server("electrum3-rvn.qortal.online", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("electrum-rvn.qortal.online", ConnectionType.SSL, 50002),
|
new Server("electrum4-rvn.qortal.online", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("electrum1-rvn.qortal.online", ConnectionType.SSL, 50002),
|
new Server("electrum1.cipig.net", Server.ConnectionType.SSL, 20051),
|
||||||
new Server("electrum1.cipig.net", ConnectionType.SSL, 20051),
|
new Server("electrum2.cipig.net", Server.ConnectionType.SSL, 20051),
|
||||||
new Server("electrum2.cipig.net", ConnectionType.SSL, 20051),
|
new Server("electrum3.cipig.net", Server.ConnectionType.SSL, 20051),
|
||||||
new Server("electrum3.cipig.net", ConnectionType.SSL, 20051),
|
new Server("rvn-dashboard.com", Server.ConnectionType.SSL, 50002),
|
||||||
new Server("rvn-dashboard.com", ConnectionType.SSL, 50002),
|
new Server("rvn4lyfe.com", Server.ConnectionType.SSL, 50002)
|
||||||
new Server("rvn4lyfe.com", ConnectionType.SSL, 50002));
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -99,8 +99,9 @@ public class Ravencoin extends Bitcoiny {
|
|||||||
@Override
|
@Override
|
||||||
public Collection<Server> getServers() {
|
public Collection<Server> getServers() {
|
||||||
return Arrays.asList(
|
return Arrays.asList(
|
||||||
new Server("localhost", ConnectionType.TCP, 50001),
|
new Server("localhost", Server.ConnectionType.TCP, 50001),
|
||||||
new Server("localhost", ConnectionType.SSL, 50002));
|
new Server("localhost", Server.ConnectionType.SSL, 50002)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -75,6 +75,9 @@ public abstract class TransactionData {
|
|||||||
@Schema(description = "groupID for this transaction")
|
@Schema(description = "groupID for this transaction")
|
||||||
protected int txGroupId;
|
protected int txGroupId;
|
||||||
|
|
||||||
|
@Schema(description = "recipient for this transaction")
|
||||||
|
protected String recipient;
|
||||||
|
|
||||||
// Not always present
|
// Not always present
|
||||||
@Schema(accessMode = AccessMode.READ_ONLY, hidden = true, description = "height of block containing transaction")
|
@Schema(accessMode = AccessMode.READ_ONLY, hidden = true, description = "height of block containing transaction")
|
||||||
protected Integer blockHeight;
|
protected Integer blockHeight;
|
||||||
@ -105,7 +108,7 @@ public abstract class TransactionData {
|
|||||||
/** Constructor for use by transaction subclasses. */
|
/** Constructor for use by transaction subclasses. */
|
||||||
protected TransactionData(TransactionType type, BaseTransactionData baseTransactionData) {
|
protected TransactionData(TransactionType type, BaseTransactionData baseTransactionData) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
|
this.recipient = baseTransactionData.recipient;
|
||||||
this.timestamp = baseTransactionData.timestamp;
|
this.timestamp = baseTransactionData.timestamp;
|
||||||
this.txGroupId = baseTransactionData.txGroupId;
|
this.txGroupId = baseTransactionData.txGroupId;
|
||||||
this.reference = baseTransactionData.reference;
|
this.reference = baseTransactionData.reference;
|
||||||
@ -136,6 +139,10 @@ public abstract class TransactionData {
|
|||||||
return this.txGroupId;
|
return this.txGroupId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getRecipient() {
|
||||||
|
return this.recipient;
|
||||||
|
}
|
||||||
|
|
||||||
public void setTxGroupId(int txGroupId) {
|
public void setTxGroupId(int txGroupId) {
|
||||||
this.txGroupId = txGroupId;
|
this.txGroupId = txGroupId;
|
||||||
}
|
}
|
||||||
@ -250,5 +257,4 @@ public abstract class TransactionData {
|
|||||||
|
|
||||||
return Arrays.equals(this.signature, otherTransactionData.signature);
|
return Arrays.equals(this.signature, otherTransactionData.signature);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
@ -8,7 +8,6 @@ import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters;
|
|||||||
import org.qortal.block.BlockChain;
|
import org.qortal.block.BlockChain;
|
||||||
import org.qortal.controller.Controller;
|
import org.qortal.controller.Controller;
|
||||||
import org.qortal.controller.arbitrary.ArbitraryDataFileListManager;
|
import org.qortal.controller.arbitrary.ArbitraryDataFileListManager;
|
||||||
import org.qortal.controller.arbitrary.ArbitraryDataManager;
|
|
||||||
import org.qortal.crypto.Crypto;
|
import org.qortal.crypto.Crypto;
|
||||||
import org.qortal.data.block.BlockData;
|
import org.qortal.data.block.BlockData;
|
||||||
import org.qortal.data.block.BlockSummaryData;
|
import org.qortal.data.block.BlockSummaryData;
|
||||||
@ -122,6 +121,22 @@ public class Network {
|
|||||||
private List<Peer> immutableOutboundHandshakedPeers = Collections.emptyList(); // always rebuilt from mutable, synced list above
|
private List<Peer> immutableOutboundHandshakedPeers = Collections.emptyList(); // always rebuilt from mutable, synced list above
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count threads per message type in order to enforce limits
|
||||||
|
*/
|
||||||
|
private final Map<MessageType, Integer> threadsPerMessageType = Collections.synchronizedMap(new HashMap<>());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep track of total thread count, to warn when the thread pool is getting low
|
||||||
|
*/
|
||||||
|
private int totalThreadCount = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thresholds at which to warn about the number of active threads
|
||||||
|
*/
|
||||||
|
private final int threadCountWarningThreshold = (int) (Settings.getInstance().getMaxNetworkThreadPoolSize() * 0.9f);
|
||||||
|
private final Integer threadCountPerMessageTypeWarningThreshold = Settings.getInstance().getThreadCountPerMessageTypeWarningThreshold();
|
||||||
|
|
||||||
private final List<PeerAddress> selfPeers = new ArrayList<>();
|
private final List<PeerAddress> selfPeers = new ArrayList<>();
|
||||||
|
|
||||||
private String bindAddress = null;
|
private String bindAddress = null;
|
||||||
@ -240,6 +255,16 @@ public class Network {
|
|||||||
private static final Network INSTANCE = new Network();
|
private static final Network INSTANCE = new Network();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<MessageType, Integer> getThreadsPerMessageType() {
|
||||||
|
return this.threadsPerMessageType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTotalThreadCount() {
|
||||||
|
synchronized (this) {
|
||||||
|
return this.totalThreadCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static Network getInstance() {
|
public static Network getInstance() {
|
||||||
return SingletonContainer.INSTANCE;
|
return SingletonContainer.INSTANCE;
|
||||||
}
|
}
|
||||||
@ -952,6 +977,37 @@ public class Network {
|
|||||||
|
|
||||||
// Should be non-handshaking messages from now on
|
// Should be non-handshaking messages from now on
|
||||||
|
|
||||||
|
// Limit threads per message type and discard if there are already too many
|
||||||
|
Integer maxThreadsForMessageType = Settings.getInstance().getMaxThreadsForMessageType(message.getType());
|
||||||
|
if (maxThreadsForMessageType != null) {
|
||||||
|
Integer threadCount = threadsPerMessageType.get(message.getType());
|
||||||
|
if (threadCount != null && threadCount >= maxThreadsForMessageType) {
|
||||||
|
LOGGER.trace("Discarding {} message as there are already {} active threads", message.getType().name(), threadCount);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn if necessary
|
||||||
|
if (threadCountPerMessageTypeWarningThreshold != null) {
|
||||||
|
Integer threadCount = threadsPerMessageType.get(message.getType());
|
||||||
|
if (threadCount != null && threadCount > threadCountPerMessageTypeWarningThreshold) {
|
||||||
|
LOGGER.info("Warning: high thread count for {} message type: {}", message.getType().name(), threadCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to per-message thread count (first initializing to 0 if not already present)
|
||||||
|
threadsPerMessageType.computeIfAbsent(message.getType(), key -> 0);
|
||||||
|
threadsPerMessageType.computeIfPresent(message.getType(), (key, value) -> value + 1);
|
||||||
|
|
||||||
|
// Add to total thread count
|
||||||
|
synchronized (this) {
|
||||||
|
totalThreadCount++;
|
||||||
|
|
||||||
|
if (totalThreadCount >= threadCountWarningThreshold) {
|
||||||
|
LOGGER.info("Warning: high total thread count: {} / {}", totalThreadCount, Settings.getInstance().getMaxNetworkThreadPoolSize());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Ordered by message type value
|
// Ordered by message type value
|
||||||
switch (message.getType()) {
|
switch (message.getType()) {
|
||||||
case GET_PEERS:
|
case GET_PEERS:
|
||||||
@ -979,6 +1035,15 @@ public class Network {
|
|||||||
Controller.getInstance().onNetworkMessage(peer, message);
|
Controller.getInstance().onNetworkMessage(peer, message);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove from per-message thread count (first initializing to 0 if not already present)
|
||||||
|
threadsPerMessageType.computeIfAbsent(message.getType(), key -> 0);
|
||||||
|
threadsPerMessageType.computeIfPresent(message.getType(), (key, value) -> value - 1);
|
||||||
|
|
||||||
|
// Remove from total thread count
|
||||||
|
synchronized (this) {
|
||||||
|
totalThreadCount--;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onHandshakingMessage(Peer peer, Message message, Handshake handshakeStatus) {
|
private void onHandshakingMessage(Peer peer, Message message, Handshake handshakeStatus) {
|
||||||
|
@ -1057,5 +1057,4 @@ public class HSQLDBDatabaseUpdates {
|
|||||||
LOGGER.info(() -> String.format("HSQLDB repository updated to version %d", databaseVersion + 1));
|
LOGGER.info(() -> String.format("HSQLDB repository updated to version %d", databaseVersion + 1));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
@ -29,6 +29,7 @@ import org.qortal.crosschain.Dogecoin.DogecoinNet;
|
|||||||
import org.qortal.crosschain.Digibyte.DigibyteNet;
|
import org.qortal.crosschain.Digibyte.DigibyteNet;
|
||||||
import org.qortal.crosschain.Ravencoin.RavencoinNet;
|
import org.qortal.crosschain.Ravencoin.RavencoinNet;
|
||||||
import org.qortal.crosschain.PirateChain.PirateChainNet;
|
import org.qortal.crosschain.PirateChain.PirateChainNet;
|
||||||
|
import org.qortal.network.message.MessageType;
|
||||||
import org.qortal.utils.EnumUtils;
|
import org.qortal.utils.EnumUtils;
|
||||||
|
|
||||||
// All properties to be converted to JSON via JAXB
|
// All properties to be converted to JSON via JAXB
|
||||||
@ -47,8 +48,8 @@ public class Settings {
|
|||||||
private static final int MAINNET_GATEWAY_PORT = 80;
|
private static final int MAINNET_GATEWAY_PORT = 80;
|
||||||
private static final int TESTNET_GATEWAY_PORT = 8080;
|
private static final int TESTNET_GATEWAY_PORT = 8080;
|
||||||
|
|
||||||
private static final int MAINNET_DEV_PROXY_PORT = 12393;
|
private static final int MAINNET_DEV_PROXY_PORT = 12393;
|
||||||
private static final int TESTNET_DEV_PROXY_PORT = 62393;
|
private static final int TESTNET_DEV_PROXY_PORT = 62393;
|
||||||
|
|
||||||
private static final Logger LOGGER = LogManager.getLogger(Settings.class);
|
private static final Logger LOGGER = LogManager.getLogger(Settings.class);
|
||||||
private static final String SETTINGS_FILENAME = "settings.json";
|
private static final String SETTINGS_FILENAME = "settings.json";
|
||||||
@ -110,10 +111,9 @@ public class Settings {
|
|||||||
private boolean gatewayLoggingEnabled = false;
|
private boolean gatewayLoggingEnabled = false;
|
||||||
private boolean gatewayLoopbackEnabled = false;
|
private boolean gatewayLoopbackEnabled = false;
|
||||||
|
|
||||||
// Developer Proxy
|
// Developer Proxy
|
||||||
private Integer devProxyPort;
|
private Integer devProxyPort;
|
||||||
private boolean devProxyLoggingEnabled = false;
|
private boolean devProxyLoggingEnabled = false;
|
||||||
|
|
||||||
|
|
||||||
// Specific to this node
|
// Specific to this node
|
||||||
private boolean wipeUnconfirmedOnStart = false;
|
private boolean wipeUnconfirmedOnStart = false;
|
||||||
@ -147,7 +147,7 @@ public class Settings {
|
|||||||
private int blockCacheSize = 10;
|
private int blockCacheSize = 10;
|
||||||
|
|
||||||
/** Maximum number of transactions for the block minter to include in a block */
|
/** Maximum number of transactions for the block minter to include in a block */
|
||||||
private int maxTransactionsPerBlock = 25;
|
private int maxTransactionsPerBlock = 50;
|
||||||
|
|
||||||
/** How long to keep old, full, AT state data (ms). */
|
/** How long to keep old, full, AT state data (ms). */
|
||||||
private long atStatesMaxLifetime = 5 * 24 * 60 * 60 * 1000L; // milliseconds
|
private long atStatesMaxLifetime = 5 * 24 * 60 * 60 * 1000L; // milliseconds
|
||||||
@ -186,7 +186,6 @@ public class Settings {
|
|||||||
* This has a significant effect on execution time. */
|
* This has a significant effect on execution time. */
|
||||||
private int blockPruneBatchSize = 10000; // blocks
|
private int blockPruneBatchSize = 10000; // blocks
|
||||||
|
|
||||||
|
|
||||||
/** Whether we should archive old data to reduce the database size */
|
/** Whether we should archive old data to reduce the database size */
|
||||||
private boolean archiveEnabled = true;
|
private boolean archiveEnabled = true;
|
||||||
/** How often to attempt archiving (ms). */
|
/** How often to attempt archiving (ms). */
|
||||||
@ -194,15 +193,12 @@ public class Settings {
|
|||||||
/** Serialization version to use when building an archive */
|
/** Serialization version to use when building an archive */
|
||||||
private int defaultArchiveVersion = 2;
|
private int defaultArchiveVersion = 2;
|
||||||
|
|
||||||
|
|
||||||
/** Whether to automatically bootstrap instead of syncing from genesis */
|
/** Whether to automatically bootstrap instead of syncing from genesis */
|
||||||
private boolean bootstrap = true;
|
private boolean bootstrap = true;
|
||||||
|
|
||||||
|
|
||||||
/** Registered names integrity check */
|
/** Registered names integrity check */
|
||||||
private boolean namesIntegrityCheckEnabled = false;
|
private boolean namesIntegrityCheckEnabled = false;
|
||||||
|
|
||||||
|
|
||||||
// Peer-to-peer related
|
// Peer-to-peer related
|
||||||
private boolean isTestNet = false;
|
private boolean isTestNet = false;
|
||||||
/** Single node testnet mode */
|
/** Single node testnet mode */
|
||||||
@ -227,10 +223,10 @@ public class Settings {
|
|||||||
private int maxRetries = 2;
|
private int maxRetries = 2;
|
||||||
|
|
||||||
/** The number of seconds of no activity before recovery mode begins */
|
/** 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 */
|
/** Minimum peer version number required in order to sync with them */
|
||||||
private String minPeerVersion = "4.1.2";
|
private String minPeerVersion = "4.3.1";
|
||||||
/** Whether to allow connections with peers below minPeerVersion
|
/** Whether to allow connections with peers below minPeerVersion
|
||||||
* If true, we won't sync with them but they can still sync with us, and will show in the peers list
|
* If true, we won't sync with them but they can still sync with us, and will show in the peers list
|
||||||
* If false, sync will be blocked both ways, and they will not appear in the peers list */
|
* If false, sync will be blocked both ways, and they will not appear in the peers list */
|
||||||
@ -289,10 +285,10 @@ public class Settings {
|
|||||||
|
|
||||||
// Bootstrap sources
|
// Bootstrap sources
|
||||||
private String[] bootstrapHosts = new String[] {
|
private String[] bootstrapHosts = new String[] {
|
||||||
"http://bootstrap.qortal.org",
|
"http://bootstrap.qortal.org",
|
||||||
"http://bootstrap2.qortal.org",
|
"http://bootstrap2.qortal.org",
|
||||||
"http://bootstrap3.qortal.org",
|
"http://bootstrap3.qortal.org",
|
||||||
"http://bootstrap.qortal.online"
|
"http://bootstrap.qortal.online"
|
||||||
};
|
};
|
||||||
|
|
||||||
// Auto-update sources
|
// Auto-update sources
|
||||||
@ -311,17 +307,35 @@ public class Settings {
|
|||||||
"1.pool.ntp.org",
|
"1.pool.ntp.org",
|
||||||
"2.pool.ntp.org",
|
"2.pool.ntp.org",
|
||||||
"3.pool.ntp.org",
|
"3.pool.ntp.org",
|
||||||
"cn.pool.ntp.org",
|
"asia.pool.ntp.org",
|
||||||
"0.cn.pool.ntp.org",
|
"0.asia.pool.ntp.org",
|
||||||
"1.cn.pool.ntp.org",
|
"1.asia.pool.ntp.org",
|
||||||
"2.cn.pool.ntp.org",
|
"2.asia.pool.ntp.org",
|
||||||
"3.cn.pool.ntp.org"
|
"3.asia.pool.ntp.org",
|
||||||
|
"europe.pool.ntp.org",
|
||||||
|
"0.europe.pool.ntp.org",
|
||||||
|
"1.europe.pool.ntp.org",
|
||||||
|
"2.europe.pool.ntp.org",
|
||||||
|
"3.europe.pool.ntp.org",
|
||||||
|
"north-america.pool.ntp.org",
|
||||||
|
"0.north-america.pool.ntp.org",
|
||||||
|
"1.north-america.pool.ntp.org",
|
||||||
|
"2.north-america.pool.ntp.org",
|
||||||
|
"3.north-america.pool.ntp.org",
|
||||||
|
"oceania.pool.ntp.org",
|
||||||
|
"0.oceania.pool.ntp.org",
|
||||||
|
"1.oceania.pool.ntp.org",
|
||||||
|
"2.oceania.pool.ntp.org",
|
||||||
|
"3.oceania.pool.ntp.org",
|
||||||
|
"south-america.pool.ntp.org",
|
||||||
|
"0.south-america.pool.ntp.org",
|
||||||
|
"1.south-america.pool.ntp.org",
|
||||||
|
"2.south-america.pool.ntp.org",
|
||||||
|
"3.south-america.pool.ntp.org"
|
||||||
};
|
};
|
||||||
/** Additional offset added to values returned by NTP.getTime() */
|
/** Additional offset added to values returned by NTP.getTime() */
|
||||||
private Long testNtpOffset = null;
|
private Long testNtpOffset = null;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Foreign chains */
|
/* Foreign chains */
|
||||||
|
|
||||||
/** The number of consecutive empty addresses required before treating a wallet's transaction set as complete */
|
/** The number of consecutive empty addresses required before treating a wallet's transaction set as complete */
|
||||||
@ -330,8 +344,6 @@ public class Settings {
|
|||||||
/** How many wallet keys to generate when using bitcoinj as the blockchain interface (e.g. when sending coins) */
|
/** How many wallet keys to generate when using bitcoinj as the blockchain interface (e.g. when sending coins) */
|
||||||
private int bitcoinjLookaheadSize = 50;
|
private int bitcoinjLookaheadSize = 50;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Data storage (QDN)
|
// Data storage (QDN)
|
||||||
|
|
||||||
/** Data storage enabled/disabled*/
|
/** Data storage enabled/disabled*/
|
||||||
@ -371,6 +383,58 @@ public class Settings {
|
|||||||
/** Whether to serve QDN data without authentication */
|
/** Whether to serve QDN data without authentication */
|
||||||
private boolean qdnAuthBypassEnabled = true;
|
private boolean qdnAuthBypassEnabled = true;
|
||||||
|
|
||||||
|
/** Limit threads per message type */
|
||||||
|
private Set<ThreadLimit> maxThreadsPerMessageType = new HashSet<>();
|
||||||
|
|
||||||
|
/** The number of threads per message type at which a warning should be logged.
|
||||||
|
* Exclude from settings.json to disable this warning. */
|
||||||
|
private Integer threadCountPerMessageTypeWarningThreshold = null;
|
||||||
|
|
||||||
|
|
||||||
|
// Domain mapping
|
||||||
|
public static class ThreadLimit {
|
||||||
|
private String messageType;
|
||||||
|
private Integer limit;
|
||||||
|
|
||||||
|
private ThreadLimit() { // makes JAXB happy; will never be invoked
|
||||||
|
}
|
||||||
|
|
||||||
|
private ThreadLimit(String messageType, Integer limit) {
|
||||||
|
this.messageType = messageType;
|
||||||
|
this.limit = limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessageType() {
|
||||||
|
return messageType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessageType(String messageType) {
|
||||||
|
this.messageType = messageType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getLimit() {
|
||||||
|
return limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLimit(Integer limit) {
|
||||||
|
this.limit = limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
if (!(other instanceof ThreadLimit))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return this.messageType.equals(((ThreadLimit) other).getMessageType());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(messageType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Domain mapping
|
// Domain mapping
|
||||||
public static class DomainMap {
|
public static class DomainMap {
|
||||||
private String domain;
|
private String domain;
|
||||||
@ -396,7 +460,6 @@ public class Settings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
private Settings() {
|
private Settings() {
|
||||||
@ -497,6 +560,9 @@ public class Settings {
|
|||||||
}
|
}
|
||||||
} while (settings.userPath != null);
|
} while (settings.userPath != null);
|
||||||
|
|
||||||
|
// Set some additional defaults if needed
|
||||||
|
settings.setAdditionalDefaults();
|
||||||
|
|
||||||
// Validate settings
|
// Validate settings
|
||||||
settings.validate();
|
settings.validate();
|
||||||
|
|
||||||
@ -533,6 +599,22 @@ public class Settings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setAdditionalDefaults() {
|
||||||
|
// Populate defaults for maxThreadsPerMessageType. If any are specified in settings.json, they will take priority.
|
||||||
|
maxThreadsPerMessageType.add(new ThreadLimit("ARBITRARY_DATA_FILE", 5));
|
||||||
|
maxThreadsPerMessageType.add(new ThreadLimit("GET_ARBITRARY_DATA_FILE", 5));
|
||||||
|
maxThreadsPerMessageType.add(new ThreadLimit("ARBITRARY_DATA", 5));
|
||||||
|
maxThreadsPerMessageType.add(new ThreadLimit("GET_ARBITRARY_DATA", 5));
|
||||||
|
maxThreadsPerMessageType.add(new ThreadLimit("ARBITRARY_DATA_FILE_LIST", 5));
|
||||||
|
maxThreadsPerMessageType.add(new ThreadLimit("GET_ARBITRARY_DATA_FILE_LIST", 5));
|
||||||
|
maxThreadsPerMessageType.add(new ThreadLimit("ARBITRARY_SIGNATURES", 5));
|
||||||
|
maxThreadsPerMessageType.add(new ThreadLimit("ARBITRARY_METADATA", 5));
|
||||||
|
maxThreadsPerMessageType.add(new ThreadLimit("GET_ARBITRARY_METADATA", 5));
|
||||||
|
maxThreadsPerMessageType.add(new ThreadLimit("GET_TRANSACTION", 10));
|
||||||
|
maxThreadsPerMessageType.add(new ThreadLimit("TRANSACTION_SIGNATURES", 5));
|
||||||
|
maxThreadsPerMessageType.add(new ThreadLimit("TRADE_PRESENCES", 5));
|
||||||
|
}
|
||||||
|
|
||||||
// Getters / setters
|
// Getters / setters
|
||||||
|
|
||||||
public String getUserPath() {
|
public String getUserPath() {
|
||||||
@ -660,17 +742,16 @@ public class Settings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public int getDevProxyPort() {
|
public int getDevProxyPort() {
|
||||||
if (this.devProxyPort != null)
|
if (this.devProxyPort != null)
|
||||||
return this.devProxyPort;
|
return this.devProxyPort;
|
||||||
|
|
||||||
return this.isTestNet ? TESTNET_DEV_PROXY_PORT : MAINNET_DEV_PROXY_PORT;
|
return this.isTestNet ? TESTNET_DEV_PROXY_PORT : MAINNET_DEV_PROXY_PORT;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isDevProxyLoggingEnabled() {
|
|
||||||
return this.devProxyLoggingEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
public boolean isDevProxyLoggingEnabled() {
|
||||||
|
return this.devProxyLoggingEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean getWipeUnconfirmedOnStart() {
|
public boolean getWipeUnconfirmedOnStart() {
|
||||||
return this.wipeUnconfirmedOnStart;
|
return this.wipeUnconfirmedOnStart;
|
||||||
@ -1054,4 +1135,20 @@ public class Settings {
|
|||||||
}
|
}
|
||||||
return this.qdnAuthBypassEnabled;
|
return this.qdnAuthBypassEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Integer getMaxThreadsForMessageType(MessageType messageType) {
|
||||||
|
if (maxThreadsPerMessageType != null) {
|
||||||
|
for (ThreadLimit threadLimit : maxThreadsPerMessageType) {
|
||||||
|
if (threadLimit.getMessageType().equals(messageType.name())) {
|
||||||
|
return threadLimit.getLimit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// No entry, so assume unlimited
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getThreadCountPerMessageTypeWarningThreshold() {
|
||||||
|
return this.threadCountPerMessageTypeWarningThreshold;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,8 +99,14 @@ public class ArbitraryTransaction extends Transaction {
|
|||||||
if (this.transactionData.getFee() < 0)
|
if (this.transactionData.getFee() < 0)
|
||||||
return ValidationResult.NEGATIVE_FEE;
|
return ValidationResult.NEGATIVE_FEE;
|
||||||
|
|
||||||
// After the feature trigger, we require the fee to be sufficient if it's not 0.
|
// As of the mempow transaction updates timestamp, a nonce is no longer supported, so a valid fee must be included
|
||||||
// If the fee is zero, then the nonce is validated in isSignatureValid() as an alternative to a fee
|
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) {
|
if (this.arbitraryTransactionData.getTimestamp() >= BlockChain.getInstance().getArbitraryOptionalFeeTimestamp() && this.arbitraryTransactionData.getFee() != 0L) {
|
||||||
return super.isFeeValid();
|
return super.isFeeValid();
|
||||||
}
|
}
|
||||||
@ -225,7 +231,13 @@ public class ArbitraryTransaction extends Transaction {
|
|||||||
// Clear nonce from transactionBytes
|
// Clear nonce from transactionBytes
|
||||||
ArbitraryTransactionTransformer.clearNonce(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();
|
boolean beforeFeatureTrigger = this.arbitraryTransactionData.getTimestamp() < BlockChain.getInstance().getArbitraryOptionalFeeTimestamp();
|
||||||
if (beforeFeatureTrigger || this.arbitraryTransactionData.getFee() == 0L) {
|
if (beforeFeatureTrigger || this.arbitraryTransactionData.getFee() == 0L) {
|
||||||
// We only need to check nonce for recent transactions due to PoW verification overhead
|
// 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
|
// Nothing to do
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConfirmable() {
|
||||||
|
// CHAT transactions can't go into blocks
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ValidationResult isValid() throws DataException {
|
public ValidationResult isValid() throws DataException {
|
||||||
// Nonce checking is done via isSignatureValid() as that method is only called once per import
|
// 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 MAX_DATA_SIZE = 4000;
|
||||||
public static final int POW_BUFFER_SIZE = 8 * 1024 * 1024; // bytes
|
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
|
// Properties
|
||||||
|
|
||||||
@ -109,7 +111,17 @@ public class MessageTransaction extends Transaction {
|
|||||||
MessageTransactionTransformer.clearNonce(transactionBytes);
|
MessageTransactionTransformer.clearNonce(transactionBytes);
|
||||||
|
|
||||||
// Calculate nonce
|
// 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();
|
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
|
@Override
|
||||||
public ValidationResult isValid() throws DataException {
|
public ValidationResult isValid() throws DataException {
|
||||||
// Nonce checking is done via isSignatureValid() as that method is only called once per import
|
// 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);
|
MessageTransactionTransformer.clearNonce(transactionBytes);
|
||||||
|
|
||||||
// Check nonce
|
// Check nonce
|
||||||
return MemoryPoW.verify2(transactionBytes, POW_BUFFER_SIZE, POW_DIFFICULTY, nonce);
|
return MemoryPoW.verify2(transactionBytes, POW_BUFFER_SIZE, getPoWDifficulty(), nonce);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -256,6 +280,11 @@ public class MessageTransaction extends Transaction {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void process() throws DataException {
|
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 we have no amount then there's nothing to do
|
||||||
if (this.messageTransactionData.getAmount() == 0L)
|
if (this.messageTransactionData.getAmount() == 0L)
|
||||||
return;
|
return;
|
||||||
@ -280,6 +309,11 @@ public class MessageTransaction extends Transaction {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void orphan() throws DataException {
|
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 we have no amount then there's nothing to do
|
||||||
if (this.messageTransactionData.getAmount() == 0L)
|
if (this.messageTransactionData.getAmount() == 0L)
|
||||||
return;
|
return;
|
||||||
|
@ -155,6 +155,12 @@ public class PresenceTransaction extends Transaction {
|
|||||||
// Nothing to do
|
// Nothing to do
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConfirmable() {
|
||||||
|
// PRESENCE transactions can't go into blocks
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ValidationResult isValid() throws DataException {
|
public ValidationResult isValid() throws DataException {
|
||||||
// Nonce checking is done via isSignatureValid() as that method is only called once per import
|
// 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.account.PublicKeyAccount;
|
||||||
import org.qortal.api.resource.TransactionsResource.ConfirmationStatus;
|
import org.qortal.api.resource.TransactionsResource.ConfirmationStatus;
|
||||||
import org.qortal.asset.Asset;
|
import org.qortal.asset.Asset;
|
||||||
|
import org.qortal.block.BlockChain;
|
||||||
import org.qortal.crypto.MemoryPoW;
|
import org.qortal.crypto.MemoryPoW;
|
||||||
import org.qortal.data.transaction.PublicizeTransactionData;
|
import org.qortal.data.transaction.PublicizeTransactionData;
|
||||||
import org.qortal.data.transaction.TransactionData;
|
import org.qortal.data.transaction.TransactionData;
|
||||||
@ -89,6 +90,12 @@ public class PublicizeTransaction extends Transaction {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ValidationResult isValid() throws DataException {
|
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
|
// There can be only one
|
||||||
List<byte[]> signatures = this.repository.getTransactionRepository().getSignaturesMatchingCriteria(
|
List<byte[]> signatures = this.repository.getTransactionRepository().getSignaturesMatchingCriteria(
|
||||||
TransactionType.PUBLICIZE,
|
TransactionType.PUBLICIZE,
|
||||||
|
@ -13,6 +13,7 @@ import org.qortal.account.PublicKeyAccount;
|
|||||||
import org.qortal.asset.Asset;
|
import org.qortal.asset.Asset;
|
||||||
import org.qortal.block.BlockChain;
|
import org.qortal.block.BlockChain;
|
||||||
import org.qortal.controller.Controller;
|
import org.qortal.controller.Controller;
|
||||||
|
import org.qortal.controller.TransactionImporter;
|
||||||
import org.qortal.crypto.Crypto;
|
import org.qortal.crypto.Crypto;
|
||||||
import org.qortal.data.block.BlockData;
|
import org.qortal.data.block.BlockData;
|
||||||
import org.qortal.data.group.GroupApprovalData;
|
import org.qortal.data.group.GroupApprovalData;
|
||||||
@ -247,7 +248,8 @@ public abstract class Transaction {
|
|||||||
GROUP_APPROVAL_REQUIRED(98),
|
GROUP_APPROVAL_REQUIRED(98),
|
||||||
ACCOUNT_NOT_TRANSFERABLE(99),
|
ACCOUNT_NOT_TRANSFERABLE(99),
|
||||||
INVALID_BUT_OK(999),
|
INVALID_BUT_OK(999),
|
||||||
NOT_YET_RELEASED(1000);
|
NOT_YET_RELEASED(1000),
|
||||||
|
NOT_SUPPORTED(1001);
|
||||||
|
|
||||||
public final int value;
|
public final int value;
|
||||||
|
|
||||||
@ -377,7 +379,7 @@ public abstract class Transaction {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public long getUnitFee(Long timestamp) {
|
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 {
|
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
|
// We exclude CHAT transactions as they never get included into blocks and
|
||||||
// have spam/DoS prevention by requiring proof of work
|
// 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
|
* @return sorted, unconfirmed transactions
|
||||||
* @throws DataException
|
* @throws DataException
|
||||||
@ -650,7 +655,8 @@ public abstract class Transaction {
|
|||||||
TransactionData transactionData = unconfirmedTransactionsIterator.next();
|
TransactionData transactionData = unconfirmedTransactionsIterator.next();
|
||||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
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();
|
unconfirmedTransactionsIterator.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -888,6 +894,17 @@ public abstract class Transaction {
|
|||||||
/* To be optionally overridden */
|
/* 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.
|
* Returns whether transaction can be added to the blockchain.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -3,8 +3,12 @@
|
|||||||
"transactionExpiryPeriod": 86400000,
|
"transactionExpiryPeriod": 86400000,
|
||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "0.001",
|
"unitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.001" },
|
||||||
|
{ "timestamp": 1692118800000, "fee": "0.01" }
|
||||||
|
],
|
||||||
"nameRegistrationUnitFees": [
|
"nameRegistrationUnitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.001" },
|
||||||
{ "timestamp": 1645372800000, "fee": "5" },
|
{ "timestamp": 1645372800000, "fee": "5" },
|
||||||
{ "timestamp": 1651420800000, "fee": "1.25" }
|
{ "timestamp": 1651420800000, "fee": "1.25" }
|
||||||
],
|
],
|
||||||
@ -18,13 +22,15 @@
|
|||||||
"maxRewardSharesPerFounderMintingAccount": 6,
|
"maxRewardSharesPerFounderMintingAccount": 6,
|
||||||
"maxRewardSharesByTimestamp": [
|
"maxRewardSharesByTimestamp": [
|
||||||
{ "timestamp": 0, "maxShares": 6 },
|
{ "timestamp": 0, "maxShares": 6 },
|
||||||
{ "timestamp": 1657382400000, "maxShares": 3 }
|
{ "timestamp": 1657382400000, "maxShares": 3 },
|
||||||
|
{ "timestamp": 1698508800000, "maxShares": 2 }
|
||||||
],
|
],
|
||||||
"founderEffectiveMintingLevel": 10,
|
"founderEffectiveMintingLevel": 10,
|
||||||
"onlineAccountSignaturesMinLifetime": 43200000,
|
"onlineAccountSignaturesMinLifetime": 43200000,
|
||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
"onlineAccountsModulusV2Timestamp": 1659801600000,
|
"onlineAccountsModulusV2Timestamp": 1659801600000,
|
||||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 1670230000000,
|
"selfSponsorshipAlgoV1SnapshotTimestamp": 1670230000000,
|
||||||
|
"mempowTransactionUpdatesTimestamp": 1693558800000,
|
||||||
"rewardsByHeight": [
|
"rewardsByHeight": [
|
||||||
{ "height": 1, "reward": 5.00 },
|
{ "height": 1, "reward": 5.00 },
|
||||||
{ "height": 259201, "reward": 4.75 },
|
{ "height": 259201, "reward": 4.75 },
|
||||||
|
@ -4,52 +4,52 @@
|
|||||||
# "localeLang": "de",
|
# "localeLang": "de",
|
||||||
|
|
||||||
### Common ###
|
### Common ###
|
||||||
JSON = JSON Nachricht konnte nicht geparst werden
|
JSON = JSON-Nachricht konnte nicht geparst werden
|
||||||
|
|
||||||
INSUFFICIENT_BALANCE = Kein Ausgleich
|
INSUFFICIENT_BALANCE = Guthaben reicht nicht aus
|
||||||
|
|
||||||
UNAUTHORIZED = API-Aufruf nicht autorisiert
|
UNAUTHORIZED = API-Aufruf nicht autorisiert
|
||||||
|
|
||||||
REPOSITORY_ISSUE = Repository-Fehler
|
REPOSITORY_ISSUE = Repository-Fehler
|
||||||
|
|
||||||
NON_PRODUCTION = Dieser APi-Aufruf ist nicht gestattet für Produtkion
|
NON_PRODUCTION = dieser API-Aufruf ist für Produktionssysteme nicht gestattet
|
||||||
|
|
||||||
BLOCKCHAIN_NEEDS_SYNC = Blockchain muss sich erst verbinden
|
BLOCKCHAIN_NEEDS_SYNC = Blockchain muss sich erst synchronisieren
|
||||||
|
|
||||||
NO_TIME_SYNC = noch keine Uhrensynchronisation
|
NO_TIME_SYNC = Uhrzeit noch nicht synchronisiert
|
||||||
|
|
||||||
### Validation ###
|
### Validation ###
|
||||||
INVALID_SIGNATURE = ungültige Signatur
|
INVALID_SIGNATURE = Signatur ungültig
|
||||||
|
|
||||||
INVALID_ADDRESS = ungültige Adresse
|
INVALID_ADDRESS = Adresse ungültig
|
||||||
|
|
||||||
INVALID_PUBLIC_KEY = ungültiger public key
|
INVALID_PUBLIC_KEY = öffentlicher Schlüssel ungültig
|
||||||
|
|
||||||
INVALID_DATA = ungültige Daten
|
INVALID_DATA = Daten ungültig
|
||||||
|
|
||||||
INVALID_NETWORK_ADDRESS = ungültige Netzwerk Adresse
|
INVALID_NETWORK_ADDRESS = Netzwerk Adresse ungültig
|
||||||
|
|
||||||
ADDRESS_UNKNOWN = Account Adresse unbekannt
|
ADDRESS_UNKNOWN = Kontoadresse unbekannt
|
||||||
|
|
||||||
INVALID_CRITERIA = ungültige Suchkriterien
|
INVALID_CRITERIA = Suchkriterien ungültig
|
||||||
|
|
||||||
INVALID_REFERENCE = ungültige Referenz
|
INVALID_REFERENCE = Referenz ungültig
|
||||||
|
|
||||||
TRANSFORMATION_ERROR = konnte JSON nicht in eine Transaktion umwandeln
|
TRANSFORMATION_ERROR = konnte JSON nicht in eine Transaktion umwandeln
|
||||||
|
|
||||||
INVALID_PRIVATE_KEY = ungültiger private key
|
INVALID_PRIVATE_KEY = öffentlicher Schlüssel ungültig
|
||||||
|
|
||||||
INVALID_HEIGHT = ungültige block height
|
INVALID_HEIGHT = Blockhöhe ungültig
|
||||||
|
|
||||||
CANNOT_MINT = Account kann nicht minten
|
CANNOT_MINT = Konto kann nicht prägen
|
||||||
|
|
||||||
### Blocks ###
|
### Blocks ###
|
||||||
BLOCK_UNKNOWN = block unbekannt
|
BLOCK_UNKNOWN = Block unbekannt
|
||||||
|
|
||||||
### Transactions ###
|
### Transactions ###
|
||||||
TRANSACTION_UNKNOWN = Transaktion unbekannt
|
TRANSACTION_UNKNOWN = Transaktion unbekannt
|
||||||
|
|
||||||
PUBLIC_KEY_NOT_FOUND = public key wurde nicht gefunden
|
PUBLIC_KEY_NOT_FOUND = öffentlicher Schlüssel wurde nicht gefunden
|
||||||
|
|
||||||
# this one is special in that caller expected to pass two additional strings, hence the two %s
|
# this one is special in that caller expected to pass two additional strings, hence the two %s
|
||||||
TRANSACTION_INVALID = Transaktion ungültig: %s (%s)
|
TRANSACTION_INVALID = Transaktion ungültig: %s (%s)
|
||||||
@ -58,19 +58,19 @@ TRANSACTION_INVALID = Transaktion ungültig: %s (%s)
|
|||||||
NAME_UNKNOWN = Name unbekannt
|
NAME_UNKNOWN = Name unbekannt
|
||||||
|
|
||||||
### Asset ###
|
### Asset ###
|
||||||
INVALID_ASSET_ID = ungültige asset ID
|
INVALID_ASSET_ID = Vermögenswert-Kennung ungültig
|
||||||
|
|
||||||
INVALID_ORDER_ID = ungültige asset order ID
|
INVALID_ORDER_ID = Vermögenswert-Auftragssnummer ungültig
|
||||||
|
|
||||||
ORDER_UNKNOWN = unbekannte asset order ID
|
ORDER_UNKNOWN = Vermögenswert-Auftragssnummer unbekannt
|
||||||
|
|
||||||
### Groups ###
|
### Groups ###
|
||||||
GROUP_UNKNOWN = Gruppe unbekannt
|
GROUP_UNKNOWN = Gruppe unbekannt
|
||||||
|
|
||||||
### Foreign Blockchain ###
|
### Foreign Blockchain ###
|
||||||
FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = fremde Blockchain oder ElectrumX Netzwerk Problem
|
FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = fremde Blockchain oder ElectrumX Netzwerkproblem
|
||||||
|
|
||||||
FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = unzureichend Bilanz auf fremde blockchain
|
FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = unzureichendes Guthaben auf fremder blockchain
|
||||||
|
|
||||||
FOREIGN_BLOCKCHAIN_TOO_SOON = zu früh um fremde Blockchain-Transaktionen zu übertragen (Sperrzeit/mittlere Blockzeit)
|
FOREIGN_BLOCKCHAIN_TOO_SOON = zu früh um fremde Blockchain-Transaktionen zu übertragen (Sperrzeit/mittlere Blockzeit)
|
||||||
|
|
||||||
@ -80,4 +80,4 @@ ORDER_SIZE_TOO_SMALL = Bestellmenge zu niedrig
|
|||||||
### Data ###
|
### Data ###
|
||||||
FILE_NOT_FOUND = Datei nicht gefunden
|
FILE_NOT_FOUND = Datei nicht gefunden
|
||||||
|
|
||||||
NO_REPLY = Peer hat nicht mit Daten verbinden
|
NO_REPLY = Peer hat nicht in vorgegebener Zeit geantwortet
|
||||||
|
@ -4,36 +4,36 @@
|
|||||||
# "localeLang": "nl",
|
# "localeLang": "nl",
|
||||||
|
|
||||||
### Common ###
|
### Common ###
|
||||||
JSON = lezen van JSON bericht gefaald
|
JSON = lezen van JSON bericht is mislukt
|
||||||
|
|
||||||
INSUFFICIENT_BALANCE = insufficient balance
|
INSUFFICIENT_BALANCE = onvoldoende saldo
|
||||||
|
|
||||||
UNAUTHORIZED = ongeautoriseerde API call
|
UNAUTHORIZED = ongeautoriseerde API call
|
||||||
|
|
||||||
REPOSITORY_ISSUE = repository fout
|
REPOSITORY_ISSUE = fout in repository
|
||||||
|
|
||||||
NON_PRODUCTION = deze API call is niet toegestaan voor productiesystemen
|
NON_PRODUCTION = deze API call is niet toegestaan voor productiesystemen
|
||||||
|
|
||||||
BLOCKCHAIN_NEEDS_SYNC = blockchain dient eerst gesynchronizeerd te worden
|
BLOCKCHAIN_NEEDS_SYNC = blockchain dient eerst te synchronizeren
|
||||||
|
|
||||||
NO_TIME_SYNC = klok nog niet gesynchronizeerd
|
NO_TIME_SYNC = klok is nog niet gesynchronizeerd
|
||||||
|
|
||||||
### Validation ###
|
### Validation ###
|
||||||
INVALID_SIGNATURE = ongeldige handtekening
|
INVALID_SIGNATURE = ongeldige signature
|
||||||
|
|
||||||
INVALID_ADDRESS = ongeldig adres
|
INVALID_ADDRESS = ongeldig adres
|
||||||
|
|
||||||
INVALID_PUBLIC_KEY = ongeldige public key
|
INVALID_PUBLIC_KEY = ongeldige public key
|
||||||
|
|
||||||
INVALID_DATA = ongeldige gegevens
|
INVALID_DATA = ongeldige data
|
||||||
|
|
||||||
INVALID_NETWORK_ADDRESS = ongeldig netwerkadres
|
INVALID_NETWORK_ADDRESS = ongeldig netwerkadres
|
||||||
|
|
||||||
ADDRESS_UNKNOWN = account adres onbekend
|
ADDRESS_UNKNOWN = account-adres onbekend
|
||||||
|
|
||||||
INVALID_CRITERIA = ongeldige zoekcriteria
|
INVALID_CRITERIA = ongeldige zoekcriteria
|
||||||
|
|
||||||
INVALID_REFERENCE = ongeldige verwijzing
|
INVALID_REFERENCE = ongeldige referentie
|
||||||
|
|
||||||
TRANSFORMATION_ERROR = JSON kon niet omgezet worden in transactie
|
TRANSFORMATION_ERROR = JSON kon niet omgezet worden in transactie
|
||||||
|
|
||||||
@ -44,10 +44,10 @@ INVALID_HEIGHT = ongeldige blokhoogte
|
|||||||
CANNOT_MINT = account kan niet minten
|
CANNOT_MINT = account kan niet minten
|
||||||
|
|
||||||
### Blocks ###
|
### Blocks ###
|
||||||
BLOCK_UNKNOWN = blok onbekend
|
BLOCK_UNKNOWN = blok niet gekend
|
||||||
|
|
||||||
### Transactions ###
|
### Transactions ###
|
||||||
TRANSACTION_UNKNOWN = onbekende transactie
|
TRANSACTION_UNKNOWN = transactie niet gekend
|
||||||
|
|
||||||
PUBLIC_KEY_NOT_FOUND = public key niet gevonden
|
PUBLIC_KEY_NOT_FOUND = public key niet gevonden
|
||||||
|
|
||||||
@ -55,29 +55,29 @@ PUBLIC_KEY_NOT_FOUND = public key niet gevonden
|
|||||||
TRANSACTION_INVALID = ongeldige transactie: %s (%s)
|
TRANSACTION_INVALID = ongeldige transactie: %s (%s)
|
||||||
|
|
||||||
### Naming ###
|
### Naming ###
|
||||||
NAME_UNKNOWN = onbekende naam
|
NAME_UNKNOWN = naam niet gekend
|
||||||
|
|
||||||
### Asset ###
|
### Asset ###
|
||||||
INVALID_ASSET_ID = ongeldige asset ID
|
INVALID_ASSET_ID = ongeldige asset ID
|
||||||
|
|
||||||
INVALID_ORDER_ID = ongeldige asset order ID
|
INVALID_ORDER_ID = ongeldige asset order ID
|
||||||
|
|
||||||
ORDER_UNKNOWN = onbekende asset order ID
|
ORDER_UNKNOWN = niet gekende asset order ID
|
||||||
|
|
||||||
### Groups ###
|
### Groups ###
|
||||||
GROUP_UNKNOWN = onbekende groep
|
GROUP_UNKNOWN = groep niet gekend
|
||||||
|
|
||||||
### Foreign Blockchain ###
|
### Foreign Blockchain ###
|
||||||
FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = blockchain of ElectrumX network probleem
|
FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = vreemde blockchain of ElectrumX networkprobleem
|
||||||
|
|
||||||
FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = onvoldoende saldo blockchain
|
FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = onvoldoende saldo bij vreemde blockchain
|
||||||
|
|
||||||
FOREIGN_BLOCKCHAIN_TOO_SOON = nog niet gereed om de blockchain transactie uittevoeren (LockTime/median block time)
|
FOREIGN_BLOCKCHAIN_TOO_SOON = te vroeg om de blockchain transactie uit te sturen (LockTime/median block time)
|
||||||
|
|
||||||
### Trade Portal ###
|
### Trade Portal ###
|
||||||
ORDER_SIZE_TOO_SMALL = order bedrag te laag
|
ORDER_SIZE_TOO_SMALL = order-bedrag te laag
|
||||||
|
|
||||||
### Data ###
|
### Data ###
|
||||||
FILE_NOT_FOUND = file niet gevonden
|
FILE_NOT_FOUND = bestand niet gevonden
|
||||||
|
|
||||||
NO_REPLY = peer reageerd niet met data
|
NO_REPLY = peer reageerde niet binnen toegelaten tijd
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||||
# SysTray pop-up menu
|
# SysTray pop-up menu
|
||||||
|
|
||||||
APPLYING_UPDATE_AND_RESTARTING = Automatisches Update anwenden und neu starten …
|
APPLYING_UPDATE_AND_RESTARTING = Automatisches Update anwenden und neu starten...
|
||||||
|
|
||||||
AUTO_UPDATE = Automatisches Update
|
AUTO_UPDATE = Automatisches Update
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ CONNECTION = Verbindung
|
|||||||
|
|
||||||
CONNECTIONS = Verbindungen
|
CONNECTIONS = Verbindungen
|
||||||
|
|
||||||
CREATING_BACKUP_OF_DB_FILES = Erstellen Backup von Datenbank Dateien …
|
CREATING_BACKUP_OF_DB_FILES = Erstelle Backup von Datenbank Dateien...
|
||||||
|
|
||||||
DB_BACKUP = Datenbank Backup
|
DB_BACKUP = Datenbank Backup
|
||||||
|
|
||||||
@ -31,18 +31,18 @@ EXIT = Verlassen
|
|||||||
|
|
||||||
LITE_NODE = Lite node
|
LITE_NODE = Lite node
|
||||||
|
|
||||||
MINTING_DISABLED = Kein minting
|
MINTING_DISABLED = Münzprägung inaktiv
|
||||||
|
|
||||||
MINTING_ENABLED = \u2714 Minting aktiviert
|
MINTING_ENABLED = \u2714 Münzprägung aktiv
|
||||||
|
|
||||||
OPEN_UI = Öffne UI
|
OPEN_UI = Öffne Benutzeroberfläche
|
||||||
|
|
||||||
PERFORMING_DB_CHECKPOINT = Speichern von unbestätigten Datenbankänderungen...
|
PERFORMING_DB_CHECKPOINT = Speichere unerfasste Datenbankänderungen...
|
||||||
|
|
||||||
PERFORMING_DB_MAINTENANCE = Planmäßige Wartung durchführen...
|
PERFORMING_DB_MAINTENANCE = Planmäßige Wartung wird durchgeführt...
|
||||||
|
|
||||||
SYNCHRONIZE_CLOCK = Synchronisiere Uhr
|
SYNCHRONIZE_CLOCK = Synchronisiere Uhrzeit
|
||||||
|
|
||||||
SYNCHRONIZING_BLOCKCHAIN = Synchronisierung der Blockchain
|
SYNCHRONIZING_BLOCKCHAIN = Synchronisiere
|
||||||
|
|
||||||
SYNCHRONIZING_CLOCK = Synchronisierung der Uhr
|
SYNCHRONIZING_CLOCK = Uhrzeit wird synchronisiert
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||||
# SysTray pop-up menu
|
# SysTray pop-up menu
|
||||||
|
|
||||||
APPLYING_UPDATE_AND_RESTARTING = Automatische update en herstart worden uitgevoerd...
|
APPLYING_UPDATE_AND_RESTARTING = Bezig met automatische update en herstart...
|
||||||
|
|
||||||
AUTO_UPDATE = Automatische Update
|
AUTO_UPDATE = Automatische Update
|
||||||
|
|
||||||
BLOCK_HEIGHT = Block hoogte
|
BLOCK_HEIGHT = blok-hoogte
|
||||||
|
|
||||||
BLOCKS_REMAINING = blocks remaining
|
BLOCKS_REMAINING = overblijvende blokken
|
||||||
|
|
||||||
BUILD_VERSION = Versie nummer
|
BUILD_VERSION = Versienummer
|
||||||
|
|
||||||
CHECK_TIME_ACCURACY = Controleer accuraatheid van de tijd
|
CHECK_TIME_ACCURACY = Controleer of de tijd correct is
|
||||||
|
|
||||||
CONNECTING = Verbinden
|
CONNECTING = Verbinden
|
||||||
|
|
||||||
@ -33,16 +33,16 @@ LITE_NODE = Lite node
|
|||||||
|
|
||||||
MINTING_DISABLED = Minten is uitgeschakeld
|
MINTING_DISABLED = Minten is uitgeschakeld
|
||||||
|
|
||||||
MINTING_ENABLED = \u2714 Minten is ingeschakeld
|
MINTING_ENABLED = \u2714 Minten is actief
|
||||||
|
|
||||||
OPEN_UI = Open UI
|
OPEN_UI = Open UI
|
||||||
|
|
||||||
PERFORMING_DB_CHECKPOINT = De database wordt gecontroleerd...
|
PERFORMING_DB_CHECKPOINT = De database wordt bijgewerkt...
|
||||||
|
|
||||||
PERFORMING_DB_MAINTENANCE = Uitvoeren van gepland onderhoud...
|
PERFORMING_DB_MAINTENANCE = Bezig met gepland onderhoud...
|
||||||
|
|
||||||
SYNCHRONIZE_CLOCK = Synchronizeer klok
|
SYNCHRONIZE_CLOCK = Synchronizeer klok
|
||||||
|
|
||||||
SYNCHRONIZING_BLOCKCHAIN = Aan het synchronizeren
|
SYNCHRONIZING_BLOCKCHAIN = Bezig met synchronizeren
|
||||||
|
|
||||||
SYNCHRONIZING_CLOCK = Klok wordt gesynchronizeerd
|
SYNCHRONIZING_CLOCK = Klok wordt gesynchronizeerd
|
||||||
|
@ -1,60 +1,60 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
ACCOUNT_ALREADY_EXISTS = Account existiert bereits
|
ACCOUNT_ALREADY_EXISTS = Konto existiert bereits
|
||||||
|
|
||||||
ACCOUNT_CANNOT_REWARD_SHARE = Account kann keine Belohnung teilen
|
ACCOUNT_CANNOT_REWARD_SHARE = Konto kann nicht an Belohnungsbeteiligung teilnehmen
|
||||||
|
|
||||||
ADDRESS_ABOVE_RATE_LIMIT = address hat das angegebene Geschwindigkeitlimit erreicht
|
ADDRESS_ABOVE_RATE_LIMIT = Adresse hat festgelegtes Tarif-Limit erreicht
|
||||||
|
|
||||||
ADDRESS_BLOCKED = Addresse ist geblockt
|
ADDRESS_BLOCKED = diese Adresse ist gesperrt
|
||||||
|
|
||||||
ALREADY_GROUP_ADMIN = bereits Gruppen Admin
|
ALREADY_GROUP_ADMIN = bereits Gruppenadmin
|
||||||
|
|
||||||
ALREADY_GROUP_MEMBER = bereits Gruppen Mitglied
|
ALREADY_GROUP_MEMBER = bereits Gruppenmitglied
|
||||||
|
|
||||||
ALREADY_VOTED_FOR_THAT_OPTION = bereits für diese Option gestimmt
|
ALREADY_VOTED_FOR_THAT_OPTION = bereits für diese Option gestimmt
|
||||||
|
|
||||||
ASSET_ALREADY_EXISTS = asset existiert bereits
|
ASSET_ALREADY_EXISTS = Vermögenswert existiert bereits
|
||||||
|
|
||||||
ASSET_DOES_NOT_EXIST = asset nicht gefunden
|
ASSET_DOES_NOT_EXIST = Vermögenswert existiert nicht
|
||||||
|
|
||||||
ASSET_DOES_NOT_MATCH_AT = asset passt nicht mit AT's asset
|
ASSET_DOES_NOT_MATCH_AT = Vermögenswert stimmt nicht mit dem Vermögenswert von AT überein
|
||||||
|
|
||||||
ASSET_NOT_SPENDABLE = asset ist nicht ausgabefähig
|
ASSET_NOT_SPENDABLE = Vermögenswert ist nicht auszahlbar
|
||||||
|
|
||||||
AT_ALREADY_EXISTS = AT existiert bereits
|
AT_ALREADY_EXISTS = AT existiert bereits
|
||||||
|
|
||||||
AT_IS_FINISHED = AT ist fertig
|
AT_IS_FINISHED = AT ist beendet
|
||||||
|
|
||||||
AT_UNKNOWN = AT unbekannt
|
AT_UNKNOWN = AT unbekannt
|
||||||
|
|
||||||
BAN_EXISTS = ban besteht bereits
|
BAN_EXISTS = Bann ist bereits vorhanden
|
||||||
|
|
||||||
BAN_UNKNOWN = ban unbekannt
|
BAN_UNKNOWN = Bann unbekannt
|
||||||
|
|
||||||
BANNED_FROM_GROUP = von der gruppe gebannt
|
BANNED_FROM_GROUP = aus der Gruppe verbannt
|
||||||
|
|
||||||
BUYER_ALREADY_OWNER = Käufer ist bereits Besitzer
|
BUYER_ALREADY_OWNER = Käufer ist bereits Eigentümer
|
||||||
|
|
||||||
CLOCK_NOT_SYNCED = Uhr nicht synchronisiert
|
CLOCK_NOT_SYNCED = Uhrzeit ist nicht synchronisiert
|
||||||
|
|
||||||
DUPLICATE_MESSAGE = Adresse sendete doppelte Nachricht
|
DUPLICATE_MESSAGE = Adresse hat doppelte Nachricht gesendet
|
||||||
|
|
||||||
DUPLICATE_OPTION = Duplizierungsmöglichkeit
|
DUPLICATE_OPTION = doppelte Option
|
||||||
|
|
||||||
GROUP_ALREADY_EXISTS = Gruppe besteht bereits
|
GROUP_ALREADY_EXISTS = Gruppe existiert bereits
|
||||||
|
|
||||||
GROUP_APPROVAL_DECIDED = Gruppenfreigabe bereits beschlossen
|
GROUP_APPROVAL_DECIDED = Gruppenzulassung bereits beschlossen
|
||||||
|
|
||||||
GROUP_APPROVAL_NOT_REQUIRED = Gruppenfreigabe nicht erforderlich
|
GROUP_APPROVAL_NOT_REQUIRED = Gruppenzustimmung nicht erforderlich
|
||||||
|
|
||||||
GROUP_DOES_NOT_EXIST = Gruppe nicht vorhanden
|
GROUP_DOES_NOT_EXIST = Gruppe existiert nicht
|
||||||
|
|
||||||
GROUP_ID_MISMATCH = Gruppen-ID stimmt nicht überein
|
GROUP_ID_MISMATCH = Gruppen-ID stimmt nicht überein
|
||||||
|
|
||||||
GROUP_OWNER_CANNOT_LEAVE = Gruppenbesitzer kann Gruppe nicht verlassen
|
GROUP_OWNER_CANNOT_LEAVE = Gruppeneigentümer kann Gruppe nicht verlassen
|
||||||
|
|
||||||
HAVE_EQUALS_WANT = das bessesene-asset ist das selbe wie das gesuchte-asset
|
HAVE_EQUALS_WANT = Haben-Vermögenswert ist derselbe wie Wollen-Vermögenswert
|
||||||
|
|
||||||
INCORRECT_NONCE = falsche PoW-Nonce
|
INCORRECT_NONCE = falsche PoW-Nonce
|
||||||
|
|
||||||
@ -64,81 +64,81 @@ INVALID_ADDRESS = ungültige Adresse
|
|||||||
|
|
||||||
INVALID_AMOUNT = ungültiger Betrag
|
INVALID_AMOUNT = ungültiger Betrag
|
||||||
|
|
||||||
INVALID_ASSET_OWNER = Ungültiger Eigentümer
|
INVALID_ASSET_OWNER = ungültiger Vermögenswert-Eigentümer
|
||||||
|
|
||||||
INVALID_AT_TRANSACTION = ungültige AT-Transaktion
|
INVALID_AT_TRANSACTION = ungültige AT-Transaktion
|
||||||
|
|
||||||
INVALID_AT_TYPE_LENGTH = ungültige AT 'Typ' Länge
|
INVALID_AT_TYPE_LENGTH = ungültige AT-Typ-Länge
|
||||||
|
|
||||||
INVALID_BUT_OK = ungültig aber OK
|
INVALID_BUT_OK = ungültig, aber OK
|
||||||
|
|
||||||
INVALID_CREATION_BYTES = ungültige Erstellungs der bytes
|
INVALID_CREATION_BYTES = ungültige Erstellungsbytes
|
||||||
|
|
||||||
INVALID_DATA_LENGTH = ungültige Datenlänge
|
INVALID_DATA_LENGTH = unzulässige Datenlänge
|
||||||
|
|
||||||
INVALID_DESCRIPTION_LENGTH = ungültige Länge der Beschreibung
|
INVALID_DESCRIPTION_LENGTH = unzulässige Länge der Beschreibung
|
||||||
|
|
||||||
INVALID_GROUP_APPROVAL_THRESHOLD = ungültiger Schwellenwert für die Gruppenzulassung
|
INVALID_GROUP_APPROVAL_THRESHOLD = ungültiger Schwellenwert für die Gruppenzulassung
|
||||||
|
|
||||||
INVALID_GROUP_BLOCK_DELAY = Ungültige Blockverzögerung der Gruppenfreigabe
|
INVALID_GROUP_BLOCK_DELAY = ungültige Blockverzögerung bei der Gruppenfreigabe
|
||||||
|
|
||||||
INVALID_GROUP_ID = ungültige Gruppen-ID
|
INVALID_GROUP_ID = ungültige Gruppen-ID
|
||||||
|
|
||||||
INVALID_GROUP_OWNER = ungültiger Gruppenbesitzer
|
INVALID_GROUP_OWNER = ungültiger Gruppeneigentümer
|
||||||
|
|
||||||
INVALID_LIFETIME = unzulässige Lebensdauer
|
INVALID_LIFETIME = unzulässige Gültigkeitsdauer
|
||||||
|
|
||||||
INVALID_NAME_LENGTH = ungültige Namenslänge
|
INVALID_NAME_LENGTH = ungültige Namenslänge
|
||||||
|
|
||||||
INVALID_NAME_OWNER = ungültiger Besitzername
|
INVALID_NAME_OWNER = ungültiger Eigentümer des Namens
|
||||||
|
|
||||||
INVALID_OPTION_LENGTH = ungültige Länge der Optionen
|
INVALID_OPTION_LENGTH = ungültige Länge der Optionen
|
||||||
|
|
||||||
INVALID_OPTIONS_COUNT = Anzahl ungültiger Optionen
|
INVALID_OPTIONS_COUNT = ungültige Anzahl von Optionen
|
||||||
|
|
||||||
INVALID_ORDER_CREATOR = ungültiger Auftragsersteller
|
INVALID_ORDER_CREATOR = ungültiger Auftragsersteller
|
||||||
|
|
||||||
INVALID_PAYMENTS_COUNT = Anzahl ungültiger Zahlungen
|
INVALID_PAYMENTS_COUNT = ungültige Anzahl der Zahlungen
|
||||||
|
|
||||||
INVALID_PUBLIC_KEY = ungültiger öffentlicher Schlüssel
|
INVALID_PUBLIC_KEY = ungültiger öffentlicher Schlüssel
|
||||||
|
|
||||||
INVALID_QUANTITY = unzulässige Menge
|
INVALID_QUANTITY = ungültige Menge
|
||||||
|
|
||||||
INVALID_REFERENCE = ungültige Referenz
|
INVALID_REFERENCE = ungültige Referenz
|
||||||
|
|
||||||
INVALID_RETURN = ungültige Rückgabe
|
INVALID_RETURN = ungültige Rückgabe
|
||||||
|
|
||||||
INVALID_REWARD_SHARE_PERCENT = ungültig Prozent der Belohnunganteile
|
INVALID_REWARD_SHARE_PERCENT = ungültiger Belohnungsbeteiligungs-Anteil
|
||||||
|
|
||||||
INVALID_SELLER = unzulässiger Verkäufer
|
INVALID_SELLER = ungültiger Verkäufer
|
||||||
|
|
||||||
INVALID_TAGS_LENGTH = ungültige 'tags'-Länge
|
INVALID_TAGS_LENGTH = ungültige 'tags'-Länge
|
||||||
|
|
||||||
INVALID_TIMESTAMP_SIGNATURE = Ungültige Zeitstempel-Signatur
|
INVALID_TIMESTAMP_SIGNATURE = ungültige Zeitstempel-Signatur
|
||||||
|
|
||||||
INVALID_TX_GROUP_ID = Ungültige Transaktionsgruppen-ID
|
INVALID_TX_GROUP_ID = ungültige Transaktionsgruppen-ID
|
||||||
|
|
||||||
INVALID_VALUE_LENGTH = ungültige 'Wert'-Länge
|
INVALID_VALUE_LENGTH = ungültige 'value'-Länge
|
||||||
|
|
||||||
INVITE_UNKNOWN = Gruppeneinladung unbekannt
|
INVITE_UNKNOWN = Gruppeneinladung unbekannt
|
||||||
|
|
||||||
JOIN_REQUEST_EXISTS = Gruppeneinladung existiert bereits
|
JOIN_REQUEST_EXISTS = Gruppenverbindungsanfrage existiert bereits
|
||||||
|
|
||||||
MAXIMUM_REWARD_SHARES = die maximale Anzahl von Reward-Shares für dieses Konto erreicht
|
MAXIMUM_REWARD_SHARES = maximale Anzahl von Belohnungsbeteiligungen für dieses Konto bereits erreicht
|
||||||
|
|
||||||
MISSING_CREATOR = fehlender Ersteller
|
MISSING_CREATOR = fehlender Ersteller
|
||||||
|
|
||||||
MULTIPLE_NAMES_FORBIDDEN = mehrere registrierte Namen pro Konto sind untersagt
|
MULTIPLE_NAMES_FORBIDDEN = mehrere registrierte Namen pro Konto sind verboten
|
||||||
|
|
||||||
NAME_ALREADY_FOR_SALE = Name bereits zum Verkauf
|
NAME_ALREADY_FOR_SALE = Name steht bereits zum Verkauf
|
||||||
|
|
||||||
NAME_ALREADY_REGISTERED = Name bereits registriert
|
NAME_ALREADY_REGISTERED = Name bereits registriert
|
||||||
|
|
||||||
NAME_BLOCKED = Name geblockt
|
NAME_BLOCKED = dieser Name ist gesperrt
|
||||||
|
|
||||||
NAME_DOES_NOT_EXIST = Name nicht vorhanden
|
NAME_DOES_NOT_EXIST = Name existiert nicht
|
||||||
|
|
||||||
NAME_NOT_FOR_SALE = Name ist unverkäuflich
|
NAME_NOT_FOR_SALE = Name steht nicht zum Verkauf
|
||||||
|
|
||||||
NAME_NOT_NORMALIZED = Name nicht in Unicode-'normalisierter' Form
|
NAME_NOT_NORMALIZED = Name nicht in Unicode-'normalisierter' Form
|
||||||
|
|
||||||
@ -150,46 +150,46 @@ NEGATIVE_PRICE = ungültiger/negativer Preis
|
|||||||
|
|
||||||
NO_BALANCE = unzureichendes Guthaben
|
NO_BALANCE = unzureichendes Guthaben
|
||||||
|
|
||||||
NO_BLOCKCHAIN_LOCK = die Blockchain des Knotens ist beschäftigt
|
NO_BLOCKCHAIN_LOCK = die Blockchain des Knotens ist derzeit beschäftigt
|
||||||
|
|
||||||
NO_FLAG_PERMISSION = Konto hat diese Berechtigung nicht
|
NO_FLAG_PERMISSION = Konto hat diese Berechtigung nicht
|
||||||
|
|
||||||
NOT_GROUP_ADMIN = Account ist kein Gruppenadmin
|
NOT_GROUP_ADMIN = Konto ist kein Gruppenadmin
|
||||||
|
|
||||||
NOT_GROUP_MEMBER = Account kein Gruppenmitglied
|
NOT_GROUP_MEMBER = Konto ist kein Gruppenmitglied
|
||||||
|
|
||||||
NOT_MINTING_ACCOUNT = Account kann nicht minten
|
NOT_MINTING_ACCOUNT = Konto kann nicht prägen
|
||||||
|
|
||||||
NOT_YET_RELEASED = Funktion noch nicht freigegeben
|
NOT_YET_RELEASED = Funktion noch nicht freigegeben
|
||||||
|
|
||||||
OK = OK
|
OK = OK
|
||||||
|
|
||||||
ORDER_ALREADY_CLOSED = Asset Trade Order ist bereits geschlossen
|
ORDER_ALREADY_CLOSED = Vermögenswert-Handelsauftrag ist bereits geschlossen
|
||||||
|
|
||||||
ORDER_DOES_NOT_EXIST = asset trade order existiert nicht
|
ORDER_DOES_NOT_EXIST = Vermögenswert-Handelsauftrag existiert nicht
|
||||||
|
|
||||||
POLL_ALREADY_EXISTS = Umfrage bereits vorhanden
|
POLL_ALREADY_EXISTS = Umfrage existiert bereits
|
||||||
|
|
||||||
POLL_DOES_NOT_EXIST = Umfrage nicht vorhanden
|
POLL_DOES_NOT_EXIST = Umfrage existiert nicht
|
||||||
|
|
||||||
POLL_OPTION_DOES_NOT_EXIST = Umfrageoption existiert nicht
|
POLL_OPTION_DOES_NOT_EXIST = Umfrageoption nicht vorhanden
|
||||||
|
|
||||||
PUBLIC_KEY_UNKNOWN = öffentlicher Schlüssel unbekannt
|
PUBLIC_KEY_UNKNOWN = öffentlicher Schlüssel unbekannt
|
||||||
|
|
||||||
REWARD_SHARE_UNKNOWN = Geteilte Belohnungen unbekant
|
REWARD_SHARE_UNKNOWN = Belohnungsbeteiligung unbekannt
|
||||||
|
|
||||||
SELF_SHARE_EXISTS = Selbstbeteiligung (Geteilte Belohnungen) sind breits vorhanden
|
SELF_SHARE_EXISTS = Selbst-Beteiligung (Belohnungsbeteiligung) existiert bereits
|
||||||
|
|
||||||
TIMESTAMP_TOO_NEW = Zeitstempel zu neu
|
TIMESTAMP_TOO_NEW = Zeitstempel zu neu
|
||||||
|
|
||||||
TIMESTAMP_TOO_OLD = Zeitstempel zu alt
|
TIMESTAMP_TOO_OLD = Zeitstempel zu alt
|
||||||
|
|
||||||
TOO_MANY_UNCONFIRMED = Account hat zu viele unbestätigte Transaktionen am laufen
|
TOO_MANY_UNCONFIRMED = Konto hat zu viele ausstehende unbestätigte Transaktionen
|
||||||
|
|
||||||
TRANSACTION_ALREADY_CONFIRMED = Transaktionen sind bereits bestätigt
|
TRANSACTION_ALREADY_CONFIRMED = Transaktion wurde bereits bestätigt
|
||||||
|
|
||||||
TRANSACTION_ALREADY_EXISTS = Transaktionen existiert bereits
|
TRANSACTION_ALREADY_EXISTS = Transaktion existiert bereits
|
||||||
|
|
||||||
TRANSACTION_UNKNOWN = Unbekante Transaktion
|
TRANSACTION_UNKNOWN = Transaktion unbekannt
|
||||||
|
|
||||||
TX_GROUP_ID_MISMATCH = Transaktion Gruppen ID stimmt nicht überein
|
TX_GROUP_ID_MISMATCH = die Gruppen-ID der Transaktion stimmt nicht überein
|
||||||
|
@ -1,28 +1,28 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
ACCOUNT_ALREADY_EXISTS = account bestaat al
|
ACCOUNT_ALREADY_EXISTS = account bestaat reeds
|
||||||
|
|
||||||
ACCOUNT_CANNOT_REWARD_SHARE = account kan geen beloningen delen
|
ACCOUNT_CANNOT_REWARD_SHARE = account kan geen beloningen delen
|
||||||
|
|
||||||
ADDRESS_ABOVE_RATE_LIMIT = adres heeft een waarde limiet bereikt
|
ADDRESS_ABOVE_RATE_LIMIT = adres heeft opgegeven limietwaarde bereikt
|
||||||
|
|
||||||
ADDRESS_BLOCKED = adres is geblokkeerd
|
ADDRESS_BLOCKED = dit adres is geblokkeerd
|
||||||
|
|
||||||
ALREADY_GROUP_ADMIN = groeps administrator bestaat al
|
ALREADY_GROUP_ADMIN = reeds gekend als groepsadministrator
|
||||||
|
|
||||||
ALREADY_GROUP_MEMBER = groeps lid bestaat al
|
ALREADY_GROUP_MEMBER = reeds gekend als groepslid
|
||||||
|
|
||||||
ALREADY_VOTED_FOR_THAT_OPTION = reeds gestemd voor die optie
|
ALREADY_VOTED_FOR_THAT_OPTION = reeds gestemd voor die optie
|
||||||
|
|
||||||
ASSET_ALREADY_EXISTS = asset bestaat al
|
ASSET_ALREADY_EXISTS = asset bestaat reeds
|
||||||
|
|
||||||
ASSET_DOES_NOT_EXIST = asset bestaat niet
|
ASSET_DOES_NOT_EXIST = asset bestaat niet
|
||||||
|
|
||||||
ASSET_DOES_NOT_MATCH_AT = asset komt niet overeen met de asset van de AT
|
ASSET_DOES_NOT_MATCH_AT = asset komt niet overeen met de asset van de AT
|
||||||
|
|
||||||
ASSET_NOT_SPENDABLE = asset is niet toerijkend
|
ASSET_NOT_SPENDABLE = asset kan niet uitbetaald worden
|
||||||
|
|
||||||
AT_ALREADY_EXISTS = AT bestaat al
|
AT_ALREADY_EXISTS = AT bestaat reeds
|
||||||
|
|
||||||
AT_IS_FINISHED = AT is afgelopen
|
AT_IS_FINISHED = AT is afgelopen
|
||||||
|
|
||||||
@ -38,25 +38,25 @@ BUYER_ALREADY_OWNER = koper is al de eigenaar
|
|||||||
|
|
||||||
CLOCK_NOT_SYNCED = klok is niet gesynchronizeerd
|
CLOCK_NOT_SYNCED = klok is niet gesynchronizeerd
|
||||||
|
|
||||||
DUPLICATE_MESSAGE = dubbel adres bericht
|
DUPLICATE_MESSAGE = adres heeft dubbel bericht verzonden
|
||||||
|
|
||||||
DUPLICATE_OPTION = dubbele optie
|
DUPLICATE_OPTION = dubbele optie
|
||||||
|
|
||||||
GROUP_ALREADY_EXISTS = groep bestaat reeds
|
GROUP_ALREADY_EXISTS = groep bestaat reeds
|
||||||
|
|
||||||
GROUP_APPROVAL_DECIDED = groeps goedkeuring afgewezen
|
GROUP_APPROVAL_DECIDED = groeps-goedkeuring afgewezen
|
||||||
|
|
||||||
GROUP_APPROVAL_NOT_REQUIRED = groeps goedkeuring niet vereist
|
GROUP_APPROVAL_NOT_REQUIRED = groeps-goedkeuring niet vereist
|
||||||
|
|
||||||
GROUP_DOES_NOT_EXIST = groep bestaat niet
|
GROUP_DOES_NOT_EXIST = groep bestaat niet
|
||||||
|
|
||||||
GROUP_ID_MISMATCH = groeps ID komt niet overeen
|
GROUP_ID_MISMATCH = groeps ID komt niet overeen
|
||||||
|
|
||||||
GROUP_OWNER_CANNOT_LEAVE = groep eigenaar kan de groep niet verlaten
|
GROUP_OWNER_CANNOT_LEAVE = groep-eigenaar kan groep niet verlaten
|
||||||
|
|
||||||
HAVE_EQUALS_WANT = asset is gelijk aan Want-asset
|
HAVE_EQUALS_WANT = asset is gelijk aan Wens-asset
|
||||||
|
|
||||||
INCORRECT_NONCE = incorrecte PoW nonce
|
INCORRECT_NONCE = foutieve PoW nonce
|
||||||
|
|
||||||
INSUFFICIENT_FEE = vergoeding te laag
|
INSUFFICIENT_FEE = vergoeding te laag
|
||||||
|
|
||||||
@ -72,19 +72,19 @@ INVALID_AT_TYPE_LENGTH = ongeldige lengte voor AT type
|
|||||||
|
|
||||||
INVALID_BUT_OK = ongeldig maar is in orde
|
INVALID_BUT_OK = ongeldig maar is in orde
|
||||||
|
|
||||||
INVALID_CREATION_BYTES = ongeldige gecreerde bytes
|
INVALID_CREATION_BYTES = ongeldige creatie-bytes
|
||||||
|
|
||||||
INVALID_DATA_LENGTH = ongeldige data lengte
|
INVALID_DATA_LENGTH = ongeldige data-lengte
|
||||||
|
|
||||||
INVALID_DESCRIPTION_LENGTH = ongeldige lengte voor de beschrijving
|
INVALID_DESCRIPTION_LENGTH = ongeldige lengte voor beschrijving
|
||||||
|
|
||||||
INVALID_GROUP_APPROVAL_THRESHOLD = ongeldige drempelwaarde voor groepsgoedkeuring
|
INVALID_GROUP_APPROVAL_THRESHOLD = ongeldige drempelwaarde voor groepsgoedkeuring
|
||||||
|
|
||||||
INVALID_GROUP_BLOCK_DELAY = ongeldige groep blok vertraging
|
INVALID_GROUP_BLOCK_DELAY = ongeldige blok-vertraging bij groepsgoedkeuring
|
||||||
|
|
||||||
INVALID_GROUP_ID = ongeldige groep-ID
|
INVALID_GROUP_ID = ongeldige groep-ID
|
||||||
|
|
||||||
INVALID_GROUP_OWNER = ongeldige groep eigenaar
|
INVALID_GROUP_OWNER = ongeldige groep-eigenaar
|
||||||
|
|
||||||
INVALID_LIFETIME = ongeldige levensduur
|
INVALID_LIFETIME = ongeldige levensduur
|
||||||
|
|
||||||
@ -94,27 +94,27 @@ INVALID_NAME_OWNER = ongeldige naam voor eigenaar
|
|||||||
|
|
||||||
INVALID_OPTION_LENGTH = ongeldige lengte voor opties
|
INVALID_OPTION_LENGTH = ongeldige lengte voor opties
|
||||||
|
|
||||||
INVALID_OPTIONS_COUNT = ongeldige hoeveelheid opties
|
INVALID_OPTIONS_COUNT = ongeldig aantal opties
|
||||||
|
|
||||||
INVALID_ORDER_CREATOR = ongeldige gebruiker voor deze order
|
INVALID_ORDER_CREATOR = ongeldige order-creatie-gebruiker
|
||||||
|
|
||||||
INVALID_PAYMENTS_COUNT = ongeldige betalings waarde
|
INVALID_PAYMENTS_COUNT = ongeldig aantal betalingen
|
||||||
|
|
||||||
INVALID_PUBLIC_KEY = ongeldige public key
|
INVALID_PUBLIC_KEY = ongeldige public key
|
||||||
|
|
||||||
INVALID_QUANTITY = ongeldige hoeveelheid
|
INVALID_QUANTITY = ongeldige hoeveelheid
|
||||||
|
|
||||||
INVALID_REFERENCE = ongeldige verwijzing
|
INVALID_REFERENCE = ongeldige referentie
|
||||||
|
|
||||||
INVALID_RETURN = ongeldige return
|
INVALID_RETURN = ongeldig resultaat
|
||||||
|
|
||||||
INVALID_REWARD_SHARE_PERCENT = ongeldig belonings percentage
|
INVALID_REWARD_SHARE_PERCENT = ongeldig belonings-deelpercentage
|
||||||
|
|
||||||
INVALID_SELLER = ongeldige verkoper
|
INVALID_SELLER = ongeldige verkoper
|
||||||
|
|
||||||
INVALID_TAGS_LENGTH = ongeldige lengte voor 'tags'
|
INVALID_TAGS_LENGTH = ongeldige lengte voor 'tags'
|
||||||
|
|
||||||
INVALID_TIMESTAMP_SIGNATURE = ongeldig tijd aanduiding
|
INVALID_TIMESTAMP_SIGNATURE = ongeldig tijd-aanduiding
|
||||||
|
|
||||||
INVALID_TX_GROUP_ID = ongeldige transactiegroep-ID
|
INVALID_TX_GROUP_ID = ongeldige transactiegroep-ID
|
||||||
|
|
||||||
@ -124,15 +124,15 @@ INVITE_UNKNOWN = onbekende groepsuitnodiging
|
|||||||
|
|
||||||
JOIN_REQUEST_EXISTS = aanvraag om lid van groep te worden bestaat al
|
JOIN_REQUEST_EXISTS = aanvraag om lid van groep te worden bestaat al
|
||||||
|
|
||||||
MAXIMUM_REWARD_SHARES = limiet aan belonen voor dit account bereikt
|
MAXIMUM_REWARD_SHARES = maximum bereikt voor beloning-delen voor dit account
|
||||||
|
|
||||||
MISSING_CREATOR = ontbrekende aanmaker
|
MISSING_CREATOR = creator niet gekend
|
||||||
|
|
||||||
MULTIPLE_NAMES_FORBIDDEN = het registreren van meerdere namen op een account is niet toegestaan
|
MULTIPLE_NAMES_FORBIDDEN = het registreren van meerdere namen op een account is niet toegestaan
|
||||||
|
|
||||||
NAME_ALREADY_FOR_SALE = naam reeds te koop
|
NAME_ALREADY_FOR_SALE = naam is reeds te koop
|
||||||
|
|
||||||
NAME_ALREADY_REGISTERED = naam reeds geregistreerd
|
NAME_ALREADY_REGISTERED = naam is reeds geregistreerd
|
||||||
|
|
||||||
NAME_BLOCKED = deze naam is geblokkeerd
|
NAME_BLOCKED = deze naam is geblokkeerd
|
||||||
|
|
||||||
@ -140,7 +140,7 @@ NAME_DOES_NOT_EXIST = naam bestaat niet
|
|||||||
|
|
||||||
NAME_NOT_FOR_SALE = naam is niet te koop
|
NAME_NOT_FOR_SALE = naam is niet te koop
|
||||||
|
|
||||||
NAME_NOT_NORMALIZED = naam voldoet niet aan Unicode-vorm
|
NAME_NOT_NORMALIZED = naam niet in Unicode-'nomaal'-vorm
|
||||||
|
|
||||||
NEGATIVE_AMOUNT = negatieve hoeveelheid
|
NEGATIVE_AMOUNT = negatieve hoeveelheid
|
||||||
|
|
||||||
@ -148,9 +148,9 @@ NEGATIVE_FEE = negatieve vergoeding
|
|||||||
|
|
||||||
NEGATIVE_PRICE = negatieve prijs
|
NEGATIVE_PRICE = negatieve prijs
|
||||||
|
|
||||||
NO_BALANCE = onvoldoende balans
|
NO_BALANCE = onvoldoende saldo
|
||||||
|
|
||||||
NO_BLOCKCHAIN_LOCK = geen blockchain slot
|
NO_BLOCKCHAIN_LOCK = blockchain op node is momenteel bezet
|
||||||
|
|
||||||
NO_FLAG_PERMISSION = account heeft hier geen toestemming voor
|
NO_FLAG_PERMISSION = account heeft hier geen toestemming voor
|
||||||
|
|
||||||
@ -176,9 +176,9 @@ POLL_OPTION_DOES_NOT_EXIST = peilingsoptie bestaat niet
|
|||||||
|
|
||||||
PUBLIC_KEY_UNKNOWN = public key onbekend
|
PUBLIC_KEY_UNKNOWN = public key onbekend
|
||||||
|
|
||||||
REWARD_SHARE_UNKNOWN = beloning vergoeding onbekend
|
REWARD_SHARE_UNKNOWN = beloningsdeel is onbekend
|
||||||
|
|
||||||
SELF_SHARE_EXISTS = zelf vergoeding bestaat reeds
|
SELF_SHARE_EXISTS = zelf-beloning (belonings-delen) bestaat reeds
|
||||||
|
|
||||||
TIMESTAMP_TOO_NEW = tijdstempel te nieuw
|
TIMESTAMP_TOO_NEW = tijdstempel te nieuw
|
||||||
|
|
||||||
@ -186,10 +186,10 @@ TIMESTAMP_TOO_OLD = tijdstempel te oud
|
|||||||
|
|
||||||
TOO_MANY_UNCONFIRMED = account heeft te veel onbevestigde transacties in afwachting
|
TOO_MANY_UNCONFIRMED = account heeft te veel onbevestigde transacties in afwachting
|
||||||
|
|
||||||
TRANSACTION_ALREADY_CONFIRMED = transactie is reeds bevestigd
|
TRANSACTION_ALREADY_CONFIRMED = transactie werd reeds bevestigd
|
||||||
|
|
||||||
TRANSACTION_ALREADY_EXISTS = transactie bestaat al
|
TRANSACTION_ALREADY_EXISTS = transactie bestaat reeds
|
||||||
|
|
||||||
TRANSACTION_UNKNOWN = transactie onbekend
|
TRANSACTION_UNKNOWN = transactie onbekend
|
||||||
|
|
||||||
TX_GROUP_ID_MISMATCH = groep ID komt niet overeen
|
TX_GROUP_ID_MISMATCH = groep-ID komt niet overeen
|
||||||
|
@ -3,6 +3,8 @@ package org.qortal.test;
|
|||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.qortal.crypto.MemoryPoW;
|
import org.qortal.crypto.MemoryPoW;
|
||||||
|
import org.qortal.repository.DataException;
|
||||||
|
import org.qortal.test.common.Common;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
@ -39,13 +41,14 @@ public class MemoryPoWTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMultipleComputes() {
|
public void testMultipleComputes() throws DataException {
|
||||||
|
Common.useDefaultSettings();
|
||||||
Random random = new Random();
|
Random random = new Random();
|
||||||
|
|
||||||
final int sampleSize = 20;
|
final int sampleSize = 10;
|
||||||
final long stddevDivisor = sampleSize * (sampleSize - 1);
|
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];
|
byte[] data = new byte[256];
|
||||||
long[] times = new long[sampleSize];
|
long[] times = new long[sampleSize];
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package org.qortal.test;
|
package org.qortal.test;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.reflect.FieldUtils;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@ -14,12 +15,9 @@ import org.qortal.group.Group.ApprovalThreshold;
|
|||||||
import org.qortal.repository.DataException;
|
import org.qortal.repository.DataException;
|
||||||
import org.qortal.repository.Repository;
|
import org.qortal.repository.Repository;
|
||||||
import org.qortal.repository.RepositoryManager;
|
import org.qortal.repository.RepositoryManager;
|
||||||
import org.qortal.test.common.BlockUtils;
|
import org.qortal.test.common.*;
|
||||||
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.transaction.TestTransaction;
|
import org.qortal.test.common.transaction.TestTransaction;
|
||||||
|
import org.qortal.transaction.DeployAtTransaction;
|
||||||
import org.qortal.transaction.MessageTransaction;
|
import org.qortal.transaction.MessageTransaction;
|
||||||
import org.qortal.transaction.Transaction;
|
import org.qortal.transaction.Transaction;
|
||||||
import org.qortal.transaction.Transaction.TransactionType;
|
import org.qortal.transaction.Transaction.TransactionType;
|
||||||
@ -31,6 +29,7 @@ import org.qortal.utils.NTP;
|
|||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
public class MessageTests extends Common {
|
public class MessageTests extends Common {
|
||||||
@ -85,7 +84,7 @@ public class MessageTests extends Common {
|
|||||||
byte[] randomReference = new byte[64];
|
byte[] randomReference = new byte[64];
|
||||||
random.nextBytes(randomReference);
|
random.nextBytes(randomReference);
|
||||||
|
|
||||||
long minimumFee = BlockChain.getInstance().getUnitFee();
|
long minimumFee = BlockChain.getInstance().getUnitFeeAtTimestamp(System.currentTimeMillis());
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||||
@ -139,7 +138,7 @@ public class MessageTests extends Common {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void withRecipentWithAmount() throws DataException {
|
public void withRecipientWithAmount() throws DataException {
|
||||||
testMessage(Group.NO_GROUP, recipient, 123L, Asset.QORT);
|
testMessage(Group.NO_GROUP, recipient, 123L, Asset.QORT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,6 +152,140 @@ public class MessageTests extends Common {
|
|||||||
testMessage(1, null, 0L, null);
|
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
|
@Test
|
||||||
public void serializationTests() throws DataException, TransformationException {
|
public void serializationTests() throws DataException, TransformationException {
|
||||||
// with recipient, with amount
|
// with recipient, with amount
|
||||||
@ -165,6 +298,24 @@ public class MessageTests extends Common {
|
|||||||
testSerialization(null, 0L, null);
|
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 {
|
private boolean isValid(int txGroupId, String recipient, long amount, Long assetId) throws DataException {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
TestAccount alice = Common.getTestAccount(repository, "alice");
|
TestAccount alice = Common.getTestAccount(repository, "alice");
|
||||||
@ -195,41 +346,48 @@ public class MessageTests extends Common {
|
|||||||
return messageTransaction.hasValidReference();
|
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()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
TestAccount alice = Common.getTestAccount(repository, "alice");
|
return testFeeNonce(repository, withFee, withNonce, recipient, isValid);
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
TestAccount alice = Common.getTestAccount(repository, "alice");
|
TestAccount alice = Common.getTestAccount(repository, "alice");
|
||||||
|
|
||||||
@ -244,6 +402,8 @@ public class MessageTests extends Common {
|
|||||||
TransactionUtils.signAndMint(repository, transactionData, alice);
|
TransactionUtils.signAndMint(repository, transactionData, alice);
|
||||||
|
|
||||||
BlockUtils.orphanLastBlock(repository);
|
BlockUtils.orphanLastBlock(repository);
|
||||||
|
|
||||||
|
return new MessageTransaction(repository, transactionData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ import org.qortal.arbitrary.ArbitraryDataReader;
|
|||||||
import org.qortal.arbitrary.exception.MissingDataException;
|
import org.qortal.arbitrary.exception.MissingDataException;
|
||||||
import org.qortal.arbitrary.misc.Category;
|
import org.qortal.arbitrary.misc.Category;
|
||||||
import org.qortal.arbitrary.misc.Service;
|
import org.qortal.arbitrary.misc.Service;
|
||||||
|
import org.qortal.block.BlockChain;
|
||||||
import org.qortal.controller.arbitrary.ArbitraryDataManager;
|
import org.qortal.controller.arbitrary.ArbitraryDataManager;
|
||||||
import org.qortal.data.arbitrary.ArbitraryResourceMetadata;
|
import org.qortal.data.arbitrary.ArbitraryResourceMetadata;
|
||||||
import org.qortal.data.transaction.ArbitraryTransactionData;
|
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.test.common.transaction.TestTransaction;
|
||||||
import org.qortal.transaction.RegisterNameTransaction;
|
import org.qortal.transaction.RegisterNameTransaction;
|
||||||
import org.qortal.utils.Base58;
|
import org.qortal.utils.Base58;
|
||||||
|
import org.qortal.utils.NTP;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@ -106,8 +108,9 @@ public class ArbitraryTransactionMetadataTests extends Common {
|
|||||||
|
|
||||||
// Create PUT transaction
|
// Create PUT transaction
|
||||||
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength);
|
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength);
|
||||||
|
long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime());
|
||||||
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name,
|
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);
|
title, description, tags, category);
|
||||||
|
|
||||||
// Check the chunk count is correct
|
// Check the chunk count is correct
|
||||||
@ -157,8 +160,9 @@ public class ArbitraryTransactionMetadataTests extends Common {
|
|||||||
|
|
||||||
// Create PUT transaction
|
// Create PUT transaction
|
||||||
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength);
|
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength);
|
||||||
|
long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime());
|
||||||
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name,
|
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);
|
title, description, tags, category);
|
||||||
|
|
||||||
// Check the chunk count is correct
|
// Check the chunk count is correct
|
||||||
@ -220,8 +224,9 @@ public class ArbitraryTransactionMetadataTests extends Common {
|
|||||||
|
|
||||||
// Create PUT transaction
|
// Create PUT transaction
|
||||||
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength);
|
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength);
|
||||||
|
long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime());
|
||||||
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name,
|
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);
|
title, description, tags, category);
|
||||||
|
|
||||||
// Check the chunk count is correct
|
// Check the chunk count is correct
|
||||||
@ -272,8 +277,9 @@ public class ArbitraryTransactionMetadataTests extends Common {
|
|||||||
|
|
||||||
// Create PUT transaction
|
// Create PUT transaction
|
||||||
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength);
|
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength);
|
||||||
|
long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime());
|
||||||
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name,
|
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);
|
title, description, tags, category);
|
||||||
|
|
||||||
// Check the chunk count is correct
|
// Check the chunk count is correct
|
||||||
@ -316,8 +322,9 @@ public class ArbitraryTransactionMetadataTests extends Common {
|
|||||||
|
|
||||||
// Create PUT transaction
|
// Create PUT transaction
|
||||||
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength);
|
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength);
|
||||||
|
long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime());
|
||||||
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name,
|
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);
|
title, description, tags, category);
|
||||||
|
|
||||||
// Check the metadata is correct
|
// Check the metadata is correct
|
||||||
|
@ -10,6 +10,7 @@ import org.qortal.arbitrary.ArbitraryDataTransactionBuilder;
|
|||||||
import org.qortal.arbitrary.exception.MissingDataException;
|
import org.qortal.arbitrary.exception.MissingDataException;
|
||||||
import org.qortal.arbitrary.misc.Category;
|
import org.qortal.arbitrary.misc.Category;
|
||||||
import org.qortal.arbitrary.misc.Service;
|
import org.qortal.arbitrary.misc.Service;
|
||||||
|
import org.qortal.block.BlockChain;
|
||||||
import org.qortal.controller.arbitrary.ArbitraryDataManager;
|
import org.qortal.controller.arbitrary.ArbitraryDataManager;
|
||||||
import org.qortal.crypto.Crypto;
|
import org.qortal.crypto.Crypto;
|
||||||
import org.qortal.data.PaymentData;
|
import org.qortal.data.PaymentData;
|
||||||
@ -50,51 +51,6 @@ public class ArbitraryTransactionTests extends Common {
|
|||||||
Common.useDefaultSettings();
|
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
|
@Test
|
||||||
public void testNonceAndFee() throws IllegalAccessException, DataException, IOException {
|
public void testNonceAndFee() throws IllegalAccessException, DataException, IOException {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
@ -497,8 +453,9 @@ public class ArbitraryTransactionTests extends Common {
|
|||||||
|
|
||||||
// Create PUT transaction
|
// Create PUT transaction
|
||||||
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength, true);
|
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength, true);
|
||||||
|
long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime());
|
||||||
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name,
|
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);
|
null, null, null, null);
|
||||||
|
|
||||||
byte[] signature = arbitraryDataFile.getSignature();
|
byte[] signature = arbitraryDataFile.getSignature();
|
||||||
@ -556,8 +513,9 @@ public class ArbitraryTransactionTests extends Common {
|
|||||||
|
|
||||||
// Create PUT transaction
|
// Create PUT transaction
|
||||||
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength, true);
|
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength, true);
|
||||||
|
long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime());
|
||||||
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name,
|
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);
|
title, description, tags, category);
|
||||||
|
|
||||||
byte[] signature = arbitraryDataFile.getSignature();
|
byte[] signature = arbitraryDataFile.getSignature();
|
||||||
@ -614,8 +572,9 @@ public class ArbitraryTransactionTests extends Common {
|
|||||||
|
|
||||||
// Create PUT transaction
|
// Create PUT transaction
|
||||||
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength, true);
|
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength, true);
|
||||||
|
long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime());
|
||||||
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name,
|
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);
|
null, null, null, null);
|
||||||
|
|
||||||
byte[] signature = arbitraryDataFile.getSignature();
|
byte[] signature = arbitraryDataFile.getSignature();
|
||||||
|
@ -5,10 +5,12 @@ import org.qortal.arbitrary.ArbitraryDataFile;
|
|||||||
import org.qortal.arbitrary.ArbitraryDataTransactionBuilder;
|
import org.qortal.arbitrary.ArbitraryDataTransactionBuilder;
|
||||||
import org.qortal.arbitrary.misc.Category;
|
import org.qortal.arbitrary.misc.Category;
|
||||||
import org.qortal.arbitrary.misc.Service;
|
import org.qortal.arbitrary.misc.Service;
|
||||||
|
import org.qortal.block.BlockChain;
|
||||||
import org.qortal.data.transaction.ArbitraryTransactionData;
|
import org.qortal.data.transaction.ArbitraryTransactionData;
|
||||||
import org.qortal.repository.DataException;
|
import org.qortal.repository.DataException;
|
||||||
import org.qortal.repository.Repository;
|
import org.qortal.repository.Repository;
|
||||||
import org.qortal.transaction.Transaction;
|
import org.qortal.transaction.Transaction;
|
||||||
|
import org.qortal.utils.NTP;
|
||||||
|
|
||||||
import java.io.BufferedWriter;
|
import java.io.BufferedWriter;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@ -20,16 +22,15 @@ import java.nio.file.Paths;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
|
|
||||||
public class ArbitraryUtils {
|
public class ArbitraryUtils {
|
||||||
|
|
||||||
public static ArbitraryDataFile createAndMintTxn(Repository repository, String publicKey58, Path path, String name, String identifier,
|
public static ArbitraryDataFile createAndMintTxn(Repository repository, String publicKey58, Path path, String name, String identifier,
|
||||||
ArbitraryTransactionData.Method method, Service service, PrivateKeyAccount account,
|
ArbitraryTransactionData.Method method, Service service, PrivateKeyAccount account,
|
||||||
int chunkSize) throws DataException {
|
int chunkSize) throws DataException {
|
||||||
|
|
||||||
|
long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime());
|
||||||
return ArbitraryUtils.createAndMintTxn(repository, publicKey58, path, name, identifier, method, service,
|
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,
|
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();
|
ArbitraryTransactionData transactionData = txnBuilder.getArbitraryTransactionData();
|
||||||
Transaction.ValidationResult result = TransactionUtils.signAndImport(repository, transactionData, account);
|
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);
|
BlockUtils.mintBlock(repository);
|
||||||
|
|
||||||
// We need a new ArbitraryDataFile instance because the files will have been moved to the signature's folder
|
// 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.data.transaction.BaseTransactionData;
|
||||||
import org.qortal.group.Group;
|
import org.qortal.group.Group;
|
||||||
import org.qortal.repository.DataException;
|
import org.qortal.repository.DataException;
|
||||||
|
import org.qortal.utils.NTP;
|
||||||
|
|
||||||
public abstract class TestTransaction {
|
public abstract class TestTransaction {
|
||||||
|
|
||||||
protected static final Random random = new Random();
|
protected static final Random random = new Random();
|
||||||
|
|
||||||
public static BaseTransactionData generateBase(PrivateKeyAccount account, int txGroupId) throws DataException {
|
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 {
|
public static BaseTransactionData generateBase(PrivateKeyAccount account) throws DataException {
|
||||||
|
790
src/test/java/org/qortal/test/crosschain/ACCTTests.java
Normal file
790
src/test/java/org/qortal/test/crosschain/ACCTTests.java
Normal file
@ -0,0 +1,790 @@
|
|||||||
|
package org.qortal.test.crosschain;
|
||||||
|
|
||||||
|
import com.google.common.hash.HashCode;
|
||||||
|
import com.google.common.primitives.Bytes;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.qortal.account.Account;
|
||||||
|
import org.qortal.account.PrivateKeyAccount;
|
||||||
|
import org.qortal.asset.Asset;
|
||||||
|
import org.qortal.block.Block;
|
||||||
|
import org.qortal.crosschain.ACCT;
|
||||||
|
import org.qortal.crosschain.AcctMode;
|
||||||
|
import org.qortal.crypto.Crypto;
|
||||||
|
import org.qortal.data.at.ATData;
|
||||||
|
import org.qortal.data.at.ATStateData;
|
||||||
|
import org.qortal.data.crosschain.CrossChainTradeData;
|
||||||
|
import org.qortal.data.transaction.BaseTransactionData;
|
||||||
|
import org.qortal.data.transaction.DeployAtTransactionData;
|
||||||
|
import org.qortal.data.transaction.MessageTransactionData;
|
||||||
|
import org.qortal.data.transaction.TransactionData;
|
||||||
|
import org.qortal.group.Group;
|
||||||
|
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.TransactionUtils;
|
||||||
|
import org.qortal.transaction.DeployAtTransaction;
|
||||||
|
import org.qortal.transaction.MessageTransaction;
|
||||||
|
import org.qortal.utils.Amounts;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.format.FormatStyle;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public abstract class ACCTTests extends Common {
|
||||||
|
|
||||||
|
public static final byte[] secretA = "This string is exactly 32 bytes!".getBytes();
|
||||||
|
public static final byte[] hashOfSecretA = Crypto.hash160(secretA); // daf59884b4d1aec8c1b17102530909ee43c0151a
|
||||||
|
public static final int tradeTimeout = 20; // blocks
|
||||||
|
public static final long redeemAmount = 80_40200000L;
|
||||||
|
public static final long fundingAmount = 123_45600000L;
|
||||||
|
public static final long foreignAmount = 864200L; // 0.00864200 foreign units
|
||||||
|
|
||||||
|
protected static final Random RANDOM = new Random();
|
||||||
|
|
||||||
|
protected abstract byte[] getPublicKey();
|
||||||
|
|
||||||
|
protected abstract byte[] buildQortalAT(String address, byte[] publicKey, long redeemAmount, long foreignAmount, int tradeTimeout);
|
||||||
|
|
||||||
|
protected abstract ACCT getInstance();
|
||||||
|
|
||||||
|
protected abstract int calcRefundTimeout(long partnersOfferMessageTransactionTimestamp, int lockTimeA);
|
||||||
|
|
||||||
|
protected abstract byte[] buildTradeMessage(String address, byte[] publicKey, byte[] hashOfSecretA, int lockTimeA, int refundTimeout);
|
||||||
|
|
||||||
|
protected abstract byte[] buildRedeemMessage(byte[] secretA, String address);
|
||||||
|
|
||||||
|
protected abstract byte[] getCodeBytesHash();
|
||||||
|
|
||||||
|
protected abstract String getSymbol();
|
||||||
|
|
||||||
|
protected abstract String getName();
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void beforeTest() throws DataException {
|
||||||
|
Common.useDefaultSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCompile() {
|
||||||
|
PrivateKeyAccount tradeAccount = createTradeAccount(null);
|
||||||
|
|
||||||
|
byte[] creationBytes = buildQortalAT(tradeAccount.getAddress(), getPublicKey(), redeemAmount, foreignAmount, tradeTimeout);
|
||||||
|
assertNotNull(creationBytes);
|
||||||
|
|
||||||
|
System.out.println("AT creation bytes: " + HashCode.fromBytes(creationBytes).toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDeploy() throws DataException {
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||||
|
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||||
|
|
||||||
|
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||||
|
|
||||||
|
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||||
|
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||||
|
|
||||||
|
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||||
|
|
||||||
|
long expectedBalance = deployersInitialBalance - fundingAmount - deployAtTransaction.getTransactionData().getFee();
|
||||||
|
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||||
|
|
||||||
|
assertEquals("Deployer's post-deployment balance incorrect", expectedBalance, actualBalance);
|
||||||
|
|
||||||
|
expectedBalance = fundingAmount;
|
||||||
|
actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT);
|
||||||
|
|
||||||
|
assertEquals("AT's post-deployment balance incorrect", expectedBalance, actualBalance);
|
||||||
|
|
||||||
|
expectedBalance = partnersInitialBalance;
|
||||||
|
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||||
|
|
||||||
|
assertEquals("Partner's post-deployment balance incorrect", expectedBalance, actualBalance);
|
||||||
|
|
||||||
|
// Test orphaning
|
||||||
|
BlockUtils.orphanLastBlock(repository);
|
||||||
|
|
||||||
|
expectedBalance = deployersInitialBalance;
|
||||||
|
actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||||
|
|
||||||
|
assertEquals("Deployer's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
||||||
|
|
||||||
|
expectedBalance = 0;
|
||||||
|
actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT);
|
||||||
|
|
||||||
|
assertEquals("AT's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
||||||
|
|
||||||
|
expectedBalance = partnersInitialBalance;
|
||||||
|
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||||
|
|
||||||
|
assertEquals("Partner's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@Test
|
||||||
|
public void testOfferCancel() throws DataException {
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||||
|
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||||
|
|
||||||
|
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||||
|
|
||||||
|
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||||
|
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||||
|
|
||||||
|
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||||
|
Account at = deployAtTransaction.getATAccount();
|
||||||
|
String atAddress = at.getAddress();
|
||||||
|
|
||||||
|
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
||||||
|
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
||||||
|
|
||||||
|
// Send creator's address to AT, instead of typical partner's address
|
||||||
|
byte[] messageData = getInstance().buildCancelMessage(deployer.getAddress());
|
||||||
|
MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress);
|
||||||
|
long messageFee = messageTransaction.getTransactionData().getFee();
|
||||||
|
|
||||||
|
// AT should process 'cancel' message in next block
|
||||||
|
BlockUtils.mintBlock(repository);
|
||||||
|
|
||||||
|
describeAt(repository, atAddress);
|
||||||
|
|
||||||
|
// Check AT is finished
|
||||||
|
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||||
|
assertTrue(atData.getIsFinished());
|
||||||
|
|
||||||
|
// AT should be in CANCELLED mode
|
||||||
|
CrossChainTradeData tradeData = getInstance().populateTradeData(repository, atData);
|
||||||
|
assertEquals(AcctMode.CANCELLED, tradeData.mode);
|
||||||
|
|
||||||
|
// Check balances
|
||||||
|
long expectedMinimumBalance = deployersPostDeploymentBalance;
|
||||||
|
long expectedMaximumBalance = deployersInitialBalance - deployAtFee - messageFee;
|
||||||
|
|
||||||
|
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||||
|
|
||||||
|
assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance);
|
||||||
|
assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance);
|
||||||
|
|
||||||
|
// Test orphaning
|
||||||
|
BlockUtils.orphanLastBlock(repository);
|
||||||
|
|
||||||
|
// Check balances
|
||||||
|
long expectedBalance = deployersPostDeploymentBalance - messageFee;
|
||||||
|
actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||||
|
|
||||||
|
assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@Test
|
||||||
|
public void testOfferCancelInvalidLength() throws DataException {
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||||
|
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||||
|
|
||||||
|
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||||
|
|
||||||
|
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||||
|
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||||
|
|
||||||
|
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||||
|
Account at = deployAtTransaction.getATAccount();
|
||||||
|
String atAddress = at.getAddress();
|
||||||
|
|
||||||
|
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
||||||
|
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
||||||
|
|
||||||
|
// Instead of sending creator's address to AT, send too-short/invalid message
|
||||||
|
byte[] messageData = new byte[7];
|
||||||
|
RANDOM.nextBytes(messageData);
|
||||||
|
MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress);
|
||||||
|
long messageFee = messageTransaction.getTransactionData().getFee();
|
||||||
|
|
||||||
|
// AT should process 'cancel' message in next block
|
||||||
|
// As message is too short, it will be padded to 32bytes but cancel code doesn't care about message content, so should be ok
|
||||||
|
BlockUtils.mintBlock(repository);
|
||||||
|
|
||||||
|
describeAt(repository, atAddress);
|
||||||
|
|
||||||
|
// Check AT is finished
|
||||||
|
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||||
|
assertTrue(atData.getIsFinished());
|
||||||
|
|
||||||
|
// AT should be in CANCELLED mode
|
||||||
|
CrossChainTradeData tradeData = getInstance().populateTradeData(repository, atData);
|
||||||
|
assertEquals(AcctMode.CANCELLED, tradeData.mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@Test
|
||||||
|
public void testTradingInfoProcessing() throws DataException {
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||||
|
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||||
|
|
||||||
|
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||||
|
|
||||||
|
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||||
|
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||||
|
|
||||||
|
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||||
|
Account at = deployAtTransaction.getATAccount();
|
||||||
|
String atAddress = at.getAddress();
|
||||||
|
|
||||||
|
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
||||||
|
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
||||||
|
int refundTimeout = calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||||
|
|
||||||
|
// Send trade info to AT
|
||||||
|
byte[] messageData = buildTradeMessage(partner.getAddress(), getPublicKey(), hashOfSecretA, lockTimeA, refundTimeout);
|
||||||
|
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
||||||
|
|
||||||
|
Block postDeploymentBlock = BlockUtils.mintBlock(repository);
|
||||||
|
int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight();
|
||||||
|
|
||||||
|
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
||||||
|
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
||||||
|
|
||||||
|
describeAt(repository, atAddress);
|
||||||
|
|
||||||
|
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||||
|
CrossChainTradeData tradeData = getInstance().populateTradeData(repository, atData);
|
||||||
|
|
||||||
|
// AT should be in TRADE mode
|
||||||
|
assertEquals(AcctMode.TRADING, tradeData.mode);
|
||||||
|
|
||||||
|
// Check hashOfSecretA was extracted correctly
|
||||||
|
assertTrue(Arrays.equals(hashOfSecretA, tradeData.hashOfSecretA));
|
||||||
|
|
||||||
|
// Check trade partner Qortal address was extracted correctly
|
||||||
|
assertEquals(partner.getAddress(), tradeData.qortalPartnerAddress);
|
||||||
|
|
||||||
|
// Check trade partner's Foreign Coin PKH was extracted correctly
|
||||||
|
assertTrue(Arrays.equals(getPublicKey(), tradeData.partnerForeignPKH));
|
||||||
|
|
||||||
|
// Test orphaning
|
||||||
|
BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight);
|
||||||
|
|
||||||
|
// Check balances
|
||||||
|
long expectedBalance = deployersPostDeploymentBalance;
|
||||||
|
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||||
|
|
||||||
|
assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TEST SENDING TRADING INFO BUT NOT FROM AT CREATOR (SHOULD BE IGNORED)
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@Test
|
||||||
|
public void testIncorrectTradeSender() throws DataException {
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||||
|
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||||
|
|
||||||
|
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||||
|
|
||||||
|
PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob");
|
||||||
|
|
||||||
|
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||||
|
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||||
|
|
||||||
|
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||||
|
Account at = deployAtTransaction.getATAccount();
|
||||||
|
String atAddress = at.getAddress();
|
||||||
|
|
||||||
|
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
||||||
|
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
||||||
|
int refundTimeout = calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||||
|
|
||||||
|
// Send trade info to AT BUT NOT FROM AT CREATOR
|
||||||
|
byte[] messageData = buildTradeMessage(partner.getAddress(), getPublicKey(), hashOfSecretA, lockTimeA, refundTimeout);
|
||||||
|
MessageTransaction messageTransaction = sendMessage(repository, bystander, messageData, atAddress);
|
||||||
|
|
||||||
|
BlockUtils.mintBlock(repository);
|
||||||
|
|
||||||
|
long expectedBalance = partnersInitialBalance;
|
||||||
|
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||||
|
|
||||||
|
assertEquals("Partner's post-initial-payout balance incorrect", expectedBalance, actualBalance);
|
||||||
|
|
||||||
|
describeAt(repository, atAddress);
|
||||||
|
|
||||||
|
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||||
|
CrossChainTradeData tradeData = getInstance().populateTradeData(repository, atData);
|
||||||
|
|
||||||
|
// AT should still be in OFFER mode
|
||||||
|
assertEquals(AcctMode.OFFERING, tradeData.mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@Test
|
||||||
|
public void testAutomaticTradeRefund() throws DataException {
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||||
|
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||||
|
|
||||||
|
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||||
|
|
||||||
|
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||||
|
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||||
|
|
||||||
|
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||||
|
Account at = deployAtTransaction.getATAccount();
|
||||||
|
String atAddress = at.getAddress();
|
||||||
|
|
||||||
|
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
||||||
|
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
||||||
|
int refundTimeout = calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||||
|
|
||||||
|
// Send trade info to AT
|
||||||
|
byte[] messageData = buildTradeMessage(partner.getAddress(), getPublicKey(), hashOfSecretA, lockTimeA, refundTimeout);
|
||||||
|
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
||||||
|
|
||||||
|
Block postDeploymentBlock = BlockUtils.mintBlock(repository);
|
||||||
|
int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight();
|
||||||
|
|
||||||
|
// Check refund
|
||||||
|
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
||||||
|
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
||||||
|
|
||||||
|
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
||||||
|
|
||||||
|
describeAt(repository, atAddress);
|
||||||
|
|
||||||
|
// Check AT is finished
|
||||||
|
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||||
|
assertTrue(atData.getIsFinished());
|
||||||
|
|
||||||
|
// AT should be in REFUNDED mode
|
||||||
|
CrossChainTradeData tradeData = getInstance().populateTradeData(repository, atData);
|
||||||
|
assertEquals(AcctMode.REFUNDED, tradeData.mode);
|
||||||
|
|
||||||
|
// Test orphaning
|
||||||
|
BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight);
|
||||||
|
|
||||||
|
// Check balances
|
||||||
|
long expectedBalance = deployersPostDeploymentBalance;
|
||||||
|
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||||
|
|
||||||
|
assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@Test
|
||||||
|
public void testCorrectSecretCorrectSender() throws DataException {
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||||
|
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||||
|
|
||||||
|
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||||
|
|
||||||
|
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||||
|
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||||
|
|
||||||
|
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||||
|
Account at = deployAtTransaction.getATAccount();
|
||||||
|
String atAddress = at.getAddress();
|
||||||
|
|
||||||
|
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
||||||
|
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
||||||
|
int refundTimeout = calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||||
|
|
||||||
|
// Send trade info to AT
|
||||||
|
byte[] messageData = buildTradeMessage(partner.getAddress(), getPublicKey(), hashOfSecretA, lockTimeA, refundTimeout);
|
||||||
|
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
||||||
|
|
||||||
|
// Give AT time to process message
|
||||||
|
BlockUtils.mintBlock(repository);
|
||||||
|
|
||||||
|
// Send correct secret to AT, from correct account
|
||||||
|
messageData = buildRedeemMessage(secretA, partner.getAddress());
|
||||||
|
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
||||||
|
|
||||||
|
// AT should send funds in the next block
|
||||||
|
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
||||||
|
BlockUtils.mintBlock(repository);
|
||||||
|
|
||||||
|
describeAt(repository, atAddress);
|
||||||
|
|
||||||
|
// Check AT is finished
|
||||||
|
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||||
|
assertTrue(atData.getIsFinished());
|
||||||
|
|
||||||
|
// AT should be in REDEEMED mode
|
||||||
|
CrossChainTradeData tradeData = getInstance().populateTradeData(repository, atData);
|
||||||
|
assertEquals(AcctMode.REDEEMED, tradeData.mode);
|
||||||
|
|
||||||
|
// Check balances
|
||||||
|
long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee() + redeemAmount;
|
||||||
|
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||||
|
|
||||||
|
assertEquals("Partner's post-redeem balance incorrect", expectedBalance, actualBalance);
|
||||||
|
|
||||||
|
// Orphan redeem
|
||||||
|
BlockUtils.orphanLastBlock(repository);
|
||||||
|
|
||||||
|
// Check balances
|
||||||
|
expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee();
|
||||||
|
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||||
|
|
||||||
|
assertEquals("Partner's post-orphan/pre-redeem balance incorrect", expectedBalance, actualBalance);
|
||||||
|
|
||||||
|
// Check AT state
|
||||||
|
ATStateData postOrphanAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
||||||
|
|
||||||
|
assertTrue("AT states mismatch", Arrays.equals(preRedeemAtStateData.getStateData(), postOrphanAtStateData.getStateData()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@Test
|
||||||
|
public void testCorrectSecretIncorrectSender() throws DataException {
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||||
|
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||||
|
|
||||||
|
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||||
|
|
||||||
|
PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob");
|
||||||
|
|
||||||
|
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||||
|
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||||
|
|
||||||
|
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||||
|
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
||||||
|
|
||||||
|
Account at = deployAtTransaction.getATAccount();
|
||||||
|
String atAddress = at.getAddress();
|
||||||
|
|
||||||
|
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
||||||
|
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
||||||
|
int refundTimeout = calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||||
|
|
||||||
|
// Send trade info to AT
|
||||||
|
byte[] messageData = buildTradeMessage(partner.getAddress(), getPublicKey(), hashOfSecretA, lockTimeA, refundTimeout);
|
||||||
|
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
||||||
|
|
||||||
|
// Give AT time to process message
|
||||||
|
BlockUtils.mintBlock(repository);
|
||||||
|
|
||||||
|
// Send correct secret to AT, but from wrong account
|
||||||
|
messageData = buildRedeemMessage(secretA, partner.getAddress());
|
||||||
|
messageTransaction = sendMessage(repository, bystander, messageData, atAddress);
|
||||||
|
|
||||||
|
// AT should NOT send funds in the next block
|
||||||
|
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
||||||
|
BlockUtils.mintBlock(repository);
|
||||||
|
|
||||||
|
describeAt(repository, atAddress);
|
||||||
|
|
||||||
|
// Check AT is NOT finished
|
||||||
|
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||||
|
assertFalse(atData.getIsFinished());
|
||||||
|
|
||||||
|
// AT should still be in TRADE mode
|
||||||
|
CrossChainTradeData tradeData = getInstance().populateTradeData(repository, atData);
|
||||||
|
assertEquals(AcctMode.TRADING, tradeData.mode);
|
||||||
|
|
||||||
|
// Check balances
|
||||||
|
long expectedBalance = partnersInitialBalance;
|
||||||
|
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||||
|
|
||||||
|
assertEquals("Partner's balance incorrect", expectedBalance, actualBalance);
|
||||||
|
|
||||||
|
// Check eventual refund
|
||||||
|
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@Test
|
||||||
|
public void testIncorrectSecretCorrectSender() throws DataException {
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||||
|
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||||
|
|
||||||
|
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||||
|
|
||||||
|
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||||
|
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||||
|
|
||||||
|
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||||
|
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
||||||
|
|
||||||
|
Account at = deployAtTransaction.getATAccount();
|
||||||
|
String atAddress = at.getAddress();
|
||||||
|
|
||||||
|
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
||||||
|
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
||||||
|
int refundTimeout = calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||||
|
|
||||||
|
// Send trade info to AT
|
||||||
|
byte[] messageData = buildTradeMessage(partner.getAddress(), getPublicKey(), hashOfSecretA, lockTimeA, refundTimeout);
|
||||||
|
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
||||||
|
|
||||||
|
// Give AT time to process message
|
||||||
|
BlockUtils.mintBlock(repository);
|
||||||
|
|
||||||
|
// Send incorrect secret to AT, from correct account
|
||||||
|
byte[] wrongSecret = new byte[32];
|
||||||
|
RANDOM.nextBytes(wrongSecret);
|
||||||
|
messageData = buildRedeemMessage(wrongSecret, partner.getAddress());
|
||||||
|
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
||||||
|
|
||||||
|
// AT should NOT send funds in the next block
|
||||||
|
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
||||||
|
BlockUtils.mintBlock(repository);
|
||||||
|
|
||||||
|
describeAt(repository, atAddress);
|
||||||
|
|
||||||
|
// Check AT is NOT finished
|
||||||
|
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||||
|
assertFalse(atData.getIsFinished());
|
||||||
|
|
||||||
|
// AT should still be in TRADE mode
|
||||||
|
CrossChainTradeData tradeData = getInstance().populateTradeData(repository, atData);
|
||||||
|
assertEquals(AcctMode.TRADING, tradeData.mode);
|
||||||
|
|
||||||
|
long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee();
|
||||||
|
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||||
|
|
||||||
|
assertEquals("Partner's balance incorrect", expectedBalance, actualBalance);
|
||||||
|
|
||||||
|
// Check eventual refund
|
||||||
|
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@Test
|
||||||
|
public void testCorrectSecretCorrectSenderInvalidMessageLength() throws DataException {
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||||
|
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||||
|
|
||||||
|
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||||
|
|
||||||
|
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||||
|
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||||
|
|
||||||
|
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||||
|
Account at = deployAtTransaction.getATAccount();
|
||||||
|
String atAddress = at.getAddress();
|
||||||
|
|
||||||
|
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
||||||
|
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
||||||
|
int refundTimeout = calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||||
|
|
||||||
|
// Send trade info to AT
|
||||||
|
byte[] messageData = buildTradeMessage(partner.getAddress(), getPublicKey(), hashOfSecretA, lockTimeA, refundTimeout);
|
||||||
|
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
||||||
|
|
||||||
|
// Give AT time to process message
|
||||||
|
BlockUtils.mintBlock(repository);
|
||||||
|
|
||||||
|
// Send correct secret to AT, from correct account, but missing receive address, hence incorrect length
|
||||||
|
messageData = Bytes.concat(secretA);
|
||||||
|
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
||||||
|
|
||||||
|
// AT should NOT send funds in the next block
|
||||||
|
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
||||||
|
BlockUtils.mintBlock(repository);
|
||||||
|
|
||||||
|
describeAt(repository, atAddress);
|
||||||
|
|
||||||
|
// Check AT is NOT finished
|
||||||
|
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||||
|
assertFalse(atData.getIsFinished());
|
||||||
|
|
||||||
|
// AT should be in TRADING mode
|
||||||
|
CrossChainTradeData tradeData = getInstance().populateTradeData(repository, atData);
|
||||||
|
assertEquals(AcctMode.TRADING, tradeData.mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
@Test
|
||||||
|
public void testDescribeDeployed() throws DataException {
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||||
|
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||||
|
|
||||||
|
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
||||||
|
|
||||||
|
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||||
|
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||||
|
|
||||||
|
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
||||||
|
|
||||||
|
List<ATData> executableAts = repository.getATRepository().getAllExecutableATs();
|
||||||
|
|
||||||
|
for (ATData atData : executableAts) {
|
||||||
|
String atAddress = atData.getATAddress();
|
||||||
|
byte[] codeBytes = atData.getCodeBytes();
|
||||||
|
byte[] codeHash = Crypto.digest(codeBytes);
|
||||||
|
|
||||||
|
System.out.println(String.format("%s: code length: %d byte%s, code hash: %s",
|
||||||
|
atAddress,
|
||||||
|
codeBytes.length,
|
||||||
|
(codeBytes.length != 1 ? "s": ""),
|
||||||
|
HashCode.fromBytes(codeHash)));
|
||||||
|
|
||||||
|
// Not one of ours?
|
||||||
|
if (!Arrays.equals(codeHash, getCodeBytesHash()))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
describeAt(repository, atAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int calcTestLockTimeA(long messageTimestamp) {
|
||||||
|
return (int) (messageTimestamp / 1000L + tradeTimeout * 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer, String tradeAddress) throws DataException {
|
||||||
|
byte[] creationBytes = buildQortalAT(tradeAddress, getPublicKey(), redeemAmount, foreignAmount, tradeTimeout);
|
||||||
|
|
||||||
|
long txTimestamp = System.currentTimeMillis();
|
||||||
|
byte[] lastReference = deployer.getLastReference();
|
||||||
|
|
||||||
|
if (lastReference == null) {
|
||||||
|
System.err.println(String.format("Qortal account %s has no last reference", deployer.getAddress()));
|
||||||
|
System.exit(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Long fee = null;
|
||||||
|
String name = "QORT-" + getSymbol() + " cross-chain trade";
|
||||||
|
String description = String.format("Qortal-" + getName() + " cross-chain trade");
|
||||||
|
String atType = "ACCT";
|
||||||
|
String tags = "QORT-" + getSymbol() + " ACCT";
|
||||||
|
|
||||||
|
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, deployer.getPublicKey(), fee, null);
|
||||||
|
TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, atType, tags, creationBytes, fundingAmount, Asset.QORT);
|
||||||
|
|
||||||
|
DeployAtTransaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData);
|
||||||
|
|
||||||
|
fee = deployAtTransaction.calcRecommendedFee();
|
||||||
|
deployAtTransactionData.setFee(fee);
|
||||||
|
|
||||||
|
TransactionUtils.signAndMint(repository, deployAtTransactionData, deployer);
|
||||||
|
|
||||||
|
return deployAtTransaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected MessageTransaction sendMessage(Repository repository, PrivateKeyAccount sender, byte[] data, String recipient) throws DataException {
|
||||||
|
long txTimestamp = System.currentTimeMillis();
|
||||||
|
byte[] lastReference = sender.getLastReference();
|
||||||
|
|
||||||
|
if (lastReference == null) {
|
||||||
|
System.err.println(String.format("Qortal account %s has no last reference", sender.getAddress()));
|
||||||
|
System.exit(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
Long fee = null;
|
||||||
|
int version = 4;
|
||||||
|
int nonce = 0;
|
||||||
|
long amount = 0;
|
||||||
|
Long assetId = null; // because amount is zero
|
||||||
|
|
||||||
|
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, sender.getPublicKey(), fee, null);
|
||||||
|
TransactionData messageTransactionData = new MessageTransactionData(baseTransactionData, version, nonce, recipient, amount, assetId, data, false, false);
|
||||||
|
|
||||||
|
MessageTransaction messageTransaction = new MessageTransaction(repository, messageTransactionData);
|
||||||
|
|
||||||
|
fee = messageTransaction.calcRecommendedFee();
|
||||||
|
messageTransactionData.setFee(fee);
|
||||||
|
|
||||||
|
TransactionUtils.signAndMint(repository, messageTransactionData, sender);
|
||||||
|
|
||||||
|
return messageTransaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void checkTradeRefund(Repository repository, Account deployer, long deployersInitialBalance, long deployAtFee) throws DataException {
|
||||||
|
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
||||||
|
int refundTimeout = tradeTimeout / 2 + 1; // close enough
|
||||||
|
|
||||||
|
// AT should automatically refund deployer after 'refundTimeout' blocks
|
||||||
|
for (int blockCount = 0; blockCount <= refundTimeout; ++blockCount)
|
||||||
|
BlockUtils.mintBlock(repository);
|
||||||
|
|
||||||
|
// We don't bother to exactly calculate QORT spent running AT for several blocks, but we do know the expected range
|
||||||
|
long expectedMinimumBalance = deployersPostDeploymentBalance;
|
||||||
|
long expectedMaximumBalance = deployersInitialBalance - deployAtFee;
|
||||||
|
|
||||||
|
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||||
|
|
||||||
|
assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance);
|
||||||
|
assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void describeAt(Repository repository, String atAddress) throws DataException {
|
||||||
|
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
||||||
|
CrossChainTradeData tradeData = getInstance().populateTradeData(repository, atData);
|
||||||
|
|
||||||
|
Function<Long, String> epochMilliFormatter = (timestamp) -> LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneOffset.UTC).format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM));
|
||||||
|
int currentBlockHeight = repository.getBlockRepository().getBlockchainHeight();
|
||||||
|
|
||||||
|
System.out.print(String.format("%s:\n"
|
||||||
|
+ "\tmode: %s\n"
|
||||||
|
+ "\tcreator: %s,\n"
|
||||||
|
+ "\tcreation timestamp: %s,\n"
|
||||||
|
+ "\tcurrent balance: %s QORT,\n"
|
||||||
|
+ "\tis finished: %b,\n"
|
||||||
|
+ "\tredeem payout: %s QORT,\n"
|
||||||
|
+ "\texpected " + getName() + ": %s " + getSymbol() + ",\n"
|
||||||
|
+ "\tcurrent block height: %d,\n",
|
||||||
|
tradeData.qortalAtAddress,
|
||||||
|
tradeData.mode,
|
||||||
|
tradeData.qortalCreator,
|
||||||
|
epochMilliFormatter.apply(tradeData.creationTimestamp),
|
||||||
|
Amounts.prettyAmount(tradeData.qortBalance),
|
||||||
|
atData.getIsFinished(),
|
||||||
|
Amounts.prettyAmount(tradeData.qortAmount),
|
||||||
|
Amounts.prettyAmount(tradeData.expectedForeignAmount),
|
||||||
|
currentBlockHeight));
|
||||||
|
|
||||||
|
describeRefundAt(tradeData, epochMilliFormatter);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void describeRefundAt(CrossChainTradeData tradeData, Function<Long, String> epochMilliFormatter) {
|
||||||
|
if (tradeData.mode != AcctMode.OFFERING && tradeData.mode != AcctMode.CANCELLED) {
|
||||||
|
System.out.println(String.format("\trefund timeout: %d minutes,\n"
|
||||||
|
+ "\trefund height: block %d,\n"
|
||||||
|
+ "\tHASH160 of secret-A: %s,\n"
|
||||||
|
+ "\t" + getName() + " P2SH-A nLockTime: %d (%s),\n"
|
||||||
|
+ "\ttrade partner: %s\n"
|
||||||
|
+ "\tpartner's receiving address: %s",
|
||||||
|
tradeData.refundTimeout,
|
||||||
|
tradeData.tradeRefundHeight,
|
||||||
|
HashCode.fromBytes(tradeData.hashOfSecretA).toString().substring(0, 40),
|
||||||
|
tradeData.lockTimeA, epochMilliFormatter.apply(tradeData.lockTimeA * 1000L),
|
||||||
|
tradeData.qortalPartnerAddress,
|
||||||
|
tradeData.qortalPartnerReceivingAddress));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected PrivateKeyAccount createTradeAccount(Repository repository) {
|
||||||
|
// We actually use a known test account with funds to avoid PoW compute
|
||||||
|
return Common.getTestAccount(repository, "alice");
|
||||||
|
}
|
||||||
|
}
|
@ -1,121 +1,59 @@
|
|||||||
package org.qortal.test.crosschain;
|
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.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.qortal.crosschain.Bitcoin;
|
import org.qortal.crosschain.Bitcoin;
|
||||||
import org.qortal.crosschain.ForeignBlockchainException;
|
import org.qortal.crosschain.Bitcoiny;
|
||||||
import org.qortal.crosschain.BitcoinyHTLC;
|
|
||||||
import org.qortal.repository.DataException;
|
|
||||||
import org.qortal.test.common.Common;
|
|
||||||
|
|
||||||
public class BitcoinTests extends Common {
|
public class BitcoinTests extends BitcoinyTests {
|
||||||
|
|
||||||
private Bitcoin bitcoin;
|
@Override
|
||||||
|
protected String getCoinName() {
|
||||||
@Before
|
return "Bitcoin";
|
||||||
public void beforeTest() throws DataException {
|
|
||||||
Common.useDefaultSettings(); // TestNet3
|
|
||||||
bitcoin = Bitcoin.getInstance();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@Override
|
||||||
public void afterTest() {
|
protected String getCoinSymbol() {
|
||||||
|
return "BTC";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Bitcoiny getCoin() {
|
||||||
|
return Bitcoin.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void resetCoinForTesting() {
|
||||||
Bitcoin.resetForTesting();
|
Bitcoin.resetForTesting();
|
||||||
bitcoin = null;
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getDeterministicKey58() {
|
||||||
|
return "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getRecipient() {
|
||||||
|
return "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore("Often fails due to unreliable BTC testnet ElectrumX servers")
|
@Ignore("Often fails due to unreliable BTC testnet ElectrumX servers")
|
||||||
public void testGetMedianBlockTime() throws BlockStoreException, ForeignBlockchainException {
|
public void testGetMedianBlockTime() {}
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore("Often fails due to unreliable BTC testnet ElectrumX servers")
|
@Ignore("Often fails due to unreliable BTC testnet ElectrumX servers")
|
||||||
public void testFindHtlcSecret() throws ForeignBlockchainException {
|
public void testFindHtlcSecret() {}
|
||||||
// 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));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore("Often fails due to unreliable BTC testnet ElectrumX servers")
|
@Ignore("Often fails due to unreliable BTC testnet ElectrumX servers")
|
||||||
public void testBuildSpend() {
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore("Often fails due to unreliable BTC testnet ElectrumX servers")
|
@Ignore("Often fails due to unreliable BTC testnet ElectrumX servers")
|
||||||
public void testGetWalletBalance() throws ForeignBlockchainException {
|
public void testGetWalletBalance() {}
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore("Often fails due to unreliable BTC testnet ElectrumX servers")
|
@Ignore("Often fails due to unreliable BTC testnet ElectrumX servers")
|
||||||
public void testGetUnusedReceiveAddress() throws ForeignBlockchainException {
|
public void testGetUnusedReceiveAddress() {}
|
||||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
|
||||||
|
|
||||||
String address = bitcoin.getUnusedReceiveAddress(xprv58);
|
|
||||||
|
|
||||||
assertNotNull(address);
|
|
||||||
|
|
||||||
System.out.println(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
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;
|
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.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.qortal.crosschain.ForeignBlockchainException;
|
import org.qortal.crosschain.Bitcoiny;
|
||||||
import org.qortal.crosschain.Digibyte;
|
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;
|
@Override
|
||||||
|
protected String getCoinName() {
|
||||||
@Before
|
return "Digibyte";
|
||||||
public void beforeTest() throws DataException {
|
|
||||||
Common.useDefaultSettings(); // TestNet3
|
|
||||||
digibyte = Digibyte.getInstance();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@Override
|
||||||
public void afterTest() {
|
protected String getCoinSymbol() {
|
||||||
|
return "DGB";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Bitcoiny getCoin() {
|
||||||
|
return Digibyte.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void resetCoinForTesting() {
|
||||||
Digibyte.resetForTesting();
|
Digibyte.resetForTesting();
|
||||||
digibyte = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Override
|
||||||
public void testGetMedianBlockTime() throws BlockStoreException, ForeignBlockchainException {
|
protected String getDeterministicKey58() {
|
||||||
long before = System.currentTimeMillis();
|
return "xpub661MyMwAqRbcEnabTLX5uebYcsE3uG5y7ve9jn1VK8iY1MaU3YLoLJEe8sTu2YVav5Zka5qf2dmMssfxmXJTqZnazZL2kL7M2tNKwEoC34R";
|
||||||
System.out.println(String.format("Digibyte median blocktime: %d", digibyte.getMedianBlockTime()));
|
}
|
||||||
long afterFirst = System.currentTimeMillis();
|
|
||||||
|
|
||||||
System.out.println(String.format("Digibyte median blocktime: %d", digibyte.getMedianBlockTime()));
|
@Override
|
||||||
long afterSecond = System.currentTimeMillis();
|
protected String getRecipient() {
|
||||||
|
return "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore(value = "Doesn't work, to be fixed later")
|
@Ignore(value = "Doesn't work, to be fixed later")
|
||||||
public void testFindHtlcSecret() throws ForeignBlockchainException {
|
public void testFindHtlcSecret() {}
|
||||||
// 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));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore(value = "No testnet nodes available, so we can't regularly test buildSpend yet")
|
@Ignore(value = "No testnet nodes available, so we can't regularly test buildSpend yet")
|
||||||
public void testBuildSpend() {
|
public void testBuildSpend() {}
|
||||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
|
||||||
|
|
||||||
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;
|
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.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.qortal.crosschain.BitcoinyHTLC;
|
import org.qortal.crosschain.Bitcoiny;
|
||||||
import org.qortal.crosschain.ForeignBlockchainException;
|
|
||||||
import org.qortal.crosschain.Dogecoin;
|
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.*;
|
@Override
|
||||||
|
protected String getCoinName() {
|
||||||
public class DogecoinTests extends Common {
|
return "Dogecoin";
|
||||||
|
|
||||||
private Dogecoin dogecoin;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void beforeTest() throws DataException {
|
|
||||||
Common.useDefaultSettings(); // TestNet3
|
|
||||||
dogecoin = Dogecoin.getInstance();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@Override
|
||||||
public void afterTest() {
|
protected String getCoinSymbol() {
|
||||||
|
return "DOGE";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Bitcoiny getCoin() {
|
||||||
|
return Dogecoin.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void resetCoinForTesting() {
|
||||||
Dogecoin.resetForTesting();
|
Dogecoin.resetForTesting();
|
||||||
dogecoin = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Override
|
||||||
public void testGetMedianBlockTime() throws BlockStoreException, ForeignBlockchainException {
|
protected String getDeterministicKey58() {
|
||||||
long before = System.currentTimeMillis();
|
return "dgpv51eADS3spNJh9drNeW1Tc1P9z2LyaQRXPBortsq6yice1k47C2u2Prvgxycr2ihNBWzKZ2LthcBBGiYkWZ69KUTVkcLVbnjq7pD8mnApEru";
|
||||||
System.out.println(String.format("Dogecoin median blocktime: %d", dogecoin.getMedianBlockTime()));
|
}
|
||||||
long afterFirst = System.currentTimeMillis();
|
|
||||||
|
|
||||||
System.out.println(String.format("Dogecoin median blocktime: %d", dogecoin.getMedianBlockTime()));
|
@Override
|
||||||
long afterSecond = System.currentTimeMillis();
|
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("2nd call should be quicker than 1st", secondPeriod < firstPeriod);
|
|
||||||
assertTrue("2nd call should take less than 5 seconds", secondPeriod < 5000L);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore(value = "Doesn't work, to be fixed later")
|
@Ignore(value = "Doesn't work, to be fixed later")
|
||||||
public void testFindHtlcSecret() throws ForeignBlockchainException {
|
public void testFindHtlcSecret() {}
|
||||||
// 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));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore(value = "No testnet nodes available, so we can't regularly test buildSpend yet")
|
@Ignore(value = "No testnet nodes available, so we can't regularly test buildSpend yet")
|
||||||
public void testBuildSpend() {
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,113 +1,43 @@
|
|||||||
package org.qortal.test.crosschain;
|
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.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.qortal.crosschain.ForeignBlockchainException;
|
import org.qortal.crosschain.Bitcoiny;
|
||||||
import org.qortal.crosschain.Litecoin;
|
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;
|
@Override
|
||||||
|
protected String getCoinName() {
|
||||||
@Before
|
return "Litecoin";
|
||||||
public void beforeTest() throws DataException {
|
|
||||||
Common.useDefaultSettings(); // TestNet3
|
|
||||||
litecoin = Litecoin.getInstance();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@Override
|
||||||
public void afterTest() {
|
protected String getCoinSymbol() {
|
||||||
|
return "LTC";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Bitcoiny getCoin() {
|
||||||
|
return Litecoin.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void resetCoinForTesting() {
|
||||||
Litecoin.resetForTesting();
|
Litecoin.resetForTesting();
|
||||||
litecoin = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Override
|
||||||
public void testGetMedianBlockTime() throws ForeignBlockchainException {
|
protected String getDeterministicKey58() {
|
||||||
long before = System.currentTimeMillis();
|
return "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
||||||
System.out.println(String.format("Litecoin median blocktime: %d", litecoin.getMedianBlockTime()));
|
}
|
||||||
long afterFirst = System.currentTimeMillis();
|
|
||||||
|
|
||||||
System.out.println(String.format("Litecoin median blocktime: %d", litecoin.getMedianBlockTime()));
|
@Override
|
||||||
long afterSecond = System.currentTimeMillis();
|
protected String getRecipient() {
|
||||||
|
return "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE";
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore(value = "Doesn't work, to be fixed later")
|
@Ignore(value = "Doesn't work, to be fixed later")
|
||||||
public void testFindHtlcSecret() throws ForeignBlockchainException {
|
public void testFindHtlcSecret() {}
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,57 +3,53 @@ package org.qortal.test.crosschain;
|
|||||||
import cash.z.wallet.sdk.rpc.CompactFormats.*;
|
import cash.z.wallet.sdk.rpc.CompactFormats.*;
|
||||||
import com.google.common.hash.HashCode;
|
import com.google.common.hash.HashCode;
|
||||||
import com.google.common.primitives.Bytes;
|
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.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.qortal.controller.tradebot.TradeBot;
|
import org.qortal.controller.tradebot.TradeBot;
|
||||||
import org.qortal.crosschain.*;
|
import org.qortal.crosschain.*;
|
||||||
import org.qortal.crypto.Crypto;
|
import org.qortal.crypto.Crypto;
|
||||||
import org.qortal.repository.DataException;
|
|
||||||
import org.qortal.test.common.Common;
|
|
||||||
import org.qortal.transform.TransformationException;
|
import org.qortal.transform.TransformationException;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
import static org.qortal.crosschain.BitcoinyHTLC.Status.*;
|
import static org.qortal.crosschain.BitcoinyHTLC.Status.*;
|
||||||
|
|
||||||
public class PirateChainTests extends Common {
|
public class PirateChainTests extends BitcoinyTests {
|
||||||
|
|
||||||
private PirateChain pirateChain;
|
@Override
|
||||||
|
protected String getCoinName() {
|
||||||
@Before
|
return "PirateChain";
|
||||||
public void beforeTest() throws DataException {
|
|
||||||
Common.useDefaultSettings();
|
|
||||||
pirateChain = PirateChain.getInstance();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@Override
|
||||||
public void afterTest() {
|
protected String getCoinSymbol() {
|
||||||
|
return "ARRR";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Bitcoiny getCoin() {
|
||||||
|
return PirateChain.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void resetCoinForTesting() {
|
||||||
Litecoin.resetForTesting();
|
Litecoin.resetForTesting();
|
||||||
pirateChain = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Override
|
||||||
public void testGetMedianBlockTime() throws BlockStoreException, ForeignBlockchainException {
|
protected String getDeterministicKey58() {
|
||||||
long before = System.currentTimeMillis();
|
return null;
|
||||||
System.out.println(String.format("Pirate Chain median blocktime: %d", pirateChain.getMedianBlockTime()));
|
}
|
||||||
long afterFirst = System.currentTimeMillis();
|
|
||||||
|
|
||||||
System.out.println(String.format("Pirate Chain median blocktime: %d", pirateChain.getMedianBlockTime()));
|
@Override
|
||||||
long afterSecond = System.currentTimeMillis();
|
protected String getRecipient() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
long firstPeriod = afterFirst - before;
|
public void makeGetMedianBlockTimeAssertions(long firstPeriod, long secondPeriod) {
|
||||||
long secondPeriod = afterSecond - afterFirst;
|
assertTrue("1st call should take less than 5 seconds", firstPeriod < 5000L);
|
||||||
|
assertTrue("2nd call should take less than 5 seconds", secondPeriod < 5000L);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -62,7 +58,7 @@ public class PirateChainTests extends Common {
|
|||||||
int count = 20;
|
int count = 20;
|
||||||
|
|
||||||
long before = System.currentTimeMillis();
|
long before = System.currentTimeMillis();
|
||||||
List<CompactBlock> compactBlocks = pirateChain.getCompactBlocks(startHeight, count);
|
List<CompactBlock> compactBlocks = ((PirateChain) bitcoiny).getCompactBlocks(startHeight, count);
|
||||||
long after = System.currentTimeMillis();
|
long after = System.currentTimeMillis();
|
||||||
|
|
||||||
System.out.println(String.format("Retrieval took: %d ms", after-before));
|
System.out.println(String.format("Retrieval took: %d ms", after-before));
|
||||||
@ -82,7 +78,7 @@ public class PirateChainTests extends Common {
|
|||||||
Bytes.reverse(txBytes);
|
Bytes.reverse(txBytes);
|
||||||
String txHashBE = HashCode.fromBytes(txBytes).toString();
|
String txHashBE = HashCode.fromBytes(txBytes).toString();
|
||||||
|
|
||||||
byte[] rawTransaction = pirateChain.getBlockchainProvider().getRawTransaction(txHashBE);
|
byte[] rawTransaction = bitcoiny.getBlockchainProvider().getRawTransaction(txHashBE);
|
||||||
assertNotNull(rawTransaction);
|
assertNotNull(rawTransaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,7 +117,7 @@ public class PirateChainTests extends Common {
|
|||||||
String p2shAddress = "ba6Q5HWrWtmfU2WZqQbrFdRYsafA45cUAt";
|
String p2shAddress = "ba6Q5HWrWtmfU2WZqQbrFdRYsafA45cUAt";
|
||||||
long p2shFee = 10000;
|
long p2shFee = 10000;
|
||||||
final long minimumAmount = 10000 + p2shFee;
|
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);
|
assertEquals(FUNDED, htlcStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,7 +126,7 @@ public class PirateChainTests extends Common {
|
|||||||
String p2shAddress = "bYZrzSSgGp8aEGvihukoMGU8sXYrx19Wka";
|
String p2shAddress = "bYZrzSSgGp8aEGvihukoMGU8sXYrx19Wka";
|
||||||
long p2shFee = 10000;
|
long p2shFee = 10000;
|
||||||
final long minimumAmount = 10000 + p2shFee;
|
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);
|
assertEquals(REDEEMED, htlcStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,14 +135,14 @@ public class PirateChainTests extends Common {
|
|||||||
String p2shAddress = "bE49izfVxz8odhu8c2BcUaVFUnt7NLFRgv";
|
String p2shAddress = "bE49izfVxz8odhu8c2BcUaVFUnt7NLFRgv";
|
||||||
long p2shFee = 10000;
|
long p2shFee = 10000;
|
||||||
final long minimumAmount = 10000 + p2shFee;
|
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);
|
assertEquals(REFUNDED, htlcStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetTxidForUnspentAddress() throws ForeignBlockchainException {
|
public void testGetTxidForUnspentAddress() throws ForeignBlockchainException {
|
||||||
String p2shAddress = "ba6Q5HWrWtmfU2WZqQbrFdRYsafA45cUAt";
|
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
|
// Reverse the byte order of the txid used by block explorers, to get to big-endian form
|
||||||
byte[] expectedTxidLE = HashCode.fromString("fea4b0c1abcf8f0f3ddc2fa2f9438501ee102aad62a9ff18a5ce7d08774755c0").asBytes();
|
byte[] expectedTxidLE = HashCode.fromString("fea4b0c1abcf8f0f3ddc2fa2f9438501ee102aad62a9ff18a5ce7d08774755c0").asBytes();
|
||||||
@ -161,7 +157,7 @@ public class PirateChainTests extends Common {
|
|||||||
String p2shAddress = "ba6Q5HWrWtmfU2WZqQbrFdRYsafA45cUAt";
|
String p2shAddress = "ba6Q5HWrWtmfU2WZqQbrFdRYsafA45cUAt";
|
||||||
long p2shFee = 10000;
|
long p2shFee = 10000;
|
||||||
final long minimumAmount = 10000 + p2shFee;
|
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
|
// Reverse the byte order of the txid used by block explorers, to get to big-endian form
|
||||||
byte[] expectedTxidLE = HashCode.fromString("fea4b0c1abcf8f0f3ddc2fa2f9438501ee102aad62a9ff18a5ce7d08774755c0").asBytes();
|
byte[] expectedTxidLE = HashCode.fromString("fea4b0c1abcf8f0f3ddc2fa2f9438501ee102aad62a9ff18a5ce7d08774755c0").asBytes();
|
||||||
@ -174,7 +170,7 @@ public class PirateChainTests extends Common {
|
|||||||
@Test
|
@Test
|
||||||
public void testGetTxidForSpentAddress() throws ForeignBlockchainException {
|
public void testGetTxidForSpentAddress() throws ForeignBlockchainException {
|
||||||
String p2shAddress = "bE49izfVxz8odhu8c2BcUaVFUnt7NLFRgv"; //"t3KtVxeEb8srJofo6atMEpMpEP6TjEi8VqA";
|
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
|
// Reverse the byte order of the txid used by block explorers, to get to big-endian form
|
||||||
byte[] expectedTxidLE = HashCode.fromString("fb386fc8eea0fbf3ea37047726b92c39441652b32d8d62a274331687f7a1eca8").asBytes();
|
byte[] expectedTxidLE = HashCode.fromString("fb386fc8eea0fbf3ea37047726b92c39441652b32d8d62a274331687f7a1eca8").asBytes();
|
||||||
@ -187,7 +183,7 @@ public class PirateChainTests extends Common {
|
|||||||
@Test
|
@Test
|
||||||
public void testGetTransactionsForAddress() throws ForeignBlockchainException {
|
public void testGetTransactionsForAddress() throws ForeignBlockchainException {
|
||||||
String p2shAddress = "bE49izfVxz8odhu8c2BcUaVFUnt7NLFRgv"; //"t3KtVxeEb8srJofo6atMEpMpEP6TjEi8VqA";
|
String p2shAddress = "bE49izfVxz8odhu8c2BcUaVFUnt7NLFRgv"; //"t3KtVxeEb8srJofo6atMEpMpEP6TjEi8VqA";
|
||||||
List<BitcoinyTransaction> transactions = pirateChain.getBlockchainProvider()
|
List<BitcoinyTransaction> transactions = bitcoiny.getBlockchainProvider()
|
||||||
.getAddressBitcoinyTransactions(p2shAddress, false);
|
.getAddressBitcoinyTransactions(p2shAddress, false);
|
||||||
|
|
||||||
assertEquals(2, transactions.size());
|
assertEquals(2, transactions.size());
|
||||||
@ -232,66 +228,17 @@ public class PirateChainTests extends Common {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore(value = "Doesn't work, to be fixed later")
|
@Ignore(value = "Doesn't work, to be fixed later")
|
||||||
public void testFindHtlcSecret() throws ForeignBlockchainException {
|
public void testFindHtlcSecret() {}
|
||||||
// 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));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore(value = "Needs adapting for Pirate Chain")
|
@Ignore(value = "Needs adapting for Pirate Chain")
|
||||||
public void testBuildSpend() {
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore(value = "Needs adapting for Pirate Chain")
|
@Ignore(value = "Needs adapting for Pirate Chain")
|
||||||
public void testGetWalletBalance() throws ForeignBlockchainException {
|
public void testGetWalletBalance() {}
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore(value = "Needs adapting for Pirate Chain")
|
@Ignore(value = "Needs adapting for Pirate Chain")
|
||||||
public void testGetUnusedReceiveAddress() throws ForeignBlockchainException {
|
public void testGetUnusedReceiveAddress() {}
|
||||||
String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ";
|
}
|
||||||
|
|
||||||
String address = pirateChain.getUnusedReceiveAddress(xprv58);
|
|
||||||
|
|
||||||
assertNotNull(address);
|
|
||||||
|
|
||||||
System.out.println(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,115 +1,47 @@
|
|||||||
package org.qortal.test.crosschain;
|
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.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.qortal.crosschain.ForeignBlockchainException;
|
import org.qortal.crosschain.Bitcoiny;
|
||||||
import org.qortal.crosschain.Ravencoin;
|
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;
|
@Override
|
||||||
|
protected String getCoinName() {
|
||||||
@Before
|
return "Ravencoin";
|
||||||
public void beforeTest() throws DataException {
|
|
||||||
Common.useDefaultSettings(); // TestNet3
|
|
||||||
ravencoin = Ravencoin.getInstance();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@Override
|
||||||
public void afterTest() {
|
protected String getCoinSymbol() {
|
||||||
|
return "RVN";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Bitcoiny getCoin() {
|
||||||
|
return Ravencoin.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void resetCoinForTesting() {
|
||||||
Ravencoin.resetForTesting();
|
Ravencoin.resetForTesting();
|
||||||
ravencoin = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Override
|
||||||
public void testGetMedianBlockTime() throws BlockStoreException, ForeignBlockchainException {
|
protected String getDeterministicKey58() {
|
||||||
long before = System.currentTimeMillis();
|
return "xpub661MyMwAqRbcEt3Ge1wNmkagyb1J7yTQu4Kquvy77Ycg2iPoh7Urg8s9Jdwp7YmrqGkDKJpUVjsZXSSsQgmAVUC17ZVQQeoWMzm7vDTt1y7";
|
||||||
System.out.println(String.format("Ravencoin median blocktime: %d", ravencoin.getMedianBlockTime()));
|
}
|
||||||
long afterFirst = System.currentTimeMillis();
|
|
||||||
|
|
||||||
System.out.println(String.format("Ravencoin median blocktime: %d", ravencoin.getMedianBlockTime()));
|
@Override
|
||||||
long afterSecond = System.currentTimeMillis();
|
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("2nd call should be quicker than 1st", secondPeriod < firstPeriod);
|
|
||||||
assertTrue("2nd call should take less than 5 seconds", secondPeriod < 5000L);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore(value = "Doesn't work, to be fixed later")
|
@Ignore(value = "Doesn't work, to be fixed later")
|
||||||
public void testFindHtlcSecret() throws ForeignBlockchainException {
|
public void testFindHtlcSecret() {}
|
||||||
// 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));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore(value = "No testnet nodes available, so we can't regularly test buildSpend yet")
|
@Ignore(value = "No testnet nodes available, so we can't regularly test buildSpend yet")
|
||||||
public void testBuildSpend() {
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,507 +2,89 @@ package org.qortal.test.crosschain.bitcoinv1;
|
|||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.ZoneOffset;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.time.format.FormatStyle;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Random;
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.qortal.account.Account;
|
import org.qortal.account.Account;
|
||||||
import org.qortal.account.PrivateKeyAccount;
|
import org.qortal.account.PrivateKeyAccount;
|
||||||
import org.qortal.asset.Asset;
|
import org.qortal.asset.Asset;
|
||||||
import org.qortal.block.Block;
|
import org.qortal.crosschain.ACCT;
|
||||||
import org.qortal.crosschain.BitcoinACCTv1;
|
import org.qortal.crosschain.BitcoinACCTv1;
|
||||||
import org.qortal.crosschain.AcctMode;
|
import org.qortal.crosschain.AcctMode;
|
||||||
import org.qortal.crypto.Crypto;
|
import org.qortal.crypto.Crypto;
|
||||||
import org.qortal.data.at.ATData;
|
import org.qortal.data.at.ATData;
|
||||||
import org.qortal.data.at.ATStateData;
|
import org.qortal.data.at.ATStateData;
|
||||||
import org.qortal.data.crosschain.CrossChainTradeData;
|
import org.qortal.data.crosschain.CrossChainTradeData;
|
||||||
import org.qortal.data.transaction.BaseTransactionData;
|
|
||||||
import org.qortal.data.transaction.DeployAtTransactionData;
|
|
||||||
import org.qortal.data.transaction.MessageTransactionData;
|
|
||||||
import org.qortal.data.transaction.TransactionData;
|
|
||||||
import org.qortal.group.Group;
|
|
||||||
import org.qortal.repository.DataException;
|
import org.qortal.repository.DataException;
|
||||||
import org.qortal.repository.Repository;
|
import org.qortal.repository.Repository;
|
||||||
import org.qortal.repository.RepositoryManager;
|
import org.qortal.repository.RepositoryManager;
|
||||||
import org.qortal.test.common.BlockUtils;
|
import org.qortal.test.common.BlockUtils;
|
||||||
import org.qortal.test.common.Common;
|
import org.qortal.test.common.Common;
|
||||||
import org.qortal.test.common.TransactionUtils;
|
import org.qortal.test.crosschain.ACCTTests;
|
||||||
import org.qortal.transaction.DeployAtTransaction;
|
import org.qortal.transaction.DeployAtTransaction;
|
||||||
import org.qortal.transaction.MessageTransaction;
|
import org.qortal.transaction.MessageTransaction;
|
||||||
import org.qortal.utils.Amounts;
|
|
||||||
|
|
||||||
import com.google.common.hash.HashCode;
|
import com.google.common.hash.HashCode;
|
||||||
import com.google.common.primitives.Bytes;
|
|
||||||
|
|
||||||
public class BitcoinACCTv1Tests extends Common {
|
public class BitcoinACCTv1Tests extends ACCTTests {
|
||||||
|
|
||||||
public static final byte[] secretA = "This string is exactly 32 bytes!".getBytes();
|
|
||||||
public static final byte[] hashOfSecretA = Crypto.hash160(secretA); // daf59884b4d1aec8c1b17102530909ee43c0151a
|
|
||||||
public static final byte[] secretB = "This string is roughly 32 bytes?".getBytes();
|
public static final byte[] secretB = "This string is roughly 32 bytes?".getBytes();
|
||||||
public static final byte[] hashOfSecretB = Crypto.hash160(secretB); // 31f0dd71decf59bbc8ef0661f4030479255cfa58
|
public static final byte[] hashOfSecretB = Crypto.hash160(secretB); // 31f0dd71decf59bbc8ef0661f4030479255cfa58
|
||||||
public static final byte[] bitcoinPublicKeyHash = HashCode.fromString("bb00bb11bb22bb33bb44bb55bb66bb77bb88bb99").asBytes();
|
public static final byte[] bitcoinPublicKeyHash = HashCode.fromString("bb00bb11bb22bb33bb44bb55bb66bb77bb88bb99").asBytes();
|
||||||
public static final int tradeTimeout = 20; // blocks
|
|
||||||
public static final long redeemAmount = 80_40200000L;
|
|
||||||
public static final long fundingAmount = 123_45600000L;
|
|
||||||
public static final long bitcoinAmount = 864200L; // 0.00864200 BTC
|
|
||||||
|
|
||||||
private static final Random RANDOM = new Random();
|
private static final String SYMBOL = "BTC";
|
||||||
|
|
||||||
@Before
|
private static final String NAME = "Bitcoin";
|
||||||
public void beforeTest() throws DataException {
|
|
||||||
Common.useDefaultSettings();
|
@Override
|
||||||
|
protected byte[] getPublicKey() {
|
||||||
|
return bitcoinPublicKeyHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Override
|
||||||
public void testCompile() {
|
protected byte[] buildQortalAT(String address, byte[] publicKey, long redeemAmount, long foreignAmount, int tradeTimeout) {
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(null);
|
return BitcoinACCTv1.buildQortalAT(address,publicKey, hashOfSecretB, redeemAmount, foreignAmount,tradeTimeout);
|
||||||
|
|
||||||
byte[] creationBytes = BitcoinACCTv1.buildQortalAT(tradeAccount.getAddress(), bitcoinPublicKeyHash, hashOfSecretB, redeemAmount, bitcoinAmount, tradeTimeout);
|
|
||||||
assertNotNull(creationBytes);
|
|
||||||
|
|
||||||
System.out.println("AT creation bytes: " + HashCode.fromBytes(creationBytes).toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Override
|
||||||
public void testDeploy() throws DataException {
|
protected ACCT getInstance() {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
return BitcoinACCTv1.getInstance();
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
}
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
@Override
|
||||||
|
protected int calcRefundTimeout(long partnersOfferMessageTransactionTimestamp, int lockTimeA) {
|
||||||
|
return BitcoinACCTv1.calcLockTimeB(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||||
|
}
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
@Override
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
protected byte[] buildTradeMessage(String address, byte[] publicKey, byte[] hashOfSecretA, int lockTimeA, int refundTimeout) {
|
||||||
|
return BitcoinACCTv1.buildTradeMessage(address, publicKey, hashOfSecretA, lockTimeA, refundTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
@Override
|
||||||
|
protected byte[] buildRedeemMessage(byte[] secretA, String address) {
|
||||||
|
return BitcoinACCTv1.buildRedeemMessage(secretA,secretB,address);
|
||||||
|
}
|
||||||
|
|
||||||
long expectedBalance = deployersInitialBalance - fundingAmount - deployAtTransaction.getTransactionData().getFee();
|
@Override
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
protected byte[] getCodeBytesHash() {
|
||||||
|
return BitcoinACCTv1.CODE_BYTES_HASH;
|
||||||
|
}
|
||||||
|
|
||||||
assertEquals("Deployer's post-deployment balance incorrect", expectedBalance, actualBalance);
|
@Override
|
||||||
|
protected String getSymbol() {
|
||||||
|
return SYMBOL;
|
||||||
|
}
|
||||||
|
|
||||||
expectedBalance = fundingAmount;
|
@Override
|
||||||
actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT);
|
protected String getName() {
|
||||||
|
return NAME;
|
||||||
assertEquals("AT's post-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
expectedBalance = partnersInitialBalance;
|
|
||||||
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Test orphaning
|
|
||||||
BlockUtils.orphanLastBlock(repository);
|
|
||||||
|
|
||||||
expectedBalance = deployersInitialBalance;
|
|
||||||
actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
expectedBalance = 0;
|
|
||||||
actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("AT's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
expectedBalance = partnersInitialBalance;
|
|
||||||
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
|
@Override
|
||||||
@Test
|
@Test
|
||||||
public void testOfferCancel() throws DataException {
|
public void testIncorrectSecretCorrectSender() throws DataException {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
|
|
||||||
// Send creator's address to AT, instead of typical partner's address
|
|
||||||
byte[] messageData = BitcoinACCTv1.getInstance().buildCancelMessage(deployer.getAddress());
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress);
|
|
||||||
long messageFee = messageTransaction.getTransactionData().getFee();
|
|
||||||
|
|
||||||
// AT should process 'cancel' message in next block
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertTrue(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in CANCELLED mode
|
|
||||||
CrossChainTradeData tradeData = BitcoinACCTv1.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.CANCELLED, tradeData.mode);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedMinimumBalance = deployersPostDeploymentBalance;
|
|
||||||
long expectedMaximumBalance = deployersInitialBalance - deployAtFee - messageFee;
|
|
||||||
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance);
|
|
||||||
assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance);
|
|
||||||
|
|
||||||
// Test orphaning
|
|
||||||
BlockUtils.orphanLastBlock(repository);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = deployersPostDeploymentBalance - messageFee;
|
|
||||||
actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testOfferCancelInvalidLength() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
|
|
||||||
// Instead of sending creator's address to AT, send too-short/invalid message
|
|
||||||
byte[] messageData = new byte[7];
|
|
||||||
RANDOM.nextBytes(messageData);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress);
|
|
||||||
long messageFee = messageTransaction.getTransactionData().getFee();
|
|
||||||
|
|
||||||
// AT should process 'cancel' message in next block
|
|
||||||
// As message is too short, it will be padded to 32bytes but cancel code doesn't care about message content, so should be ok
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertTrue(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in CANCELLED mode
|
|
||||||
CrossChainTradeData tradeData = BitcoinACCTv1.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.CANCELLED, tradeData.mode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testTradingInfoProcessing() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int lockTimeB = BitcoinACCTv1.calcLockTimeB(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = BitcoinACCTv1.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, lockTimeB);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
Block postDeploymentBlock = BlockUtils.mintBlock(repository);
|
|
||||||
int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight();
|
|
||||||
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
CrossChainTradeData tradeData = BitcoinACCTv1.getInstance().populateTradeData(repository, atData);
|
|
||||||
|
|
||||||
// AT should be in TRADE mode
|
|
||||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
|
||||||
|
|
||||||
// Check hashOfSecretA was extracted correctly
|
|
||||||
assertTrue(Arrays.equals(hashOfSecretA, tradeData.hashOfSecretA));
|
|
||||||
|
|
||||||
// Check trade partner Qortal address was extracted correctly
|
|
||||||
assertEquals(partner.getAddress(), tradeData.qortalPartnerAddress);
|
|
||||||
|
|
||||||
// Check trade partner's Bitcoin PKH was extracted correctly
|
|
||||||
assertTrue(Arrays.equals(bitcoinPublicKeyHash, tradeData.partnerForeignPKH));
|
|
||||||
|
|
||||||
// Test orphaning
|
|
||||||
BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = deployersPostDeploymentBalance;
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TEST SENDING TRADING INFO BUT NOT FROM AT CREATOR (SHOULD BE IGNORED)
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testIncorrectTradeSender() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int lockTimeB = BitcoinACCTv1.calcLockTimeB(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT BUT NOT FROM AT CREATOR
|
|
||||||
byte[] messageData = BitcoinACCTv1.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, lockTimeB);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, bystander, messageData, atAddress);
|
|
||||||
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
long expectedBalance = partnersInitialBalance;
|
|
||||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-initial-payout balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
CrossChainTradeData tradeData = BitcoinACCTv1.getInstance().populateTradeData(repository, atData);
|
|
||||||
|
|
||||||
// AT should still be in OFFER mode
|
|
||||||
assertEquals(AcctMode.OFFERING, tradeData.mode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testAutomaticTradeRefund() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int lockTimeB = BitcoinACCTv1.calcLockTimeB(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = BitcoinACCTv1.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, lockTimeB);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
Block postDeploymentBlock = BlockUtils.mintBlock(repository);
|
|
||||||
int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight();
|
|
||||||
|
|
||||||
// Check refund
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
|
|
||||||
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertTrue(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in REFUNDED mode
|
|
||||||
CrossChainTradeData tradeData = BitcoinACCTv1.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.REFUNDED, tradeData.mode);
|
|
||||||
|
|
||||||
// Test orphaning
|
|
||||||
BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = deployersPostDeploymentBalance;
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testCorrectSecretsCorrectSender() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int lockTimeB = BitcoinACCTv1.calcLockTimeB(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = BitcoinACCTv1.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, lockTimeB);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
// Give AT time to process message
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// Send correct secrets to AT, from correct account
|
|
||||||
messageData = BitcoinACCTv1.buildRedeemMessage(secretA, secretB, partner.getAddress());
|
|
||||||
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
|
||||||
|
|
||||||
// AT should send funds in the next block
|
|
||||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertTrue(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in REDEEMED mode
|
|
||||||
CrossChainTradeData tradeData = BitcoinACCTv1.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.REDEEMED, tradeData.mode);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee() + redeemAmount;
|
|
||||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-redeem balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Orphan redeem
|
|
||||||
BlockUtils.orphanLastBlock(repository);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee();
|
|
||||||
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-orphan/pre-redeem balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Check AT state
|
|
||||||
ATStateData postOrphanAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
|
|
||||||
assertTrue("AT states mismatch", Arrays.equals(preRedeemAtStateData.getStateData(), postOrphanAtStateData.getStateData()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testCorrectSecretsIncorrectSender() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int lockTimeB = BitcoinACCTv1.calcLockTimeB(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = BitcoinACCTv1.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, lockTimeB);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
// Give AT time to process message
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// Send correct secrets to AT, but from wrong account
|
|
||||||
messageData = BitcoinACCTv1.buildRedeemMessage(secretA, secretB, partner.getAddress());
|
|
||||||
messageTransaction = sendMessage(repository, bystander, messageData, atAddress);
|
|
||||||
|
|
||||||
// AT should NOT send funds in the next block
|
|
||||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is NOT finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertFalse(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should still be in TRADE mode
|
|
||||||
CrossChainTradeData tradeData = BitcoinACCTv1.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = partnersInitialBalance;
|
|
||||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Check eventual refund
|
|
||||||
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testIncorrectSecretsCorrectSender() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||||
@ -582,197 +164,8 @@ public class BitcoinACCTv1Tests extends Common {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Override
|
||||||
@Test
|
protected void describeRefundAt(CrossChainTradeData tradeData, Function<Long, String> epochMilliFormatter) {
|
||||||
public void testCorrectSecretsCorrectSenderInvalidMessageLength() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int lockTimeB = BitcoinACCTv1.calcLockTimeB(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = BitcoinACCTv1.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, lockTimeB);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
// Give AT time to process message
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// Send correct secrets to AT, from correct account, but missing receive address, hence incorrect length
|
|
||||||
messageData = Bytes.concat(secretA, secretB);
|
|
||||||
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
|
||||||
|
|
||||||
// AT should NOT send funds in the next block
|
|
||||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is NOT finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertFalse(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in TRADING mode
|
|
||||||
CrossChainTradeData tradeData = BitcoinACCTv1.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testDescribeDeployed() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
|
|
||||||
List<ATData> executableAts = repository.getATRepository().getAllExecutableATs();
|
|
||||||
|
|
||||||
for (ATData atData : executableAts) {
|
|
||||||
String atAddress = atData.getATAddress();
|
|
||||||
byte[] codeBytes = atData.getCodeBytes();
|
|
||||||
byte[] codeHash = Crypto.digest(codeBytes);
|
|
||||||
|
|
||||||
System.out.println(String.format("%s: code length: %d byte%s, code hash: %s",
|
|
||||||
atAddress,
|
|
||||||
codeBytes.length,
|
|
||||||
(codeBytes.length != 1 ? "s": ""),
|
|
||||||
HashCode.fromBytes(codeHash)));
|
|
||||||
|
|
||||||
// Not one of ours?
|
|
||||||
if (!Arrays.equals(codeHash, BitcoinACCTv1.CODE_BYTES_HASH))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int calcTestLockTimeA(long messageTimestamp) {
|
|
||||||
return (int) (messageTimestamp / 1000L + tradeTimeout * 60);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer, String tradeAddress) throws DataException {
|
|
||||||
byte[] creationBytes = BitcoinACCTv1.buildQortalAT(tradeAddress, bitcoinPublicKeyHash, hashOfSecretB, redeemAmount, bitcoinAmount, tradeTimeout);
|
|
||||||
|
|
||||||
long txTimestamp = System.currentTimeMillis();
|
|
||||||
byte[] lastReference = deployer.getLastReference();
|
|
||||||
|
|
||||||
if (lastReference == null) {
|
|
||||||
System.err.println(String.format("Qortal account %s has no last reference", deployer.getAddress()));
|
|
||||||
System.exit(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
Long fee = null;
|
|
||||||
String name = "QORT-BTC cross-chain trade";
|
|
||||||
String description = String.format("Qortal-Bitcoin cross-chain trade");
|
|
||||||
String atType = "ACCT";
|
|
||||||
String tags = "QORT-BTC ACCT";
|
|
||||||
|
|
||||||
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, deployer.getPublicKey(), fee, null);
|
|
||||||
TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, atType, tags, creationBytes, fundingAmount, Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData);
|
|
||||||
|
|
||||||
fee = deployAtTransaction.calcRecommendedFee();
|
|
||||||
deployAtTransactionData.setFee(fee);
|
|
||||||
|
|
||||||
TransactionUtils.signAndMint(repository, deployAtTransactionData, deployer);
|
|
||||||
|
|
||||||
return deployAtTransaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
private MessageTransaction sendMessage(Repository repository, PrivateKeyAccount sender, byte[] data, String recipient) throws DataException {
|
|
||||||
long txTimestamp = System.currentTimeMillis();
|
|
||||||
byte[] lastReference = sender.getLastReference();
|
|
||||||
|
|
||||||
if (lastReference == null) {
|
|
||||||
System.err.println(String.format("Qortal account %s has no last reference", sender.getAddress()));
|
|
||||||
System.exit(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
Long fee = null;
|
|
||||||
int version = 4;
|
|
||||||
int nonce = 0;
|
|
||||||
long amount = 0;
|
|
||||||
Long assetId = null; // because amount is zero
|
|
||||||
|
|
||||||
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, sender.getPublicKey(), fee, null);
|
|
||||||
TransactionData messageTransactionData = new MessageTransactionData(baseTransactionData, version, nonce, recipient, amount, assetId, data, false, false);
|
|
||||||
|
|
||||||
MessageTransaction messageTransaction = new MessageTransaction(repository, messageTransactionData);
|
|
||||||
|
|
||||||
fee = messageTransaction.calcRecommendedFee();
|
|
||||||
messageTransactionData.setFee(fee);
|
|
||||||
|
|
||||||
TransactionUtils.signAndMint(repository, messageTransactionData, sender);
|
|
||||||
|
|
||||||
return messageTransaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkTradeRefund(Repository repository, Account deployer, long deployersInitialBalance, long deployAtFee) throws DataException {
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
int refundTimeout = tradeTimeout * 3 / 4 + 1; // close enough
|
|
||||||
|
|
||||||
// AT should automatically refund deployer after 'refundTimeout' blocks
|
|
||||||
for (int blockCount = 0; blockCount <= refundTimeout; ++blockCount)
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// We don't bother to exactly calculate QORT spent running AT for several blocks, but we do know the expected range
|
|
||||||
long expectedMinimumBalance = deployersPostDeploymentBalance;
|
|
||||||
long expectedMaximumBalance = deployersInitialBalance - deployAtFee;
|
|
||||||
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance);
|
|
||||||
assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void describeAt(Repository repository, String atAddress) throws DataException {
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
CrossChainTradeData tradeData = BitcoinACCTv1.getInstance().populateTradeData(repository, atData);
|
|
||||||
|
|
||||||
Function<Long, String> epochMilliFormatter = (timestamp) -> LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneOffset.UTC).format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM));
|
|
||||||
int currentBlockHeight = repository.getBlockRepository().getBlockchainHeight();
|
|
||||||
|
|
||||||
System.out.print(String.format("%s:\n"
|
|
||||||
+ "\tmode: %s\n"
|
|
||||||
+ "\tcreator: %s,\n"
|
|
||||||
+ "\tcreation timestamp: %s,\n"
|
|
||||||
+ "\tcurrent balance: %s QORT,\n"
|
|
||||||
+ "\tis finished: %b,\n"
|
|
||||||
+ "\tHASH160 of secret-B: %s,\n"
|
|
||||||
+ "\tredeem payout: %s QORT,\n"
|
|
||||||
+ "\texpected bitcoin: %s BTC,\n"
|
|
||||||
+ "\tcurrent block height: %d,\n",
|
|
||||||
tradeData.qortalAtAddress,
|
|
||||||
tradeData.mode,
|
|
||||||
tradeData.qortalCreator,
|
|
||||||
epochMilliFormatter.apply(tradeData.creationTimestamp),
|
|
||||||
Amounts.prettyAmount(tradeData.qortBalance),
|
|
||||||
atData.getIsFinished(),
|
|
||||||
HashCode.fromBytes(tradeData.hashOfSecretB).toString().substring(0, 40),
|
|
||||||
Amounts.prettyAmount(tradeData.qortAmount),
|
|
||||||
Amounts.prettyAmount(tradeData.expectedForeignAmount),
|
|
||||||
currentBlockHeight));
|
|
||||||
|
|
||||||
if (tradeData.mode != AcctMode.OFFERING && tradeData.mode != AcctMode.CANCELLED) {
|
if (tradeData.mode != AcctMode.OFFERING && tradeData.mode != AcctMode.CANCELLED) {
|
||||||
System.out.println(String.format("\trefund height: block %d,\n"
|
System.out.println(String.format("\trefund height: block %d,\n"
|
||||||
+ "\tHASH160 of secret-A: %s,\n"
|
+ "\tHASH160 of secret-A: %s,\n"
|
||||||
@ -786,10 +179,4 @@ public class BitcoinACCTv1Tests extends Common {
|
|||||||
tradeData.qortalPartnerAddress));
|
tradeData.qortalPartnerAddress));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private PrivateKeyAccount createTradeAccount(Repository repository) {
|
|
||||||
// We actually use a known test account with funds to avoid PoW compute
|
|
||||||
return Common.getTestAccount(repository, "alice");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,769 +1,58 @@
|
|||||||
package org.qortal.test.crosschain.bitcoinv3;
|
package org.qortal.test.crosschain.bitcoinv3;
|
||||||
|
|
||||||
import com.google.common.hash.HashCode;
|
import com.google.common.hash.HashCode;
|
||||||
import com.google.common.primitives.Bytes;
|
import org.qortal.crosschain.ACCT;
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.qortal.account.Account;
|
|
||||||
import org.qortal.account.PrivateKeyAccount;
|
|
||||||
import org.qortal.asset.Asset;
|
|
||||||
import org.qortal.block.Block;
|
|
||||||
import org.qortal.crosschain.AcctMode;
|
|
||||||
import org.qortal.crosschain.BitcoinACCTv3;
|
import org.qortal.crosschain.BitcoinACCTv3;
|
||||||
import org.qortal.crypto.Crypto;
|
import org.qortal.test.crosschain.ACCTTests;
|
||||||
import org.qortal.data.at.ATData;
|
|
||||||
import org.qortal.data.at.ATStateData;
|
|
||||||
import org.qortal.data.crosschain.CrossChainTradeData;
|
|
||||||
import org.qortal.data.transaction.BaseTransactionData;
|
|
||||||
import org.qortal.data.transaction.DeployAtTransactionData;
|
|
||||||
import org.qortal.data.transaction.MessageTransactionData;
|
|
||||||
import org.qortal.data.transaction.TransactionData;
|
|
||||||
import org.qortal.group.Group;
|
|
||||||
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.TransactionUtils;
|
|
||||||
import org.qortal.transaction.DeployAtTransaction;
|
|
||||||
import org.qortal.transaction.MessageTransaction;
|
|
||||||
import org.qortal.utils.Amounts;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
public class BitcoinACCTv3Tests extends ACCTTests {
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.ZoneOffset;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.time.format.FormatStyle;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Random;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
public class BitcoinACCTv3Tests extends Common {
|
|
||||||
|
|
||||||
public static final byte[] secretA = "This string is exactly 32 bytes!".getBytes();
|
|
||||||
public static final byte[] hashOfSecretA = Crypto.hash160(secretA); // daf59884b4d1aec8c1b17102530909ee43c0151a
|
|
||||||
public static final byte[] bitcoinPublicKeyHash = HashCode.fromString("bb00bb11bb22bb33bb44bb55bb66bb77bb88bb99").asBytes();
|
public static final byte[] bitcoinPublicKeyHash = HashCode.fromString("bb00bb11bb22bb33bb44bb55bb66bb77bb88bb99").asBytes();
|
||||||
public static final int tradeTimeout = 20; // blocks
|
private static final String SYMBOL = "BTC";
|
||||||
public static final long redeemAmount = 80_40200000L;
|
private static final String NAME = "Bitcoin";
|
||||||
public static final long fundingAmount = 123_45600000L;
|
|
||||||
public static final long bitcoinAmount = 864200L; // 0.00864200 BTC
|
|
||||||
|
|
||||||
private static final Random RANDOM = new Random();
|
@Override
|
||||||
|
protected byte[] getPublicKey() {
|
||||||
@Before
|
return bitcoinPublicKeyHash;
|
||||||
public void beforeTest() throws DataException {
|
|
||||||
Common.useDefaultSettings();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Override
|
||||||
public void testCompile() {
|
protected byte[] buildQortalAT(String address, byte[] publicKey, long redeemAmount, long foreignAmount, int tradeTimeout) {
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(null);
|
return BitcoinACCTv3.buildQortalAT(address, publicKey, redeemAmount, foreignAmount, tradeTimeout);
|
||||||
|
|
||||||
byte[] creationBytes = BitcoinACCTv3.buildQortalAT(tradeAccount.getAddress(), bitcoinPublicKeyHash, redeemAmount, bitcoinAmount, tradeTimeout);
|
|
||||||
assertNotNull(creationBytes);
|
|
||||||
|
|
||||||
System.out.println("AT creation bytes: " + HashCode.fromBytes(creationBytes).toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Override
|
||||||
public void testDeploy() throws DataException {
|
protected ACCT getInstance() {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
return BitcoinACCTv3.getInstance();
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
|
|
||||||
long expectedBalance = deployersInitialBalance - fundingAmount - deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
expectedBalance = fundingAmount;
|
|
||||||
actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("AT's post-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
expectedBalance = partnersInitialBalance;
|
|
||||||
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Test orphaning
|
|
||||||
BlockUtils.orphanLastBlock(repository);
|
|
||||||
|
|
||||||
expectedBalance = deployersInitialBalance;
|
|
||||||
actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
expectedBalance = 0;
|
|
||||||
actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("AT's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
expectedBalance = partnersInitialBalance;
|
|
||||||
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Override
|
||||||
@Test
|
protected int calcRefundTimeout(long partnersOfferMessageTransactionTimestamp, int lockTimeA) {
|
||||||
public void testOfferCancel() throws DataException {
|
return BitcoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
|
|
||||||
// Send creator's address to AT, instead of typical partner's address
|
|
||||||
byte[] messageData = BitcoinACCTv3.getInstance().buildCancelMessage(deployer.getAddress());
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress);
|
|
||||||
long messageFee = messageTransaction.getTransactionData().getFee();
|
|
||||||
|
|
||||||
// AT should process 'cancel' message in next block
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertTrue(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in CANCELLED mode
|
|
||||||
CrossChainTradeData tradeData = BitcoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.CANCELLED, tradeData.mode);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedMinimumBalance = deployersPostDeploymentBalance;
|
|
||||||
long expectedMaximumBalance = deployersInitialBalance - deployAtFee - messageFee;
|
|
||||||
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance);
|
|
||||||
assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance);
|
|
||||||
|
|
||||||
// Test orphaning
|
|
||||||
BlockUtils.orphanLastBlock(repository);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = deployersPostDeploymentBalance - messageFee;
|
|
||||||
actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Override
|
||||||
@Test
|
protected byte[] buildTradeMessage(String address, byte[] publicKey, byte[] hashOfSecretA, int lockTimeA, int refundTimeout) {
|
||||||
public void testOfferCancelInvalidLength() throws DataException {
|
return BitcoinACCTv3.buildTradeMessage(address, publicKey, hashOfSecretA, lockTimeA, refundTimeout);
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
|
|
||||||
// Instead of sending creator's address to AT, send too-short/invalid message
|
|
||||||
byte[] messageData = new byte[7];
|
|
||||||
RANDOM.nextBytes(messageData);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress);
|
|
||||||
long messageFee = messageTransaction.getTransactionData().getFee();
|
|
||||||
|
|
||||||
// AT should process 'cancel' message in next block
|
|
||||||
// As message is too short, it will be padded to 32bytes but cancel code doesn't care about message content, so should be ok
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertTrue(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in CANCELLED mode
|
|
||||||
CrossChainTradeData tradeData = BitcoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.CANCELLED, tradeData.mode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Override
|
||||||
@Test
|
protected byte[] buildRedeemMessage(byte[] secretA, String address) {
|
||||||
public void testTradingInfoProcessing() throws DataException {
|
return BitcoinACCTv3.buildRedeemMessage(secretA, address);
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = BitcoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = BitcoinACCTv3.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
Block postDeploymentBlock = BlockUtils.mintBlock(repository);
|
|
||||||
int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight();
|
|
||||||
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
CrossChainTradeData tradeData = BitcoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
|
|
||||||
// AT should be in TRADE mode
|
|
||||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
|
||||||
|
|
||||||
// Check hashOfSecretA was extracted correctly
|
|
||||||
assertTrue(Arrays.equals(hashOfSecretA, tradeData.hashOfSecretA));
|
|
||||||
|
|
||||||
// Check trade partner Qortal address was extracted correctly
|
|
||||||
assertEquals(partner.getAddress(), tradeData.qortalPartnerAddress);
|
|
||||||
|
|
||||||
// Check trade partner's Bitcoin PKH was extracted correctly
|
|
||||||
assertTrue(Arrays.equals(bitcoinPublicKeyHash, tradeData.partnerForeignPKH));
|
|
||||||
|
|
||||||
// Test orphaning
|
|
||||||
BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = deployersPostDeploymentBalance;
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TEST SENDING TRADING INFO BUT NOT FROM AT CREATOR (SHOULD BE IGNORED)
|
@Override
|
||||||
@SuppressWarnings("unused")
|
protected byte[] getCodeBytesHash() {
|
||||||
@Test
|
return BitcoinACCTv3.CODE_BYTES_HASH;
|
||||||
public void testIncorrectTradeSender() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = BitcoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT BUT NOT FROM AT CREATOR
|
|
||||||
byte[] messageData = BitcoinACCTv3.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, bystander, messageData, atAddress);
|
|
||||||
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
long expectedBalance = partnersInitialBalance;
|
|
||||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-initial-payout balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
CrossChainTradeData tradeData = BitcoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
|
|
||||||
// AT should still be in OFFER mode
|
|
||||||
assertEquals(AcctMode.OFFERING, tradeData.mode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Override
|
||||||
@Test
|
protected String getSymbol() {
|
||||||
public void testAutomaticTradeRefund() throws DataException {
|
return SYMBOL;
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = BitcoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = BitcoinACCTv3.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
Block postDeploymentBlock = BlockUtils.mintBlock(repository);
|
|
||||||
int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight();
|
|
||||||
|
|
||||||
// Check refund
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
|
|
||||||
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertTrue(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in REFUNDED mode
|
|
||||||
CrossChainTradeData tradeData = BitcoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.REFUNDED, tradeData.mode);
|
|
||||||
|
|
||||||
// Test orphaning
|
|
||||||
BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = deployersPostDeploymentBalance;
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Override
|
||||||
@Test
|
protected String getName() {
|
||||||
public void testCorrectSecretCorrectSender() throws DataException {
|
return NAME;
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = BitcoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = BitcoinACCTv3.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
// Give AT time to process message
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// Send correct secret to AT, from correct account
|
|
||||||
messageData = BitcoinACCTv3.buildRedeemMessage(secretA, partner.getAddress());
|
|
||||||
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
|
||||||
|
|
||||||
// AT should send funds in the next block
|
|
||||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertTrue(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in REDEEMED mode
|
|
||||||
CrossChainTradeData tradeData = BitcoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.REDEEMED, tradeData.mode);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee() + redeemAmount;
|
|
||||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-redeem balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Orphan redeem
|
|
||||||
BlockUtils.orphanLastBlock(repository);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee();
|
|
||||||
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-orphan/pre-redeem balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Check AT state
|
|
||||||
ATStateData postOrphanAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
|
|
||||||
assertTrue("AT states mismatch", Arrays.equals(preRedeemAtStateData.getStateData(), postOrphanAtStateData.getStateData()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testCorrectSecretIncorrectSender() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = BitcoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = BitcoinACCTv3.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
// Give AT time to process message
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// Send correct secret to AT, but from wrong account
|
|
||||||
messageData = BitcoinACCTv3.buildRedeemMessage(secretA, partner.getAddress());
|
|
||||||
messageTransaction = sendMessage(repository, bystander, messageData, atAddress);
|
|
||||||
|
|
||||||
// AT should NOT send funds in the next block
|
|
||||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is NOT finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertFalse(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should still be in TRADE mode
|
|
||||||
CrossChainTradeData tradeData = BitcoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = partnersInitialBalance;
|
|
||||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Check eventual refund
|
|
||||||
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testIncorrectSecretCorrectSender() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = BitcoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = BitcoinACCTv3.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
// Give AT time to process message
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// Send incorrect secret to AT, from correct account
|
|
||||||
byte[] wrongSecret = new byte[32];
|
|
||||||
RANDOM.nextBytes(wrongSecret);
|
|
||||||
messageData = BitcoinACCTv3.buildRedeemMessage(wrongSecret, partner.getAddress());
|
|
||||||
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
|
||||||
|
|
||||||
// AT should NOT send funds in the next block
|
|
||||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is NOT finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertFalse(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should still be in TRADE mode
|
|
||||||
CrossChainTradeData tradeData = BitcoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
|
||||||
|
|
||||||
long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee();
|
|
||||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Check eventual refund
|
|
||||||
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testCorrectSecretCorrectSenderInvalidMessageLength() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = BitcoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = BitcoinACCTv3.buildTradeMessage(partner.getAddress(), bitcoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
// Give AT time to process message
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// Send correct secret to AT, from correct account, but missing receive address, hence incorrect length
|
|
||||||
messageData = Bytes.concat(secretA);
|
|
||||||
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
|
||||||
|
|
||||||
// AT should NOT send funds in the next block
|
|
||||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is NOT finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertFalse(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in TRADING mode
|
|
||||||
CrossChainTradeData tradeData = BitcoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testDescribeDeployed() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
|
|
||||||
List<ATData> executableAts = repository.getATRepository().getAllExecutableATs();
|
|
||||||
|
|
||||||
for (ATData atData : executableAts) {
|
|
||||||
String atAddress = atData.getATAddress();
|
|
||||||
byte[] codeBytes = atData.getCodeBytes();
|
|
||||||
byte[] codeHash = Crypto.digest(codeBytes);
|
|
||||||
|
|
||||||
System.out.println(String.format("%s: code length: %d byte%s, code hash: %s",
|
|
||||||
atAddress,
|
|
||||||
codeBytes.length,
|
|
||||||
(codeBytes.length != 1 ? "s": ""),
|
|
||||||
HashCode.fromBytes(codeHash)));
|
|
||||||
|
|
||||||
// Not one of ours?
|
|
||||||
if (!Arrays.equals(codeHash, BitcoinACCTv3.CODE_BYTES_HASH))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int calcTestLockTimeA(long messageTimestamp) {
|
|
||||||
return (int) (messageTimestamp / 1000L + tradeTimeout * 60);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer, String tradeAddress) throws DataException {
|
|
||||||
byte[] creationBytes = BitcoinACCTv3.buildQortalAT(tradeAddress, bitcoinPublicKeyHash, redeemAmount, bitcoinAmount, tradeTimeout);
|
|
||||||
|
|
||||||
long txTimestamp = System.currentTimeMillis();
|
|
||||||
byte[] lastReference = deployer.getLastReference();
|
|
||||||
|
|
||||||
if (lastReference == null) {
|
|
||||||
System.err.println(String.format("Qortal account %s has no last reference", deployer.getAddress()));
|
|
||||||
System.exit(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
Long fee = null;
|
|
||||||
String name = "QORT-BTC cross-chain trade";
|
|
||||||
String description = String.format("Qortal-Bitcoin cross-chain trade");
|
|
||||||
String atType = "ACCT";
|
|
||||||
String tags = "QORT-BTC ACCT";
|
|
||||||
|
|
||||||
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, deployer.getPublicKey(), fee, null);
|
|
||||||
TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, atType, tags, creationBytes, fundingAmount, Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData);
|
|
||||||
|
|
||||||
fee = deployAtTransaction.calcRecommendedFee();
|
|
||||||
deployAtTransactionData.setFee(fee);
|
|
||||||
|
|
||||||
TransactionUtils.signAndMint(repository, deployAtTransactionData, deployer);
|
|
||||||
|
|
||||||
return deployAtTransaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
private MessageTransaction sendMessage(Repository repository, PrivateKeyAccount sender, byte[] data, String recipient) throws DataException {
|
|
||||||
long txTimestamp = System.currentTimeMillis();
|
|
||||||
byte[] lastReference = sender.getLastReference();
|
|
||||||
|
|
||||||
if (lastReference == null) {
|
|
||||||
System.err.println(String.format("Qortal account %s has no last reference", sender.getAddress()));
|
|
||||||
System.exit(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
Long fee = null;
|
|
||||||
int version = 4;
|
|
||||||
int nonce = 0;
|
|
||||||
long amount = 0;
|
|
||||||
Long assetId = null; // because amount is zero
|
|
||||||
|
|
||||||
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, sender.getPublicKey(), fee, null);
|
|
||||||
TransactionData messageTransactionData = new MessageTransactionData(baseTransactionData, version, nonce, recipient, amount, assetId, data, false, false);
|
|
||||||
|
|
||||||
MessageTransaction messageTransaction = new MessageTransaction(repository, messageTransactionData);
|
|
||||||
|
|
||||||
fee = messageTransaction.calcRecommendedFee();
|
|
||||||
messageTransactionData.setFee(fee);
|
|
||||||
|
|
||||||
TransactionUtils.signAndMint(repository, messageTransactionData, sender);
|
|
||||||
|
|
||||||
return messageTransaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkTradeRefund(Repository repository, Account deployer, long deployersInitialBalance, long deployAtFee) throws DataException {
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
int refundTimeout = tradeTimeout / 2 + 1; // close enough
|
|
||||||
|
|
||||||
// AT should automatically refund deployer after 'refundTimeout' blocks
|
|
||||||
for (int blockCount = 0; blockCount <= refundTimeout; ++blockCount)
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// We don't bother to exactly calculate QORT spent running AT for several blocks, but we do know the expected range
|
|
||||||
long expectedMinimumBalance = deployersPostDeploymentBalance;
|
|
||||||
long expectedMaximumBalance = deployersInitialBalance - deployAtFee;
|
|
||||||
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance);
|
|
||||||
assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void describeAt(Repository repository, String atAddress) throws DataException {
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
CrossChainTradeData tradeData = BitcoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
|
|
||||||
Function<Long, String> epochMilliFormatter = (timestamp) -> LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneOffset.UTC).format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM));
|
|
||||||
int currentBlockHeight = repository.getBlockRepository().getBlockchainHeight();
|
|
||||||
|
|
||||||
System.out.print(String.format("%s:\n"
|
|
||||||
+ "\tmode: %s\n"
|
|
||||||
+ "\tcreator: %s,\n"
|
|
||||||
+ "\tcreation timestamp: %s,\n"
|
|
||||||
+ "\tcurrent balance: %s QORT,\n"
|
|
||||||
+ "\tis finished: %b,\n"
|
|
||||||
+ "\tredeem payout: %s QORT,\n"
|
|
||||||
+ "\texpected Bitcoin: %s BTC,\n"
|
|
||||||
+ "\tcurrent block height: %d,\n",
|
|
||||||
tradeData.qortalAtAddress,
|
|
||||||
tradeData.mode,
|
|
||||||
tradeData.qortalCreator,
|
|
||||||
epochMilliFormatter.apply(tradeData.creationTimestamp),
|
|
||||||
Amounts.prettyAmount(tradeData.qortBalance),
|
|
||||||
atData.getIsFinished(),
|
|
||||||
Amounts.prettyAmount(tradeData.qortAmount),
|
|
||||||
Amounts.prettyAmount(tradeData.expectedForeignAmount),
|
|
||||||
currentBlockHeight));
|
|
||||||
|
|
||||||
if (tradeData.mode != AcctMode.OFFERING && tradeData.mode != AcctMode.CANCELLED) {
|
|
||||||
System.out.println(String.format("\trefund timeout: %d minutes,\n"
|
|
||||||
+ "\trefund height: block %d,\n"
|
|
||||||
+ "\tHASH160 of secret-A: %s,\n"
|
|
||||||
+ "\tBitcoin P2SH-A nLockTime: %d (%s),\n"
|
|
||||||
+ "\ttrade partner: %s\n"
|
|
||||||
+ "\tpartner's receiving address: %s",
|
|
||||||
tradeData.refundTimeout,
|
|
||||||
tradeData.tradeRefundHeight,
|
|
||||||
HashCode.fromBytes(tradeData.hashOfSecretA).toString().substring(0, 40),
|
|
||||||
tradeData.lockTimeA, epochMilliFormatter.apply(tradeData.lockTimeA * 1000L),
|
|
||||||
tradeData.qortalPartnerAddress,
|
|
||||||
tradeData.qortalPartnerReceivingAddress));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private PrivateKeyAccount createTradeAccount(Repository repository) {
|
|
||||||
// We actually use a known test account with funds to avoid PoW compute
|
|
||||||
return Common.getTestAccount(repository, "alice");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,769 +1,58 @@
|
|||||||
package org.qortal.test.crosschain.digibytev3;
|
package org.qortal.test.crosschain.digibytev3;
|
||||||
|
|
||||||
import com.google.common.hash.HashCode;
|
import com.google.common.hash.HashCode;
|
||||||
import com.google.common.primitives.Bytes;
|
import org.qortal.crosschain.ACCT;
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.qortal.account.Account;
|
|
||||||
import org.qortal.account.PrivateKeyAccount;
|
|
||||||
import org.qortal.asset.Asset;
|
|
||||||
import org.qortal.block.Block;
|
|
||||||
import org.qortal.crosschain.AcctMode;
|
|
||||||
import org.qortal.crosschain.DigibyteACCTv3;
|
import org.qortal.crosschain.DigibyteACCTv3;
|
||||||
import org.qortal.crypto.Crypto;
|
import org.qortal.test.crosschain.ACCTTests;
|
||||||
import org.qortal.data.at.ATData;
|
|
||||||
import org.qortal.data.at.ATStateData;
|
|
||||||
import org.qortal.data.crosschain.CrossChainTradeData;
|
|
||||||
import org.qortal.data.transaction.BaseTransactionData;
|
|
||||||
import org.qortal.data.transaction.DeployAtTransactionData;
|
|
||||||
import org.qortal.data.transaction.MessageTransactionData;
|
|
||||||
import org.qortal.data.transaction.TransactionData;
|
|
||||||
import org.qortal.group.Group;
|
|
||||||
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.TransactionUtils;
|
|
||||||
import org.qortal.transaction.DeployAtTransaction;
|
|
||||||
import org.qortal.transaction.MessageTransaction;
|
|
||||||
import org.qortal.utils.Amounts;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
public class DigibyteACCTv3Tests extends ACCTTests {
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.ZoneOffset;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.time.format.FormatStyle;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Random;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
public class DigibyteACCTv3Tests extends Common {
|
|
||||||
|
|
||||||
public static final byte[] secretA = "This string is exactly 32 bytes!".getBytes();
|
|
||||||
public static final byte[] hashOfSecretA = Crypto.hash160(secretA); // daf59884b4d1aec8c1b17102530909ee43c0151a
|
|
||||||
public static final byte[] digibytePublicKeyHash = HashCode.fromString("bb00bb11bb22bb33bb44bb55bb66bb77bb88bb99").asBytes();
|
public static final byte[] digibytePublicKeyHash = HashCode.fromString("bb00bb11bb22bb33bb44bb55bb66bb77bb88bb99").asBytes();
|
||||||
public static final int tradeTimeout = 20; // blocks
|
private static final String SYMBOL = "DGB";
|
||||||
public static final long redeemAmount = 80_40200000L;
|
private static final String NAME = "DigiByte";
|
||||||
public static final long fundingAmount = 123_45600000L;
|
|
||||||
public static final long digibyteAmount = 864200L; // 0.00864200 DGB
|
|
||||||
|
|
||||||
private static final Random RANDOM = new Random();
|
@Override
|
||||||
|
protected byte[] getPublicKey() {
|
||||||
@Before
|
return digibytePublicKeyHash;
|
||||||
public void beforeTest() throws DataException {
|
|
||||||
Common.useDefaultSettings();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Override
|
||||||
public void testCompile() {
|
protected byte[] buildQortalAT(String address, byte[] publicKey, long redeemAmount, long foreignAmount, int tradeTimeout) {
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(null);
|
return DigibyteACCTv3.buildQortalAT(address, publicKey, redeemAmount, foreignAmount, tradeTimeout);
|
||||||
|
|
||||||
byte[] creationBytes = DigibyteACCTv3.buildQortalAT(tradeAccount.getAddress(), digibytePublicKeyHash, redeemAmount, digibyteAmount, tradeTimeout);
|
|
||||||
assertNotNull(creationBytes);
|
|
||||||
|
|
||||||
System.out.println("AT creation bytes: " + HashCode.fromBytes(creationBytes).toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Override
|
||||||
public void testDeploy() throws DataException {
|
protected ACCT getInstance() {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
return DigibyteACCTv3.getInstance();
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
|
|
||||||
long expectedBalance = deployersInitialBalance - fundingAmount - deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
expectedBalance = fundingAmount;
|
|
||||||
actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("AT's post-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
expectedBalance = partnersInitialBalance;
|
|
||||||
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Test orphaning
|
|
||||||
BlockUtils.orphanLastBlock(repository);
|
|
||||||
|
|
||||||
expectedBalance = deployersInitialBalance;
|
|
||||||
actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
expectedBalance = 0;
|
|
||||||
actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("AT's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
expectedBalance = partnersInitialBalance;
|
|
||||||
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Override
|
||||||
@Test
|
protected int calcRefundTimeout(long partnersOfferMessageTransactionTimestamp, int lockTimeA) {
|
||||||
public void testOfferCancel() throws DataException {
|
return DigibyteACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
|
|
||||||
// Send creator's address to AT, instead of typical partner's address
|
|
||||||
byte[] messageData = DigibyteACCTv3.getInstance().buildCancelMessage(deployer.getAddress());
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress);
|
|
||||||
long messageFee = messageTransaction.getTransactionData().getFee();
|
|
||||||
|
|
||||||
// AT should process 'cancel' message in next block
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertTrue(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in CANCELLED mode
|
|
||||||
CrossChainTradeData tradeData = DigibyteACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.CANCELLED, tradeData.mode);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedMinimumBalance = deployersPostDeploymentBalance;
|
|
||||||
long expectedMaximumBalance = deployersInitialBalance - deployAtFee - messageFee;
|
|
||||||
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance);
|
|
||||||
assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance);
|
|
||||||
|
|
||||||
// Test orphaning
|
|
||||||
BlockUtils.orphanLastBlock(repository);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = deployersPostDeploymentBalance - messageFee;
|
|
||||||
actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Override
|
||||||
@Test
|
protected byte[] buildTradeMessage(String address, byte[] publicKey, byte[] hashOfSecretA, int lockTimeA, int refundTimeout) {
|
||||||
public void testOfferCancelInvalidLength() throws DataException {
|
return DigibyteACCTv3.buildTradeMessage(address, publicKey, hashOfSecretA, lockTimeA, refundTimeout);
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
|
|
||||||
// Instead of sending creator's address to AT, send too-short/invalid message
|
|
||||||
byte[] messageData = new byte[7];
|
|
||||||
RANDOM.nextBytes(messageData);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress);
|
|
||||||
long messageFee = messageTransaction.getTransactionData().getFee();
|
|
||||||
|
|
||||||
// AT should process 'cancel' message in next block
|
|
||||||
// As message is too short, it will be padded to 32bytes but cancel code doesn't care about message content, so should be ok
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertTrue(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in CANCELLED mode
|
|
||||||
CrossChainTradeData tradeData = DigibyteACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.CANCELLED, tradeData.mode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Override
|
||||||
@Test
|
protected byte[] buildRedeemMessage(byte[] secretA, String address) {
|
||||||
public void testTradingInfoProcessing() throws DataException {
|
return DigibyteACCTv3.buildRedeemMessage(secretA, address);
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = DigibyteACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = DigibyteACCTv3.buildTradeMessage(partner.getAddress(), digibytePublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
Block postDeploymentBlock = BlockUtils.mintBlock(repository);
|
|
||||||
int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight();
|
|
||||||
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
CrossChainTradeData tradeData = DigibyteACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
|
|
||||||
// AT should be in TRADE mode
|
|
||||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
|
||||||
|
|
||||||
// Check hashOfSecretA was extracted correctly
|
|
||||||
assertTrue(Arrays.equals(hashOfSecretA, tradeData.hashOfSecretA));
|
|
||||||
|
|
||||||
// Check trade partner Qortal address was extracted correctly
|
|
||||||
assertEquals(partner.getAddress(), tradeData.qortalPartnerAddress);
|
|
||||||
|
|
||||||
// Check trade partner's digibyte PKH was extracted correctly
|
|
||||||
assertTrue(Arrays.equals(digibytePublicKeyHash, tradeData.partnerForeignPKH));
|
|
||||||
|
|
||||||
// Test orphaning
|
|
||||||
BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = deployersPostDeploymentBalance;
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TEST SENDING TRADING INFO BUT NOT FROM AT CREATOR (SHOULD BE IGNORED)
|
@Override
|
||||||
@SuppressWarnings("unused")
|
protected byte[] getCodeBytesHash() {
|
||||||
@Test
|
return DigibyteACCTv3.CODE_BYTES_HASH;
|
||||||
public void testIncorrectTradeSender() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = DigibyteACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT BUT NOT FROM AT CREATOR
|
|
||||||
byte[] messageData = DigibyteACCTv3.buildTradeMessage(partner.getAddress(), digibytePublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, bystander, messageData, atAddress);
|
|
||||||
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
long expectedBalance = partnersInitialBalance;
|
|
||||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-initial-payout balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
CrossChainTradeData tradeData = DigibyteACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
|
|
||||||
// AT should still be in OFFER mode
|
|
||||||
assertEquals(AcctMode.OFFERING, tradeData.mode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Override
|
||||||
@Test
|
protected String getSymbol() {
|
||||||
public void testAutomaticTradeRefund() throws DataException {
|
return SYMBOL;
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = DigibyteACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = DigibyteACCTv3.buildTradeMessage(partner.getAddress(), digibytePublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
Block postDeploymentBlock = BlockUtils.mintBlock(repository);
|
|
||||||
int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight();
|
|
||||||
|
|
||||||
// Check refund
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
|
|
||||||
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertTrue(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in REFUNDED mode
|
|
||||||
CrossChainTradeData tradeData = DigibyteACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.REFUNDED, tradeData.mode);
|
|
||||||
|
|
||||||
// Test orphaning
|
|
||||||
BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = deployersPostDeploymentBalance;
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Override
|
||||||
@Test
|
protected String getName() {
|
||||||
public void testCorrectSecretCorrectSender() throws DataException {
|
return NAME;
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = DigibyteACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = DigibyteACCTv3.buildTradeMessage(partner.getAddress(), digibytePublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
// Give AT time to process message
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// Send correct secret to AT, from correct account
|
|
||||||
messageData = DigibyteACCTv3.buildRedeemMessage(secretA, partner.getAddress());
|
|
||||||
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
|
||||||
|
|
||||||
// AT should send funds in the next block
|
|
||||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertTrue(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in REDEEMED mode
|
|
||||||
CrossChainTradeData tradeData = DigibyteACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.REDEEMED, tradeData.mode);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee() + redeemAmount;
|
|
||||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-redeem balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Orphan redeem
|
|
||||||
BlockUtils.orphanLastBlock(repository);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee();
|
|
||||||
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-orphan/pre-redeem balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Check AT state
|
|
||||||
ATStateData postOrphanAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
|
|
||||||
assertTrue("AT states mismatch", Arrays.equals(preRedeemAtStateData.getStateData(), postOrphanAtStateData.getStateData()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testCorrectSecretIncorrectSender() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = DigibyteACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = DigibyteACCTv3.buildTradeMessage(partner.getAddress(), digibytePublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
// Give AT time to process message
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// Send correct secret to AT, but from wrong account
|
|
||||||
messageData = DigibyteACCTv3.buildRedeemMessage(secretA, partner.getAddress());
|
|
||||||
messageTransaction = sendMessage(repository, bystander, messageData, atAddress);
|
|
||||||
|
|
||||||
// AT should NOT send funds in the next block
|
|
||||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is NOT finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertFalse(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should still be in TRADE mode
|
|
||||||
CrossChainTradeData tradeData = DigibyteACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = partnersInitialBalance;
|
|
||||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Check eventual refund
|
|
||||||
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testIncorrectSecretCorrectSender() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = DigibyteACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = DigibyteACCTv3.buildTradeMessage(partner.getAddress(), digibytePublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
// Give AT time to process message
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// Send incorrect secret to AT, from correct account
|
|
||||||
byte[] wrongSecret = new byte[32];
|
|
||||||
RANDOM.nextBytes(wrongSecret);
|
|
||||||
messageData = DigibyteACCTv3.buildRedeemMessage(wrongSecret, partner.getAddress());
|
|
||||||
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
|
||||||
|
|
||||||
// AT should NOT send funds in the next block
|
|
||||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is NOT finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertFalse(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should still be in TRADE mode
|
|
||||||
CrossChainTradeData tradeData = DigibyteACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
|
||||||
|
|
||||||
long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee();
|
|
||||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Check eventual refund
|
|
||||||
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testCorrectSecretCorrectSenderInvalidMessageLength() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = DigibyteACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = DigibyteACCTv3.buildTradeMessage(partner.getAddress(), digibytePublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
// Give AT time to process message
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// Send correct secret to AT, from correct account, but missing receive address, hence incorrect length
|
|
||||||
messageData = Bytes.concat(secretA);
|
|
||||||
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
|
||||||
|
|
||||||
// AT should NOT send funds in the next block
|
|
||||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is NOT finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertFalse(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in TRADING mode
|
|
||||||
CrossChainTradeData tradeData = DigibyteACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testDescribeDeployed() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
|
|
||||||
List<ATData> executableAts = repository.getATRepository().getAllExecutableATs();
|
|
||||||
|
|
||||||
for (ATData atData : executableAts) {
|
|
||||||
String atAddress = atData.getATAddress();
|
|
||||||
byte[] codeBytes = atData.getCodeBytes();
|
|
||||||
byte[] codeHash = Crypto.digest(codeBytes);
|
|
||||||
|
|
||||||
System.out.println(String.format("%s: code length: %d byte%s, code hash: %s",
|
|
||||||
atAddress,
|
|
||||||
codeBytes.length,
|
|
||||||
(codeBytes.length != 1 ? "s": ""),
|
|
||||||
HashCode.fromBytes(codeHash)));
|
|
||||||
|
|
||||||
// Not one of ours?
|
|
||||||
if (!Arrays.equals(codeHash, DigibyteACCTv3.CODE_BYTES_HASH))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int calcTestLockTimeA(long messageTimestamp) {
|
|
||||||
return (int) (messageTimestamp / 1000L + tradeTimeout * 60);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer, String tradeAddress) throws DataException {
|
|
||||||
byte[] creationBytes = DigibyteACCTv3.buildQortalAT(tradeAddress, digibytePublicKeyHash, redeemAmount, digibyteAmount, tradeTimeout);
|
|
||||||
|
|
||||||
long txTimestamp = System.currentTimeMillis();
|
|
||||||
byte[] lastReference = deployer.getLastReference();
|
|
||||||
|
|
||||||
if (lastReference == null) {
|
|
||||||
System.err.println(String.format("Qortal account %s has no last reference", deployer.getAddress()));
|
|
||||||
System.exit(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
Long fee = null;
|
|
||||||
String name = "QORT-DGB cross-chain trade";
|
|
||||||
String description = String.format("Qortal-Digibyte cross-chain trade");
|
|
||||||
String atType = "ACCT";
|
|
||||||
String tags = "QORT-DGB ACCT";
|
|
||||||
|
|
||||||
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, deployer.getPublicKey(), fee, null);
|
|
||||||
TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, atType, tags, creationBytes, fundingAmount, Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData);
|
|
||||||
|
|
||||||
fee = deployAtTransaction.calcRecommendedFee();
|
|
||||||
deployAtTransactionData.setFee(fee);
|
|
||||||
|
|
||||||
TransactionUtils.signAndMint(repository, deployAtTransactionData, deployer);
|
|
||||||
|
|
||||||
return deployAtTransaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
private MessageTransaction sendMessage(Repository repository, PrivateKeyAccount sender, byte[] data, String recipient) throws DataException {
|
|
||||||
long txTimestamp = System.currentTimeMillis();
|
|
||||||
byte[] lastReference = sender.getLastReference();
|
|
||||||
|
|
||||||
if (lastReference == null) {
|
|
||||||
System.err.println(String.format("Qortal account %s has no last reference", sender.getAddress()));
|
|
||||||
System.exit(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
Long fee = null;
|
|
||||||
int version = 4;
|
|
||||||
int nonce = 0;
|
|
||||||
long amount = 0;
|
|
||||||
Long assetId = null; // because amount is zero
|
|
||||||
|
|
||||||
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, sender.getPublicKey(), fee, null);
|
|
||||||
TransactionData messageTransactionData = new MessageTransactionData(baseTransactionData, version, nonce, recipient, amount, assetId, data, false, false);
|
|
||||||
|
|
||||||
MessageTransaction messageTransaction = new MessageTransaction(repository, messageTransactionData);
|
|
||||||
|
|
||||||
fee = messageTransaction.calcRecommendedFee();
|
|
||||||
messageTransactionData.setFee(fee);
|
|
||||||
|
|
||||||
TransactionUtils.signAndMint(repository, messageTransactionData, sender);
|
|
||||||
|
|
||||||
return messageTransaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkTradeRefund(Repository repository, Account deployer, long deployersInitialBalance, long deployAtFee) throws DataException {
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
int refundTimeout = tradeTimeout / 2 + 1; // close enough
|
|
||||||
|
|
||||||
// AT should automatically refund deployer after 'refundTimeout' blocks
|
|
||||||
for (int blockCount = 0; blockCount <= refundTimeout; ++blockCount)
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// We don't bother to exactly calculate QORT spent running AT for several blocks, but we do know the expected range
|
|
||||||
long expectedMinimumBalance = deployersPostDeploymentBalance;
|
|
||||||
long expectedMaximumBalance = deployersInitialBalance - deployAtFee;
|
|
||||||
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance);
|
|
||||||
assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void describeAt(Repository repository, String atAddress) throws DataException {
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
CrossChainTradeData tradeData = DigibyteACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
|
|
||||||
Function<Long, String> epochMilliFormatter = (timestamp) -> LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneOffset.UTC).format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM));
|
|
||||||
int currentBlockHeight = repository.getBlockRepository().getBlockchainHeight();
|
|
||||||
|
|
||||||
System.out.print(String.format("%s:\n"
|
|
||||||
+ "\tmode: %s\n"
|
|
||||||
+ "\tcreator: %s,\n"
|
|
||||||
+ "\tcreation timestamp: %s,\n"
|
|
||||||
+ "\tcurrent balance: %s QORT,\n"
|
|
||||||
+ "\tis finished: %b,\n"
|
|
||||||
+ "\tredeem payout: %s QORT,\n"
|
|
||||||
+ "\texpected digibyte: %s DGB,\n"
|
|
||||||
+ "\tcurrent block height: %d,\n",
|
|
||||||
tradeData.qortalAtAddress,
|
|
||||||
tradeData.mode,
|
|
||||||
tradeData.qortalCreator,
|
|
||||||
epochMilliFormatter.apply(tradeData.creationTimestamp),
|
|
||||||
Amounts.prettyAmount(tradeData.qortBalance),
|
|
||||||
atData.getIsFinished(),
|
|
||||||
Amounts.prettyAmount(tradeData.qortAmount),
|
|
||||||
Amounts.prettyAmount(tradeData.expectedForeignAmount),
|
|
||||||
currentBlockHeight));
|
|
||||||
|
|
||||||
if (tradeData.mode != AcctMode.OFFERING && tradeData.mode != AcctMode.CANCELLED) {
|
|
||||||
System.out.println(String.format("\trefund timeout: %d minutes,\n"
|
|
||||||
+ "\trefund height: block %d,\n"
|
|
||||||
+ "\tHASH160 of secret-A: %s,\n"
|
|
||||||
+ "\tDigibyte P2SH-A nLockTime: %d (%s),\n"
|
|
||||||
+ "\ttrade partner: %s\n"
|
|
||||||
+ "\tpartner's receiving address: %s",
|
|
||||||
tradeData.refundTimeout,
|
|
||||||
tradeData.tradeRefundHeight,
|
|
||||||
HashCode.fromBytes(tradeData.hashOfSecretA).toString().substring(0, 40),
|
|
||||||
tradeData.lockTimeA, epochMilliFormatter.apply(tradeData.lockTimeA * 1000L),
|
|
||||||
tradeData.qortalPartnerAddress,
|
|
||||||
tradeData.qortalPartnerReceivingAddress));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private PrivateKeyAccount createTradeAccount(Repository repository) {
|
|
||||||
// We actually use a known test account with funds to avoid PoW compute
|
|
||||||
return Common.getTestAccount(repository, "alice");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,769 +1,58 @@
|
|||||||
package org.qortal.test.crosschain.dogecoinv3;
|
package org.qortal.test.crosschain.dogecoinv3;
|
||||||
|
|
||||||
import com.google.common.hash.HashCode;
|
import com.google.common.hash.HashCode;
|
||||||
import com.google.common.primitives.Bytes;
|
import org.qortal.crosschain.ACCT;
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.qortal.account.Account;
|
|
||||||
import org.qortal.account.PrivateKeyAccount;
|
|
||||||
import org.qortal.asset.Asset;
|
|
||||||
import org.qortal.block.Block;
|
|
||||||
import org.qortal.crosschain.AcctMode;
|
|
||||||
import org.qortal.crosschain.DogecoinACCTv3;
|
import org.qortal.crosschain.DogecoinACCTv3;
|
||||||
import org.qortal.crypto.Crypto;
|
import org.qortal.test.crosschain.ACCTTests;
|
||||||
import org.qortal.data.at.ATData;
|
|
||||||
import org.qortal.data.at.ATStateData;
|
|
||||||
import org.qortal.data.crosschain.CrossChainTradeData;
|
|
||||||
import org.qortal.data.transaction.BaseTransactionData;
|
|
||||||
import org.qortal.data.transaction.DeployAtTransactionData;
|
|
||||||
import org.qortal.data.transaction.MessageTransactionData;
|
|
||||||
import org.qortal.data.transaction.TransactionData;
|
|
||||||
import org.qortal.group.Group;
|
|
||||||
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.TransactionUtils;
|
|
||||||
import org.qortal.transaction.DeployAtTransaction;
|
|
||||||
import org.qortal.transaction.MessageTransaction;
|
|
||||||
import org.qortal.utils.Amounts;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
public class DogecoinACCTv3Tests extends ACCTTests {
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.ZoneOffset;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.time.format.FormatStyle;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Random;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
public class DogecoinACCTv3Tests extends Common {
|
|
||||||
|
|
||||||
public static final byte[] secretA = "This string is exactly 32 bytes!".getBytes();
|
|
||||||
public static final byte[] hashOfSecretA = Crypto.hash160(secretA); // daf59884b4d1aec8c1b17102530909ee43c0151a
|
|
||||||
public static final byte[] dogecoinPublicKeyHash = HashCode.fromString("bb00bb11bb22bb33bb44bb55bb66bb77bb88bb99").asBytes();
|
public static final byte[] dogecoinPublicKeyHash = HashCode.fromString("bb00bb11bb22bb33bb44bb55bb66bb77bb88bb99").asBytes();
|
||||||
public static final int tradeTimeout = 20; // blocks
|
private static final String SYMBOL = "DOGE";
|
||||||
public static final long redeemAmount = 80_40200000L;
|
private static final String NAME = "Dogecoin";
|
||||||
public static final long fundingAmount = 123_45600000L;
|
|
||||||
public static final long dogecoinAmount = 864200L; // 0.00864200 DOGE
|
|
||||||
|
|
||||||
private static final Random RANDOM = new Random();
|
@Override
|
||||||
|
protected byte[] getPublicKey() {
|
||||||
@Before
|
return dogecoinPublicKeyHash;
|
||||||
public void beforeTest() throws DataException {
|
|
||||||
Common.useDefaultSettings();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Override
|
||||||
public void testCompile() {
|
protected byte[] buildQortalAT(String address, byte[] publicKey, long redeemAmount, long foreignAmount, int tradeTimeout) {
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(null);
|
return DogecoinACCTv3.buildQortalAT(address, publicKey, redeemAmount, foreignAmount, tradeTimeout);
|
||||||
|
|
||||||
byte[] creationBytes = DogecoinACCTv3.buildQortalAT(tradeAccount.getAddress(), dogecoinPublicKeyHash, redeemAmount, dogecoinAmount, tradeTimeout);
|
|
||||||
assertNotNull(creationBytes);
|
|
||||||
|
|
||||||
System.out.println("AT creation bytes: " + HashCode.fromBytes(creationBytes).toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Override
|
||||||
public void testDeploy() throws DataException {
|
protected ACCT getInstance() {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
return DogecoinACCTv3.getInstance();
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
|
|
||||||
long expectedBalance = deployersInitialBalance - fundingAmount - deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
expectedBalance = fundingAmount;
|
|
||||||
actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("AT's post-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
expectedBalance = partnersInitialBalance;
|
|
||||||
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Test orphaning
|
|
||||||
BlockUtils.orphanLastBlock(repository);
|
|
||||||
|
|
||||||
expectedBalance = deployersInitialBalance;
|
|
||||||
actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
expectedBalance = 0;
|
|
||||||
actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("AT's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
expectedBalance = partnersInitialBalance;
|
|
||||||
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Override
|
||||||
@Test
|
protected int calcRefundTimeout(long partnersOfferMessageTransactionTimestamp, int lockTimeA) {
|
||||||
public void testOfferCancel() throws DataException {
|
return DogecoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
|
|
||||||
// Send creator's address to AT, instead of typical partner's address
|
|
||||||
byte[] messageData = DogecoinACCTv3.getInstance().buildCancelMessage(deployer.getAddress());
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress);
|
|
||||||
long messageFee = messageTransaction.getTransactionData().getFee();
|
|
||||||
|
|
||||||
// AT should process 'cancel' message in next block
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertTrue(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in CANCELLED mode
|
|
||||||
CrossChainTradeData tradeData = DogecoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.CANCELLED, tradeData.mode);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedMinimumBalance = deployersPostDeploymentBalance;
|
|
||||||
long expectedMaximumBalance = deployersInitialBalance - deployAtFee - messageFee;
|
|
||||||
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance);
|
|
||||||
assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance);
|
|
||||||
|
|
||||||
// Test orphaning
|
|
||||||
BlockUtils.orphanLastBlock(repository);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = deployersPostDeploymentBalance - messageFee;
|
|
||||||
actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Override
|
||||||
@Test
|
protected byte[] buildTradeMessage(String address, byte[] publicKey, byte[] hashOfSecretA, int lockTimeA, int refundTimeout) {
|
||||||
public void testOfferCancelInvalidLength() throws DataException {
|
return DogecoinACCTv3.buildTradeMessage(address, publicKey, hashOfSecretA, lockTimeA, refundTimeout);
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
|
|
||||||
// Instead of sending creator's address to AT, send too-short/invalid message
|
|
||||||
byte[] messageData = new byte[7];
|
|
||||||
RANDOM.nextBytes(messageData);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress);
|
|
||||||
long messageFee = messageTransaction.getTransactionData().getFee();
|
|
||||||
|
|
||||||
// AT should process 'cancel' message in next block
|
|
||||||
// As message is too short, it will be padded to 32bytes but cancel code doesn't care about message content, so should be ok
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertTrue(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in CANCELLED mode
|
|
||||||
CrossChainTradeData tradeData = DogecoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.CANCELLED, tradeData.mode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Override
|
||||||
@Test
|
protected byte[] buildRedeemMessage(byte[] secretA, String address) {
|
||||||
public void testTradingInfoProcessing() throws DataException {
|
return DogecoinACCTv3.buildRedeemMessage(secretA, address);
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = DogecoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = DogecoinACCTv3.buildTradeMessage(partner.getAddress(), dogecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
Block postDeploymentBlock = BlockUtils.mintBlock(repository);
|
|
||||||
int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight();
|
|
||||||
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
CrossChainTradeData tradeData = DogecoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
|
|
||||||
// AT should be in TRADE mode
|
|
||||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
|
||||||
|
|
||||||
// Check hashOfSecretA was extracted correctly
|
|
||||||
assertTrue(Arrays.equals(hashOfSecretA, tradeData.hashOfSecretA));
|
|
||||||
|
|
||||||
// Check trade partner Qortal address was extracted correctly
|
|
||||||
assertEquals(partner.getAddress(), tradeData.qortalPartnerAddress);
|
|
||||||
|
|
||||||
// Check trade partner's dogecoin PKH was extracted correctly
|
|
||||||
assertTrue(Arrays.equals(dogecoinPublicKeyHash, tradeData.partnerForeignPKH));
|
|
||||||
|
|
||||||
// Test orphaning
|
|
||||||
BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = deployersPostDeploymentBalance;
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TEST SENDING TRADING INFO BUT NOT FROM AT CREATOR (SHOULD BE IGNORED)
|
@Override
|
||||||
@SuppressWarnings("unused")
|
protected byte[] getCodeBytesHash() {
|
||||||
@Test
|
return DogecoinACCTv3.CODE_BYTES_HASH;
|
||||||
public void testIncorrectTradeSender() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = DogecoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT BUT NOT FROM AT CREATOR
|
|
||||||
byte[] messageData = DogecoinACCTv3.buildTradeMessage(partner.getAddress(), dogecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, bystander, messageData, atAddress);
|
|
||||||
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
long expectedBalance = partnersInitialBalance;
|
|
||||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-initial-payout balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
CrossChainTradeData tradeData = DogecoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
|
|
||||||
// AT should still be in OFFER mode
|
|
||||||
assertEquals(AcctMode.OFFERING, tradeData.mode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Override
|
||||||
@Test
|
protected String getSymbol() {
|
||||||
public void testAutomaticTradeRefund() throws DataException {
|
return SYMBOL;
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = DogecoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = DogecoinACCTv3.buildTradeMessage(partner.getAddress(), dogecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
Block postDeploymentBlock = BlockUtils.mintBlock(repository);
|
|
||||||
int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight();
|
|
||||||
|
|
||||||
// Check refund
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
|
|
||||||
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertTrue(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in REFUNDED mode
|
|
||||||
CrossChainTradeData tradeData = DogecoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.REFUNDED, tradeData.mode);
|
|
||||||
|
|
||||||
// Test orphaning
|
|
||||||
BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = deployersPostDeploymentBalance;
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Override
|
||||||
@Test
|
protected String getName() {
|
||||||
public void testCorrectSecretCorrectSender() throws DataException {
|
return NAME;
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = DogecoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = DogecoinACCTv3.buildTradeMessage(partner.getAddress(), dogecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
// Give AT time to process message
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// Send correct secret to AT, from correct account
|
|
||||||
messageData = DogecoinACCTv3.buildRedeemMessage(secretA, partner.getAddress());
|
|
||||||
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
|
||||||
|
|
||||||
// AT should send funds in the next block
|
|
||||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertTrue(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in REDEEMED mode
|
|
||||||
CrossChainTradeData tradeData = DogecoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.REDEEMED, tradeData.mode);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee() + redeemAmount;
|
|
||||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-redeem balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Orphan redeem
|
|
||||||
BlockUtils.orphanLastBlock(repository);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee();
|
|
||||||
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-orphan/pre-redeem balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Check AT state
|
|
||||||
ATStateData postOrphanAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
|
|
||||||
assertTrue("AT states mismatch", Arrays.equals(preRedeemAtStateData.getStateData(), postOrphanAtStateData.getStateData()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testCorrectSecretIncorrectSender() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = DogecoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = DogecoinACCTv3.buildTradeMessage(partner.getAddress(), dogecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
// Give AT time to process message
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// Send correct secret to AT, but from wrong account
|
|
||||||
messageData = DogecoinACCTv3.buildRedeemMessage(secretA, partner.getAddress());
|
|
||||||
messageTransaction = sendMessage(repository, bystander, messageData, atAddress);
|
|
||||||
|
|
||||||
// AT should NOT send funds in the next block
|
|
||||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is NOT finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertFalse(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should still be in TRADE mode
|
|
||||||
CrossChainTradeData tradeData = DogecoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = partnersInitialBalance;
|
|
||||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Check eventual refund
|
|
||||||
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testIncorrectSecretCorrectSender() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = DogecoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = DogecoinACCTv3.buildTradeMessage(partner.getAddress(), dogecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
// Give AT time to process message
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// Send incorrect secret to AT, from correct account
|
|
||||||
byte[] wrongSecret = new byte[32];
|
|
||||||
RANDOM.nextBytes(wrongSecret);
|
|
||||||
messageData = DogecoinACCTv3.buildRedeemMessage(wrongSecret, partner.getAddress());
|
|
||||||
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
|
||||||
|
|
||||||
// AT should NOT send funds in the next block
|
|
||||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is NOT finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertFalse(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should still be in TRADE mode
|
|
||||||
CrossChainTradeData tradeData = DogecoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
|
||||||
|
|
||||||
long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee();
|
|
||||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Check eventual refund
|
|
||||||
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testCorrectSecretCorrectSenderInvalidMessageLength() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = DogecoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = DogecoinACCTv3.buildTradeMessage(partner.getAddress(), dogecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
// Give AT time to process message
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// Send correct secret to AT, from correct account, but missing receive address, hence incorrect length
|
|
||||||
messageData = Bytes.concat(secretA);
|
|
||||||
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
|
||||||
|
|
||||||
// AT should NOT send funds in the next block
|
|
||||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is NOT finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertFalse(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in TRADING mode
|
|
||||||
CrossChainTradeData tradeData = DogecoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testDescribeDeployed() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
|
|
||||||
List<ATData> executableAts = repository.getATRepository().getAllExecutableATs();
|
|
||||||
|
|
||||||
for (ATData atData : executableAts) {
|
|
||||||
String atAddress = atData.getATAddress();
|
|
||||||
byte[] codeBytes = atData.getCodeBytes();
|
|
||||||
byte[] codeHash = Crypto.digest(codeBytes);
|
|
||||||
|
|
||||||
System.out.println(String.format("%s: code length: %d byte%s, code hash: %s",
|
|
||||||
atAddress,
|
|
||||||
codeBytes.length,
|
|
||||||
(codeBytes.length != 1 ? "s": ""),
|
|
||||||
HashCode.fromBytes(codeHash)));
|
|
||||||
|
|
||||||
// Not one of ours?
|
|
||||||
if (!Arrays.equals(codeHash, DogecoinACCTv3.CODE_BYTES_HASH))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int calcTestLockTimeA(long messageTimestamp) {
|
|
||||||
return (int) (messageTimestamp / 1000L + tradeTimeout * 60);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer, String tradeAddress) throws DataException {
|
|
||||||
byte[] creationBytes = DogecoinACCTv3.buildQortalAT(tradeAddress, dogecoinPublicKeyHash, redeemAmount, dogecoinAmount, tradeTimeout);
|
|
||||||
|
|
||||||
long txTimestamp = System.currentTimeMillis();
|
|
||||||
byte[] lastReference = deployer.getLastReference();
|
|
||||||
|
|
||||||
if (lastReference == null) {
|
|
||||||
System.err.println(String.format("Qortal account %s has no last reference", deployer.getAddress()));
|
|
||||||
System.exit(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
Long fee = null;
|
|
||||||
String name = "QORT-DOGE cross-chain trade";
|
|
||||||
String description = String.format("Qortal-Dogecoin cross-chain trade");
|
|
||||||
String atType = "ACCT";
|
|
||||||
String tags = "QORT-DOGE ACCT";
|
|
||||||
|
|
||||||
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, deployer.getPublicKey(), fee, null);
|
|
||||||
TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, atType, tags, creationBytes, fundingAmount, Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData);
|
|
||||||
|
|
||||||
fee = deployAtTransaction.calcRecommendedFee();
|
|
||||||
deployAtTransactionData.setFee(fee);
|
|
||||||
|
|
||||||
TransactionUtils.signAndMint(repository, deployAtTransactionData, deployer);
|
|
||||||
|
|
||||||
return deployAtTransaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
private MessageTransaction sendMessage(Repository repository, PrivateKeyAccount sender, byte[] data, String recipient) throws DataException {
|
|
||||||
long txTimestamp = System.currentTimeMillis();
|
|
||||||
byte[] lastReference = sender.getLastReference();
|
|
||||||
|
|
||||||
if (lastReference == null) {
|
|
||||||
System.err.println(String.format("Qortal account %s has no last reference", sender.getAddress()));
|
|
||||||
System.exit(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
Long fee = null;
|
|
||||||
int version = 4;
|
|
||||||
int nonce = 0;
|
|
||||||
long amount = 0;
|
|
||||||
Long assetId = null; // because amount is zero
|
|
||||||
|
|
||||||
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, sender.getPublicKey(), fee, null);
|
|
||||||
TransactionData messageTransactionData = new MessageTransactionData(baseTransactionData, version, nonce, recipient, amount, assetId, data, false, false);
|
|
||||||
|
|
||||||
MessageTransaction messageTransaction = new MessageTransaction(repository, messageTransactionData);
|
|
||||||
|
|
||||||
fee = messageTransaction.calcRecommendedFee();
|
|
||||||
messageTransactionData.setFee(fee);
|
|
||||||
|
|
||||||
TransactionUtils.signAndMint(repository, messageTransactionData, sender);
|
|
||||||
|
|
||||||
return messageTransaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkTradeRefund(Repository repository, Account deployer, long deployersInitialBalance, long deployAtFee) throws DataException {
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
int refundTimeout = tradeTimeout / 2 + 1; // close enough
|
|
||||||
|
|
||||||
// AT should automatically refund deployer after 'refundTimeout' blocks
|
|
||||||
for (int blockCount = 0; blockCount <= refundTimeout; ++blockCount)
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// We don't bother to exactly calculate QORT spent running AT for several blocks, but we do know the expected range
|
|
||||||
long expectedMinimumBalance = deployersPostDeploymentBalance;
|
|
||||||
long expectedMaximumBalance = deployersInitialBalance - deployAtFee;
|
|
||||||
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance);
|
|
||||||
assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void describeAt(Repository repository, String atAddress) throws DataException {
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
CrossChainTradeData tradeData = DogecoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
|
|
||||||
Function<Long, String> epochMilliFormatter = (timestamp) -> LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneOffset.UTC).format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM));
|
|
||||||
int currentBlockHeight = repository.getBlockRepository().getBlockchainHeight();
|
|
||||||
|
|
||||||
System.out.print(String.format("%s:\n"
|
|
||||||
+ "\tmode: %s\n"
|
|
||||||
+ "\tcreator: %s,\n"
|
|
||||||
+ "\tcreation timestamp: %s,\n"
|
|
||||||
+ "\tcurrent balance: %s QORT,\n"
|
|
||||||
+ "\tis finished: %b,\n"
|
|
||||||
+ "\tredeem payout: %s QORT,\n"
|
|
||||||
+ "\texpected dogecoin: %s DOGE,\n"
|
|
||||||
+ "\tcurrent block height: %d,\n",
|
|
||||||
tradeData.qortalAtAddress,
|
|
||||||
tradeData.mode,
|
|
||||||
tradeData.qortalCreator,
|
|
||||||
epochMilliFormatter.apply(tradeData.creationTimestamp),
|
|
||||||
Amounts.prettyAmount(tradeData.qortBalance),
|
|
||||||
atData.getIsFinished(),
|
|
||||||
Amounts.prettyAmount(tradeData.qortAmount),
|
|
||||||
Amounts.prettyAmount(tradeData.expectedForeignAmount),
|
|
||||||
currentBlockHeight));
|
|
||||||
|
|
||||||
if (tradeData.mode != AcctMode.OFFERING && tradeData.mode != AcctMode.CANCELLED) {
|
|
||||||
System.out.println(String.format("\trefund timeout: %d minutes,\n"
|
|
||||||
+ "\trefund height: block %d,\n"
|
|
||||||
+ "\tHASH160 of secret-A: %s,\n"
|
|
||||||
+ "\tDogecoin P2SH-A nLockTime: %d (%s),\n"
|
|
||||||
+ "\ttrade partner: %s\n"
|
|
||||||
+ "\tpartner's receiving address: %s",
|
|
||||||
tradeData.refundTimeout,
|
|
||||||
tradeData.tradeRefundHeight,
|
|
||||||
HashCode.fromBytes(tradeData.hashOfSecretA).toString().substring(0, 40),
|
|
||||||
tradeData.lockTimeA, epochMilliFormatter.apply(tradeData.lockTimeA * 1000L),
|
|
||||||
tradeData.qortalPartnerAddress,
|
|
||||||
tradeData.qortalPartnerReceivingAddress));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private PrivateKeyAccount createTradeAccount(Repository repository) {
|
|
||||||
// We actually use a known test account with funds to avoid PoW compute
|
|
||||||
return Common.getTestAccount(repository, "alice");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,770 +1,60 @@
|
|||||||
package org.qortal.test.crosschain.litecoinv1;
|
package org.qortal.test.crosschain.litecoinv1;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import org.qortal.crosschain.ACCT;
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.ZoneOffset;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.time.format.FormatStyle;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Random;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.qortal.account.Account;
|
|
||||||
import org.qortal.account.PrivateKeyAccount;
|
|
||||||
import org.qortal.asset.Asset;
|
|
||||||
import org.qortal.block.Block;
|
|
||||||
import org.qortal.crosschain.LitecoinACCTv1;
|
import org.qortal.crosschain.LitecoinACCTv1;
|
||||||
import org.qortal.crosschain.AcctMode;
|
import org.qortal.test.crosschain.ACCTTests;
|
||||||
import org.qortal.crypto.Crypto;
|
|
||||||
import org.qortal.data.at.ATData;
|
|
||||||
import org.qortal.data.at.ATStateData;
|
|
||||||
import org.qortal.data.crosschain.CrossChainTradeData;
|
|
||||||
import org.qortal.data.transaction.BaseTransactionData;
|
|
||||||
import org.qortal.data.transaction.DeployAtTransactionData;
|
|
||||||
import org.qortal.data.transaction.MessageTransactionData;
|
|
||||||
import org.qortal.data.transaction.TransactionData;
|
|
||||||
import org.qortal.group.Group;
|
|
||||||
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.TransactionUtils;
|
|
||||||
import org.qortal.transaction.DeployAtTransaction;
|
|
||||||
import org.qortal.transaction.MessageTransaction;
|
|
||||||
import org.qortal.utils.Amounts;
|
|
||||||
|
|
||||||
import com.google.common.hash.HashCode;
|
import com.google.common.hash.HashCode;
|
||||||
import com.google.common.primitives.Bytes;
|
|
||||||
|
|
||||||
public class LitecoinACCTv1Tests extends Common {
|
public class LitecoinACCTv1Tests extends ACCTTests {
|
||||||
|
|
||||||
public static final byte[] secretA = "This string is exactly 32 bytes!".getBytes();
|
|
||||||
public static final byte[] hashOfSecretA = Crypto.hash160(secretA); // daf59884b4d1aec8c1b17102530909ee43c0151a
|
|
||||||
public static final byte[] litecoinPublicKeyHash = HashCode.fromString("bb00bb11bb22bb33bb44bb55bb66bb77bb88bb99").asBytes();
|
public static final byte[] litecoinPublicKeyHash = HashCode.fromString("bb00bb11bb22bb33bb44bb55bb66bb77bb88bb99").asBytes();
|
||||||
public static final int tradeTimeout = 20; // blocks
|
private static final String SYMBOL = "LTC";
|
||||||
public static final long redeemAmount = 80_40200000L;
|
|
||||||
public static final long fundingAmount = 123_45600000L;
|
|
||||||
public static final long litecoinAmount = 864200L; // 0.00864200 LTC
|
|
||||||
|
|
||||||
private static final Random RANDOM = new Random();
|
private static final String NAME = "Litecoin";
|
||||||
|
|
||||||
@Before
|
@Override
|
||||||
public void beforeTest() throws DataException {
|
protected byte[] getPublicKey() {
|
||||||
Common.useDefaultSettings();
|
return litecoinPublicKeyHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Override
|
||||||
public void testCompile() {
|
protected byte[] buildQortalAT(String address, byte[] publicKey, long redeemAmount, long foreignAmount, int tradeTimeout) {
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(null);
|
return LitecoinACCTv1.buildQortalAT(address, publicKey, redeemAmount, foreignAmount, tradeTimeout);
|
||||||
|
|
||||||
byte[] creationBytes = LitecoinACCTv1.buildQortalAT(tradeAccount.getAddress(), litecoinPublicKeyHash, redeemAmount, litecoinAmount, tradeTimeout);
|
|
||||||
assertNotNull(creationBytes);
|
|
||||||
|
|
||||||
System.out.println("AT creation bytes: " + HashCode.fromBytes(creationBytes).toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Override
|
||||||
public void testDeploy() throws DataException {
|
protected ACCT getInstance() {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
return LitecoinACCTv1.getInstance();
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
|
|
||||||
long expectedBalance = deployersInitialBalance - fundingAmount - deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
expectedBalance = fundingAmount;
|
|
||||||
actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("AT's post-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
expectedBalance = partnersInitialBalance;
|
|
||||||
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Test orphaning
|
|
||||||
BlockUtils.orphanLastBlock(repository);
|
|
||||||
|
|
||||||
expectedBalance = deployersInitialBalance;
|
|
||||||
actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
expectedBalance = 0;
|
|
||||||
actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("AT's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
expectedBalance = partnersInitialBalance;
|
|
||||||
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Override
|
||||||
@Test
|
protected int calcRefundTimeout(long partnersOfferMessageTransactionTimestamp, int lockTimeA) {
|
||||||
public void testOfferCancel() throws DataException {
|
return LitecoinACCTv1.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
|
|
||||||
// Send creator's address to AT, instead of typical partner's address
|
|
||||||
byte[] messageData = LitecoinACCTv1.getInstance().buildCancelMessage(deployer.getAddress());
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress);
|
|
||||||
long messageFee = messageTransaction.getTransactionData().getFee();
|
|
||||||
|
|
||||||
// AT should process 'cancel' message in next block
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertTrue(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in CANCELLED mode
|
|
||||||
CrossChainTradeData tradeData = LitecoinACCTv1.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.CANCELLED, tradeData.mode);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedMinimumBalance = deployersPostDeploymentBalance;
|
|
||||||
long expectedMaximumBalance = deployersInitialBalance - deployAtFee - messageFee;
|
|
||||||
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance);
|
|
||||||
assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance);
|
|
||||||
|
|
||||||
// Test orphaning
|
|
||||||
BlockUtils.orphanLastBlock(repository);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = deployersPostDeploymentBalance - messageFee;
|
|
||||||
actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Override
|
||||||
@Test
|
protected byte[] buildTradeMessage(String address, byte[] publicKey, byte[] hashOfSecretA, int lockTimeA, int refundTimeout) {
|
||||||
public void testOfferCancelInvalidLength() throws DataException {
|
return LitecoinACCTv1.buildTradeMessage(address, publicKey, hashOfSecretA, lockTimeA, refundTimeout);
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
|
|
||||||
// Instead of sending creator's address to AT, send too-short/invalid message
|
|
||||||
byte[] messageData = new byte[7];
|
|
||||||
RANDOM.nextBytes(messageData);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress);
|
|
||||||
long messageFee = messageTransaction.getTransactionData().getFee();
|
|
||||||
|
|
||||||
// AT should process 'cancel' message in next block
|
|
||||||
// As message is too short, it will be padded to 32bytes but cancel code doesn't care about message content, so should be ok
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertTrue(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in CANCELLED mode
|
|
||||||
CrossChainTradeData tradeData = LitecoinACCTv1.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.CANCELLED, tradeData.mode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Override
|
||||||
@Test
|
protected byte[] buildRedeemMessage(byte[] secretA, String address) {
|
||||||
public void testTradingInfoProcessing() throws DataException {
|
return LitecoinACCTv1.buildRedeemMessage(secretA, address);
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = LitecoinACCTv1.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = LitecoinACCTv1.buildTradeMessage(partner.getAddress(), litecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
Block postDeploymentBlock = BlockUtils.mintBlock(repository);
|
|
||||||
int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight();
|
|
||||||
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
CrossChainTradeData tradeData = LitecoinACCTv1.getInstance().populateTradeData(repository, atData);
|
|
||||||
|
|
||||||
// AT should be in TRADE mode
|
|
||||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
|
||||||
|
|
||||||
// Check hashOfSecretA was extracted correctly
|
|
||||||
assertTrue(Arrays.equals(hashOfSecretA, tradeData.hashOfSecretA));
|
|
||||||
|
|
||||||
// Check trade partner Qortal address was extracted correctly
|
|
||||||
assertEquals(partner.getAddress(), tradeData.qortalPartnerAddress);
|
|
||||||
|
|
||||||
// Check trade partner's Litecoin PKH was extracted correctly
|
|
||||||
assertTrue(Arrays.equals(litecoinPublicKeyHash, tradeData.partnerForeignPKH));
|
|
||||||
|
|
||||||
// Test orphaning
|
|
||||||
BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = deployersPostDeploymentBalance;
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TEST SENDING TRADING INFO BUT NOT FROM AT CREATOR (SHOULD BE IGNORED)
|
@Override
|
||||||
@SuppressWarnings("unused")
|
protected byte[] getCodeBytesHash() {
|
||||||
@Test
|
return LitecoinACCTv1.CODE_BYTES_HASH;
|
||||||
public void testIncorrectTradeSender() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = LitecoinACCTv1.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT BUT NOT FROM AT CREATOR
|
|
||||||
byte[] messageData = LitecoinACCTv1.buildTradeMessage(partner.getAddress(), litecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, bystander, messageData, atAddress);
|
|
||||||
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
long expectedBalance = partnersInitialBalance;
|
|
||||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-initial-payout balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
CrossChainTradeData tradeData = LitecoinACCTv1.getInstance().populateTradeData(repository, atData);
|
|
||||||
|
|
||||||
// AT should still be in OFFER mode
|
|
||||||
assertEquals(AcctMode.OFFERING, tradeData.mode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Override
|
||||||
@Test
|
protected String getSymbol() {
|
||||||
public void testAutomaticTradeRefund() throws DataException {
|
return SYMBOL;
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = LitecoinACCTv1.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = LitecoinACCTv1.buildTradeMessage(partner.getAddress(), litecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
Block postDeploymentBlock = BlockUtils.mintBlock(repository);
|
|
||||||
int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight();
|
|
||||||
|
|
||||||
// Check refund
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
|
|
||||||
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertTrue(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in REFUNDED mode
|
|
||||||
CrossChainTradeData tradeData = LitecoinACCTv1.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.REFUNDED, tradeData.mode);
|
|
||||||
|
|
||||||
// Test orphaning
|
|
||||||
BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = deployersPostDeploymentBalance;
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Override
|
||||||
@Test
|
protected String getName() {
|
||||||
public void testCorrectSecretCorrectSender() throws DataException {
|
return NAME;
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = LitecoinACCTv1.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = LitecoinACCTv1.buildTradeMessage(partner.getAddress(), litecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
// Give AT time to process message
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// Send correct secret to AT, from correct account
|
|
||||||
messageData = LitecoinACCTv1.buildRedeemMessage(secretA, partner.getAddress());
|
|
||||||
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
|
||||||
|
|
||||||
// AT should send funds in the next block
|
|
||||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertTrue(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in REDEEMED mode
|
|
||||||
CrossChainTradeData tradeData = LitecoinACCTv1.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.REDEEMED, tradeData.mode);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee() + redeemAmount;
|
|
||||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-redeem balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Orphan redeem
|
|
||||||
BlockUtils.orphanLastBlock(repository);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee();
|
|
||||||
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-orphan/pre-redeem balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Check AT state
|
|
||||||
ATStateData postOrphanAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
|
|
||||||
assertTrue("AT states mismatch", Arrays.equals(preRedeemAtStateData.getStateData(), postOrphanAtStateData.getStateData()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testCorrectSecretIncorrectSender() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = LitecoinACCTv1.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = LitecoinACCTv1.buildTradeMessage(partner.getAddress(), litecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
// Give AT time to process message
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// Send correct secret to AT, but from wrong account
|
|
||||||
messageData = LitecoinACCTv1.buildRedeemMessage(secretA, partner.getAddress());
|
|
||||||
messageTransaction = sendMessage(repository, bystander, messageData, atAddress);
|
|
||||||
|
|
||||||
// AT should NOT send funds in the next block
|
|
||||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is NOT finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertFalse(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should still be in TRADE mode
|
|
||||||
CrossChainTradeData tradeData = LitecoinACCTv1.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = partnersInitialBalance;
|
|
||||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Check eventual refund
|
|
||||||
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testIncorrectSecretCorrectSender() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = LitecoinACCTv1.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = LitecoinACCTv1.buildTradeMessage(partner.getAddress(), litecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
// Give AT time to process message
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// Send incorrect secret to AT, from correct account
|
|
||||||
byte[] wrongSecret = new byte[32];
|
|
||||||
RANDOM.nextBytes(wrongSecret);
|
|
||||||
messageData = LitecoinACCTv1.buildRedeemMessage(wrongSecret, partner.getAddress());
|
|
||||||
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
|
||||||
|
|
||||||
// AT should NOT send funds in the next block
|
|
||||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is NOT finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertFalse(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should still be in TRADE mode
|
|
||||||
CrossChainTradeData tradeData = LitecoinACCTv1.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
|
||||||
|
|
||||||
long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee();
|
|
||||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Check eventual refund
|
|
||||||
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testCorrectSecretCorrectSenderInvalidMessageLength() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = LitecoinACCTv1.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = LitecoinACCTv1.buildTradeMessage(partner.getAddress(), litecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
// Give AT time to process message
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// Send correct secret to AT, from correct account, but missing receive address, hence incorrect length
|
|
||||||
messageData = Bytes.concat(secretA);
|
|
||||||
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
|
||||||
|
|
||||||
// AT should NOT send funds in the next block
|
|
||||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is NOT finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertFalse(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in TRADING mode
|
|
||||||
CrossChainTradeData tradeData = LitecoinACCTv1.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testDescribeDeployed() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
|
|
||||||
List<ATData> executableAts = repository.getATRepository().getAllExecutableATs();
|
|
||||||
|
|
||||||
for (ATData atData : executableAts) {
|
|
||||||
String atAddress = atData.getATAddress();
|
|
||||||
byte[] codeBytes = atData.getCodeBytes();
|
|
||||||
byte[] codeHash = Crypto.digest(codeBytes);
|
|
||||||
|
|
||||||
System.out.println(String.format("%s: code length: %d byte%s, code hash: %s",
|
|
||||||
atAddress,
|
|
||||||
codeBytes.length,
|
|
||||||
(codeBytes.length != 1 ? "s": ""),
|
|
||||||
HashCode.fromBytes(codeHash)));
|
|
||||||
|
|
||||||
// Not one of ours?
|
|
||||||
if (!Arrays.equals(codeHash, LitecoinACCTv1.CODE_BYTES_HASH))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int calcTestLockTimeA(long messageTimestamp) {
|
|
||||||
return (int) (messageTimestamp / 1000L + tradeTimeout * 60);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer, String tradeAddress) throws DataException {
|
|
||||||
byte[] creationBytes = LitecoinACCTv1.buildQortalAT(tradeAddress, litecoinPublicKeyHash, redeemAmount, litecoinAmount, tradeTimeout);
|
|
||||||
|
|
||||||
long txTimestamp = System.currentTimeMillis();
|
|
||||||
byte[] lastReference = deployer.getLastReference();
|
|
||||||
|
|
||||||
if (lastReference == null) {
|
|
||||||
System.err.println(String.format("Qortal account %s has no last reference", deployer.getAddress()));
|
|
||||||
System.exit(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
Long fee = null;
|
|
||||||
String name = "QORT-LTC cross-chain trade";
|
|
||||||
String description = String.format("Qortal-Litecoin cross-chain trade");
|
|
||||||
String atType = "ACCT";
|
|
||||||
String tags = "QORT-LTC ACCT";
|
|
||||||
|
|
||||||
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, deployer.getPublicKey(), fee, null);
|
|
||||||
TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, atType, tags, creationBytes, fundingAmount, Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData);
|
|
||||||
|
|
||||||
fee = deployAtTransaction.calcRecommendedFee();
|
|
||||||
deployAtTransactionData.setFee(fee);
|
|
||||||
|
|
||||||
TransactionUtils.signAndMint(repository, deployAtTransactionData, deployer);
|
|
||||||
|
|
||||||
return deployAtTransaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
private MessageTransaction sendMessage(Repository repository, PrivateKeyAccount sender, byte[] data, String recipient) throws DataException {
|
|
||||||
long txTimestamp = System.currentTimeMillis();
|
|
||||||
byte[] lastReference = sender.getLastReference();
|
|
||||||
|
|
||||||
if (lastReference == null) {
|
|
||||||
System.err.println(String.format("Qortal account %s has no last reference", sender.getAddress()));
|
|
||||||
System.exit(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
Long fee = null;
|
|
||||||
int version = 4;
|
|
||||||
int nonce = 0;
|
|
||||||
long amount = 0;
|
|
||||||
Long assetId = null; // because amount is zero
|
|
||||||
|
|
||||||
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, sender.getPublicKey(), fee, null);
|
|
||||||
TransactionData messageTransactionData = new MessageTransactionData(baseTransactionData, version, nonce, recipient, amount, assetId, data, false, false);
|
|
||||||
|
|
||||||
MessageTransaction messageTransaction = new MessageTransaction(repository, messageTransactionData);
|
|
||||||
|
|
||||||
fee = messageTransaction.calcRecommendedFee();
|
|
||||||
messageTransactionData.setFee(fee);
|
|
||||||
|
|
||||||
TransactionUtils.signAndMint(repository, messageTransactionData, sender);
|
|
||||||
|
|
||||||
return messageTransaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkTradeRefund(Repository repository, Account deployer, long deployersInitialBalance, long deployAtFee) throws DataException {
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
int refundTimeout = tradeTimeout / 2 + 1; // close enough
|
|
||||||
|
|
||||||
// AT should automatically refund deployer after 'refundTimeout' blocks
|
|
||||||
for (int blockCount = 0; blockCount <= refundTimeout; ++blockCount)
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// We don't bother to exactly calculate QORT spent running AT for several blocks, but we do know the expected range
|
|
||||||
long expectedMinimumBalance = deployersPostDeploymentBalance;
|
|
||||||
long expectedMaximumBalance = deployersInitialBalance - deployAtFee;
|
|
||||||
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance);
|
|
||||||
assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void describeAt(Repository repository, String atAddress) throws DataException {
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
CrossChainTradeData tradeData = LitecoinACCTv1.getInstance().populateTradeData(repository, atData);
|
|
||||||
|
|
||||||
Function<Long, String> epochMilliFormatter = (timestamp) -> LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneOffset.UTC).format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM));
|
|
||||||
int currentBlockHeight = repository.getBlockRepository().getBlockchainHeight();
|
|
||||||
|
|
||||||
System.out.print(String.format("%s:\n"
|
|
||||||
+ "\tmode: %s\n"
|
|
||||||
+ "\tcreator: %s,\n"
|
|
||||||
+ "\tcreation timestamp: %s,\n"
|
|
||||||
+ "\tcurrent balance: %s QORT,\n"
|
|
||||||
+ "\tis finished: %b,\n"
|
|
||||||
+ "\tredeem payout: %s QORT,\n"
|
|
||||||
+ "\texpected Litecoin: %s LTC,\n"
|
|
||||||
+ "\tcurrent block height: %d,\n",
|
|
||||||
tradeData.qortalAtAddress,
|
|
||||||
tradeData.mode,
|
|
||||||
tradeData.qortalCreator,
|
|
||||||
epochMilliFormatter.apply(tradeData.creationTimestamp),
|
|
||||||
Amounts.prettyAmount(tradeData.qortBalance),
|
|
||||||
atData.getIsFinished(),
|
|
||||||
Amounts.prettyAmount(tradeData.qortAmount),
|
|
||||||
Amounts.prettyAmount(tradeData.expectedForeignAmount),
|
|
||||||
currentBlockHeight));
|
|
||||||
|
|
||||||
if (tradeData.mode != AcctMode.OFFERING && tradeData.mode != AcctMode.CANCELLED) {
|
|
||||||
System.out.println(String.format("\trefund timeout: %d minutes,\n"
|
|
||||||
+ "\trefund height: block %d,\n"
|
|
||||||
+ "\tHASH160 of secret-A: %s,\n"
|
|
||||||
+ "\tLitecoin P2SH-A nLockTime: %d (%s),\n"
|
|
||||||
+ "\ttrade partner: %s\n"
|
|
||||||
+ "\tpartner's receiving address: %s",
|
|
||||||
tradeData.refundTimeout,
|
|
||||||
tradeData.tradeRefundHeight,
|
|
||||||
HashCode.fromBytes(tradeData.hashOfSecretA).toString().substring(0, 40),
|
|
||||||
tradeData.lockTimeA, epochMilliFormatter.apply(tradeData.lockTimeA * 1000L),
|
|
||||||
tradeData.qortalPartnerAddress,
|
|
||||||
tradeData.qortalPartnerReceivingAddress));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private PrivateKeyAccount createTradeAccount(Repository repository) {
|
|
||||||
// We actually use a known test account with funds to avoid PoW compute
|
|
||||||
return Common.getTestAccount(repository, "alice");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,769 +1,58 @@
|
|||||||
package org.qortal.test.crosschain.litecoinv3;
|
package org.qortal.test.crosschain.litecoinv3;
|
||||||
|
|
||||||
import com.google.common.hash.HashCode;
|
import com.google.common.hash.HashCode;
|
||||||
import com.google.common.primitives.Bytes;
|
import org.qortal.crosschain.ACCT;
|
||||||
import org.junit.Before;
|
import org.qortal.crosschain.LitecoinACCTv1;
|
||||||
import org.junit.Test;
|
import org.qortal.test.crosschain.ACCTTests;
|
||||||
import org.qortal.account.Account;
|
|
||||||
import org.qortal.account.PrivateKeyAccount;
|
|
||||||
import org.qortal.asset.Asset;
|
|
||||||
import org.qortal.block.Block;
|
|
||||||
import org.qortal.crosschain.AcctMode;
|
|
||||||
import org.qortal.crosschain.LitecoinACCTv3;
|
|
||||||
import org.qortal.crypto.Crypto;
|
|
||||||
import org.qortal.data.at.ATData;
|
|
||||||
import org.qortal.data.at.ATStateData;
|
|
||||||
import org.qortal.data.crosschain.CrossChainTradeData;
|
|
||||||
import org.qortal.data.transaction.BaseTransactionData;
|
|
||||||
import org.qortal.data.transaction.DeployAtTransactionData;
|
|
||||||
import org.qortal.data.transaction.MessageTransactionData;
|
|
||||||
import org.qortal.data.transaction.TransactionData;
|
|
||||||
import org.qortal.group.Group;
|
|
||||||
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.TransactionUtils;
|
|
||||||
import org.qortal.transaction.DeployAtTransaction;
|
|
||||||
import org.qortal.transaction.MessageTransaction;
|
|
||||||
import org.qortal.utils.Amounts;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
public class LitecoinACCTv3Tests extends ACCTTests {
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.ZoneOffset;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.time.format.FormatStyle;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Random;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
public class LitecoinACCTv3Tests extends Common {
|
|
||||||
|
|
||||||
public static final byte[] secretA = "This string is exactly 32 bytes!".getBytes();
|
|
||||||
public static final byte[] hashOfSecretA = Crypto.hash160(secretA); // daf59884b4d1aec8c1b17102530909ee43c0151a
|
|
||||||
public static final byte[] litecoinPublicKeyHash = HashCode.fromString("bb00bb11bb22bb33bb44bb55bb66bb77bb88bb99").asBytes();
|
public static final byte[] litecoinPublicKeyHash = HashCode.fromString("bb00bb11bb22bb33bb44bb55bb66bb77bb88bb99").asBytes();
|
||||||
public static final int tradeTimeout = 20; // blocks
|
private static final String SYMBOL = "LTC";
|
||||||
public static final long redeemAmount = 80_40200000L;
|
private static final String NAME = "Litecoin";
|
||||||
public static final long fundingAmount = 123_45600000L;
|
|
||||||
public static final long litecoinAmount = 864200L; // 0.00864200 LTC
|
|
||||||
|
|
||||||
private static final Random RANDOM = new Random();
|
@Override
|
||||||
|
protected byte[] getPublicKey() {
|
||||||
@Before
|
return litecoinPublicKeyHash;
|
||||||
public void beforeTest() throws DataException {
|
|
||||||
Common.useDefaultSettings();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Override
|
||||||
public void testCompile() {
|
protected byte[] buildQortalAT(String address, byte[] publicKey, long redeemAmount, long foreignAmount, int tradeTimeout) {
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(null);
|
return LitecoinACCTv1.buildQortalAT(address, publicKey, redeemAmount, foreignAmount, tradeTimeout);
|
||||||
|
|
||||||
byte[] creationBytes = LitecoinACCTv3.buildQortalAT(tradeAccount.getAddress(), litecoinPublicKeyHash, redeemAmount, litecoinAmount, tradeTimeout);
|
|
||||||
assertNotNull(creationBytes);
|
|
||||||
|
|
||||||
System.out.println("AT creation bytes: " + HashCode.fromBytes(creationBytes).toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Override
|
||||||
public void testDeploy() throws DataException {
|
protected ACCT getInstance() {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
return LitecoinACCTv1.getInstance();
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
|
|
||||||
long expectedBalance = deployersInitialBalance - fundingAmount - deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
expectedBalance = fundingAmount;
|
|
||||||
actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("AT's post-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
expectedBalance = partnersInitialBalance;
|
|
||||||
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Test orphaning
|
|
||||||
BlockUtils.orphanLastBlock(repository);
|
|
||||||
|
|
||||||
expectedBalance = deployersInitialBalance;
|
|
||||||
actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
expectedBalance = 0;
|
|
||||||
actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("AT's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
expectedBalance = partnersInitialBalance;
|
|
||||||
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Override
|
||||||
@Test
|
protected int calcRefundTimeout(long partnersOfferMessageTransactionTimestamp, int lockTimeA) {
|
||||||
public void testOfferCancel() throws DataException {
|
return LitecoinACCTv1.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
|
|
||||||
// Send creator's address to AT, instead of typical partner's address
|
|
||||||
byte[] messageData = LitecoinACCTv3.getInstance().buildCancelMessage(deployer.getAddress());
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress);
|
|
||||||
long messageFee = messageTransaction.getTransactionData().getFee();
|
|
||||||
|
|
||||||
// AT should process 'cancel' message in next block
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertTrue(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in CANCELLED mode
|
|
||||||
CrossChainTradeData tradeData = LitecoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.CANCELLED, tradeData.mode);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedMinimumBalance = deployersPostDeploymentBalance;
|
|
||||||
long expectedMaximumBalance = deployersInitialBalance - deployAtFee - messageFee;
|
|
||||||
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance);
|
|
||||||
assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance);
|
|
||||||
|
|
||||||
// Test orphaning
|
|
||||||
BlockUtils.orphanLastBlock(repository);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = deployersPostDeploymentBalance - messageFee;
|
|
||||||
actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Override
|
||||||
@Test
|
protected byte[] buildTradeMessage(String address, byte[] publicKey, byte[] hashOfSecretA, int lockTimeA, int refundTimeout) {
|
||||||
public void testOfferCancelInvalidLength() throws DataException {
|
return LitecoinACCTv1.buildTradeMessage(address, publicKey, hashOfSecretA, lockTimeA, refundTimeout);
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
|
|
||||||
// Instead of sending creator's address to AT, send too-short/invalid message
|
|
||||||
byte[] messageData = new byte[7];
|
|
||||||
RANDOM.nextBytes(messageData);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress);
|
|
||||||
long messageFee = messageTransaction.getTransactionData().getFee();
|
|
||||||
|
|
||||||
// AT should process 'cancel' message in next block
|
|
||||||
// As message is too short, it will be padded to 32bytes but cancel code doesn't care about message content, so should be ok
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertTrue(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in CANCELLED mode
|
|
||||||
CrossChainTradeData tradeData = LitecoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.CANCELLED, tradeData.mode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Override
|
||||||
@Test
|
protected byte[] buildRedeemMessage(byte[] secretA, String address) {
|
||||||
public void testTradingInfoProcessing() throws DataException {
|
return LitecoinACCTv1.buildRedeemMessage(secretA, address);
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = LitecoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = LitecoinACCTv3.buildTradeMessage(partner.getAddress(), litecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
Block postDeploymentBlock = BlockUtils.mintBlock(repository);
|
|
||||||
int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight();
|
|
||||||
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
CrossChainTradeData tradeData = LitecoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
|
|
||||||
// AT should be in TRADE mode
|
|
||||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
|
||||||
|
|
||||||
// Check hashOfSecretA was extracted correctly
|
|
||||||
assertTrue(Arrays.equals(hashOfSecretA, tradeData.hashOfSecretA));
|
|
||||||
|
|
||||||
// Check trade partner Qortal address was extracted correctly
|
|
||||||
assertEquals(partner.getAddress(), tradeData.qortalPartnerAddress);
|
|
||||||
|
|
||||||
// Check trade partner's Litecoin PKH was extracted correctly
|
|
||||||
assertTrue(Arrays.equals(litecoinPublicKeyHash, tradeData.partnerForeignPKH));
|
|
||||||
|
|
||||||
// Test orphaning
|
|
||||||
BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = deployersPostDeploymentBalance;
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TEST SENDING TRADING INFO BUT NOT FROM AT CREATOR (SHOULD BE IGNORED)
|
@Override
|
||||||
@SuppressWarnings("unused")
|
protected byte[] getCodeBytesHash() {
|
||||||
@Test
|
return LitecoinACCTv1.CODE_BYTES_HASH;
|
||||||
public void testIncorrectTradeSender() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = LitecoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT BUT NOT FROM AT CREATOR
|
|
||||||
byte[] messageData = LitecoinACCTv3.buildTradeMessage(partner.getAddress(), litecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, bystander, messageData, atAddress);
|
|
||||||
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
long expectedBalance = partnersInitialBalance;
|
|
||||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-initial-payout balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
CrossChainTradeData tradeData = LitecoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
|
|
||||||
// AT should still be in OFFER mode
|
|
||||||
assertEquals(AcctMode.OFFERING, tradeData.mode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Override
|
||||||
@Test
|
protected String getSymbol() {
|
||||||
public void testAutomaticTradeRefund() throws DataException {
|
return SYMBOL;
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = LitecoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = LitecoinACCTv3.buildTradeMessage(partner.getAddress(), litecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
Block postDeploymentBlock = BlockUtils.mintBlock(repository);
|
|
||||||
int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight();
|
|
||||||
|
|
||||||
// Check refund
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
|
|
||||||
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertTrue(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in REFUNDED mode
|
|
||||||
CrossChainTradeData tradeData = LitecoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.REFUNDED, tradeData.mode);
|
|
||||||
|
|
||||||
// Test orphaning
|
|
||||||
BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = deployersPostDeploymentBalance;
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Override
|
||||||
@Test
|
protected String getName() {
|
||||||
public void testCorrectSecretCorrectSender() throws DataException {
|
return NAME;
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = LitecoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = LitecoinACCTv3.buildTradeMessage(partner.getAddress(), litecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
// Give AT time to process message
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// Send correct secret to AT, from correct account
|
|
||||||
messageData = LitecoinACCTv3.buildRedeemMessage(secretA, partner.getAddress());
|
|
||||||
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
|
||||||
|
|
||||||
// AT should send funds in the next block
|
|
||||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertTrue(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in REDEEMED mode
|
|
||||||
CrossChainTradeData tradeData = LitecoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.REDEEMED, tradeData.mode);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee() + redeemAmount;
|
|
||||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-redeem balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Orphan redeem
|
|
||||||
BlockUtils.orphanLastBlock(repository);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee();
|
|
||||||
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-orphan/pre-redeem balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Check AT state
|
|
||||||
ATStateData postOrphanAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
|
|
||||||
assertTrue("AT states mismatch", Arrays.equals(preRedeemAtStateData.getStateData(), postOrphanAtStateData.getStateData()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testCorrectSecretIncorrectSender() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = LitecoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = LitecoinACCTv3.buildTradeMessage(partner.getAddress(), litecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
// Give AT time to process message
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// Send correct secret to AT, but from wrong account
|
|
||||||
messageData = LitecoinACCTv3.buildRedeemMessage(secretA, partner.getAddress());
|
|
||||||
messageTransaction = sendMessage(repository, bystander, messageData, atAddress);
|
|
||||||
|
|
||||||
// AT should NOT send funds in the next block
|
|
||||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is NOT finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertFalse(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should still be in TRADE mode
|
|
||||||
CrossChainTradeData tradeData = LitecoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = partnersInitialBalance;
|
|
||||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Check eventual refund
|
|
||||||
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testIncorrectSecretCorrectSender() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = LitecoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = LitecoinACCTv3.buildTradeMessage(partner.getAddress(), litecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
// Give AT time to process message
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// Send incorrect secret to AT, from correct account
|
|
||||||
byte[] wrongSecret = new byte[32];
|
|
||||||
RANDOM.nextBytes(wrongSecret);
|
|
||||||
messageData = LitecoinACCTv3.buildRedeemMessage(wrongSecret, partner.getAddress());
|
|
||||||
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
|
||||||
|
|
||||||
// AT should NOT send funds in the next block
|
|
||||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is NOT finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertFalse(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should still be in TRADE mode
|
|
||||||
CrossChainTradeData tradeData = LitecoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
|
||||||
|
|
||||||
long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee();
|
|
||||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Check eventual refund
|
|
||||||
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testCorrectSecretCorrectSenderInvalidMessageLength() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = LitecoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = LitecoinACCTv3.buildTradeMessage(partner.getAddress(), litecoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
// Give AT time to process message
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// Send correct secret to AT, from correct account, but missing receive address, hence incorrect length
|
|
||||||
messageData = Bytes.concat(secretA);
|
|
||||||
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
|
||||||
|
|
||||||
// AT should NOT send funds in the next block
|
|
||||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is NOT finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertFalse(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in TRADING mode
|
|
||||||
CrossChainTradeData tradeData = LitecoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testDescribeDeployed() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
|
|
||||||
List<ATData> executableAts = repository.getATRepository().getAllExecutableATs();
|
|
||||||
|
|
||||||
for (ATData atData : executableAts) {
|
|
||||||
String atAddress = atData.getATAddress();
|
|
||||||
byte[] codeBytes = atData.getCodeBytes();
|
|
||||||
byte[] codeHash = Crypto.digest(codeBytes);
|
|
||||||
|
|
||||||
System.out.println(String.format("%s: code length: %d byte%s, code hash: %s",
|
|
||||||
atAddress,
|
|
||||||
codeBytes.length,
|
|
||||||
(codeBytes.length != 1 ? "s": ""),
|
|
||||||
HashCode.fromBytes(codeHash)));
|
|
||||||
|
|
||||||
// Not one of ours?
|
|
||||||
if (!Arrays.equals(codeHash, LitecoinACCTv3.CODE_BYTES_HASH))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int calcTestLockTimeA(long messageTimestamp) {
|
|
||||||
return (int) (messageTimestamp / 1000L + tradeTimeout * 60);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer, String tradeAddress) throws DataException {
|
|
||||||
byte[] creationBytes = LitecoinACCTv3.buildQortalAT(tradeAddress, litecoinPublicKeyHash, redeemAmount, litecoinAmount, tradeTimeout);
|
|
||||||
|
|
||||||
long txTimestamp = System.currentTimeMillis();
|
|
||||||
byte[] lastReference = deployer.getLastReference();
|
|
||||||
|
|
||||||
if (lastReference == null) {
|
|
||||||
System.err.println(String.format("Qortal account %s has no last reference", deployer.getAddress()));
|
|
||||||
System.exit(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
Long fee = null;
|
|
||||||
String name = "QORT-LTC cross-chain trade";
|
|
||||||
String description = String.format("Qortal-Litecoin cross-chain trade");
|
|
||||||
String atType = "ACCT";
|
|
||||||
String tags = "QORT-LTC ACCT";
|
|
||||||
|
|
||||||
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, deployer.getPublicKey(), fee, null);
|
|
||||||
TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, atType, tags, creationBytes, fundingAmount, Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData);
|
|
||||||
|
|
||||||
fee = deployAtTransaction.calcRecommendedFee();
|
|
||||||
deployAtTransactionData.setFee(fee);
|
|
||||||
|
|
||||||
TransactionUtils.signAndMint(repository, deployAtTransactionData, deployer);
|
|
||||||
|
|
||||||
return deployAtTransaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
private MessageTransaction sendMessage(Repository repository, PrivateKeyAccount sender, byte[] data, String recipient) throws DataException {
|
|
||||||
long txTimestamp = System.currentTimeMillis();
|
|
||||||
byte[] lastReference = sender.getLastReference();
|
|
||||||
|
|
||||||
if (lastReference == null) {
|
|
||||||
System.err.println(String.format("Qortal account %s has no last reference", sender.getAddress()));
|
|
||||||
System.exit(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
Long fee = null;
|
|
||||||
int version = 4;
|
|
||||||
int nonce = 0;
|
|
||||||
long amount = 0;
|
|
||||||
Long assetId = null; // because amount is zero
|
|
||||||
|
|
||||||
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, sender.getPublicKey(), fee, null);
|
|
||||||
TransactionData messageTransactionData = new MessageTransactionData(baseTransactionData, version, nonce, recipient, amount, assetId, data, false, false);
|
|
||||||
|
|
||||||
MessageTransaction messageTransaction = new MessageTransaction(repository, messageTransactionData);
|
|
||||||
|
|
||||||
fee = messageTransaction.calcRecommendedFee();
|
|
||||||
messageTransactionData.setFee(fee);
|
|
||||||
|
|
||||||
TransactionUtils.signAndMint(repository, messageTransactionData, sender);
|
|
||||||
|
|
||||||
return messageTransaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkTradeRefund(Repository repository, Account deployer, long deployersInitialBalance, long deployAtFee) throws DataException {
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
int refundTimeout = tradeTimeout / 2 + 1; // close enough
|
|
||||||
|
|
||||||
// AT should automatically refund deployer after 'refundTimeout' blocks
|
|
||||||
for (int blockCount = 0; blockCount <= refundTimeout; ++blockCount)
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// We don't bother to exactly calculate QORT spent running AT for several blocks, but we do know the expected range
|
|
||||||
long expectedMinimumBalance = deployersPostDeploymentBalance;
|
|
||||||
long expectedMaximumBalance = deployersInitialBalance - deployAtFee;
|
|
||||||
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance);
|
|
||||||
assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void describeAt(Repository repository, String atAddress) throws DataException {
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
CrossChainTradeData tradeData = LitecoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
|
|
||||||
Function<Long, String> epochMilliFormatter = (timestamp) -> LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneOffset.UTC).format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM));
|
|
||||||
int currentBlockHeight = repository.getBlockRepository().getBlockchainHeight();
|
|
||||||
|
|
||||||
System.out.print(String.format("%s:\n"
|
|
||||||
+ "\tmode: %s\n"
|
|
||||||
+ "\tcreator: %s,\n"
|
|
||||||
+ "\tcreation timestamp: %s,\n"
|
|
||||||
+ "\tcurrent balance: %s QORT,\n"
|
|
||||||
+ "\tis finished: %b,\n"
|
|
||||||
+ "\tredeem payout: %s QORT,\n"
|
|
||||||
+ "\texpected Litecoin: %s LTC,\n"
|
|
||||||
+ "\tcurrent block height: %d,\n",
|
|
||||||
tradeData.qortalAtAddress,
|
|
||||||
tradeData.mode,
|
|
||||||
tradeData.qortalCreator,
|
|
||||||
epochMilliFormatter.apply(tradeData.creationTimestamp),
|
|
||||||
Amounts.prettyAmount(tradeData.qortBalance),
|
|
||||||
atData.getIsFinished(),
|
|
||||||
Amounts.prettyAmount(tradeData.qortAmount),
|
|
||||||
Amounts.prettyAmount(tradeData.expectedForeignAmount),
|
|
||||||
currentBlockHeight));
|
|
||||||
|
|
||||||
if (tradeData.mode != AcctMode.OFFERING && tradeData.mode != AcctMode.CANCELLED) {
|
|
||||||
System.out.println(String.format("\trefund timeout: %d minutes,\n"
|
|
||||||
+ "\trefund height: block %d,\n"
|
|
||||||
+ "\tHASH160 of secret-A: %s,\n"
|
|
||||||
+ "\tLitecoin P2SH-A nLockTime: %d (%s),\n"
|
|
||||||
+ "\ttrade partner: %s\n"
|
|
||||||
+ "\tpartner's receiving address: %s",
|
|
||||||
tradeData.refundTimeout,
|
|
||||||
tradeData.tradeRefundHeight,
|
|
||||||
HashCode.fromBytes(tradeData.hashOfSecretA).toString().substring(0, 40),
|
|
||||||
tradeData.lockTimeA, epochMilliFormatter.apply(tradeData.lockTimeA * 1000L),
|
|
||||||
tradeData.qortalPartnerAddress,
|
|
||||||
tradeData.qortalPartnerReceivingAddress));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private PrivateKeyAccount createTradeAccount(Repository repository) {
|
|
||||||
// We actually use a known test account with funds to avoid PoW compute
|
|
||||||
return Common.getTestAccount(repository, "alice");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,771 +1,58 @@
|
|||||||
package org.qortal.test.crosschain.piratechainv3;
|
package org.qortal.test.crosschain.piratechainv3;
|
||||||
|
|
||||||
import com.google.common.hash.HashCode;
|
import com.google.common.hash.HashCode;
|
||||||
import com.google.common.primitives.Bytes;
|
import org.qortal.crosschain.ACCT;
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.qortal.account.Account;
|
|
||||||
import org.qortal.account.PrivateKeyAccount;
|
|
||||||
import org.qortal.asset.Asset;
|
|
||||||
import org.qortal.block.Block;
|
|
||||||
import org.qortal.crosschain.AcctMode;
|
|
||||||
import org.qortal.crosschain.PirateChainACCTv3;
|
import org.qortal.crosschain.PirateChainACCTv3;
|
||||||
import org.qortal.crypto.Crypto;
|
import org.qortal.test.crosschain.ACCTTests;
|
||||||
import org.qortal.data.at.ATData;
|
|
||||||
import org.qortal.data.at.ATStateData;
|
|
||||||
import org.qortal.data.crosschain.CrossChainTradeData;
|
|
||||||
import org.qortal.data.transaction.BaseTransactionData;
|
|
||||||
import org.qortal.data.transaction.DeployAtTransactionData;
|
|
||||||
import org.qortal.data.transaction.MessageTransactionData;
|
|
||||||
import org.qortal.data.transaction.TransactionData;
|
|
||||||
import org.qortal.group.Group;
|
|
||||||
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.TransactionUtils;
|
|
||||||
import org.qortal.transaction.DeployAtTransaction;
|
|
||||||
import org.qortal.transaction.MessageTransaction;
|
|
||||||
import org.qortal.utils.Amounts;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
public class PirateChainACCTv3Tests extends ACCTTests {
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.ZoneOffset;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.time.format.FormatStyle;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Random;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
public class PirateChainACCTv3Tests extends Common {
|
|
||||||
|
|
||||||
public static final byte[] secretA = "This string is exactly 32 bytes!".getBytes();
|
|
||||||
public static final byte[] hashOfSecretA = Crypto.hash160(secretA); // daf59884b4d1aec8c1b17102530909ee43c0151a
|
|
||||||
public static final byte[] pirateChainPublicKey = HashCode.fromString("aabb00bb11bb22bb33bb44bb55bb66bb77bb88bb99cc00cc11cc22cc33cc44cc55").asBytes(); // 33 bytes
|
public static final byte[] pirateChainPublicKey = HashCode.fromString("aabb00bb11bb22bb33bb44bb55bb66bb77bb88bb99cc00cc11cc22cc33cc44cc55").asBytes(); // 33 bytes
|
||||||
public static final int tradeTimeout = 20; // blocks
|
private static final String SYMBOL = "ARRR";
|
||||||
public static final long redeemAmount = 80_40200000L;
|
private static final String NAME = "Pirate Chain";
|
||||||
public static final long fundingAmount = 123_45600000L;
|
|
||||||
public static final long arrrAmount = 864200L; // 0.00864200 ARRR
|
|
||||||
|
|
||||||
private static final Random RANDOM = new Random();
|
@Override
|
||||||
|
protected byte[] getPublicKey() {
|
||||||
@Before
|
return pirateChainPublicKey;
|
||||||
public void beforeTest() throws DataException {
|
|
||||||
Common.useDefaultSettings();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Override
|
||||||
public void testCompile() {
|
protected byte[] buildQortalAT(String address, byte[] publicKey, long redeemAmount, long foreignAmount, int tradeTimeout) {
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(null);
|
return PirateChainACCTv3.buildQortalAT(address, publicKey, redeemAmount, foreignAmount, tradeTimeout);
|
||||||
|
|
||||||
byte[] creationBytes = PirateChainACCTv3.buildQortalAT(tradeAccount.getAddress(), pirateChainPublicKey, redeemAmount, arrrAmount, tradeTimeout);
|
|
||||||
assertNotNull(creationBytes);
|
|
||||||
|
|
||||||
System.out.println("AT creation bytes: " + HashCode.fromBytes(creationBytes).toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Override
|
||||||
public void testDeploy() throws DataException {
|
protected ACCT getInstance() {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
return PirateChainACCTv3.getInstance();
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
|
|
||||||
long expectedBalance = deployersInitialBalance - fundingAmount - deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
expectedBalance = fundingAmount;
|
|
||||||
actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("AT's post-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
expectedBalance = partnersInitialBalance;
|
|
||||||
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Test orphaning
|
|
||||||
BlockUtils.orphanLastBlock(repository);
|
|
||||||
|
|
||||||
expectedBalance = deployersInitialBalance;
|
|
||||||
actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
expectedBalance = 0;
|
|
||||||
actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("AT's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
expectedBalance = partnersInitialBalance;
|
|
||||||
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Override
|
||||||
@Test
|
protected int calcRefundTimeout(long partnersOfferMessageTransactionTimestamp, int lockTimeA) {
|
||||||
public void testOfferCancel() throws DataException {
|
return PirateChainACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
|
|
||||||
// Send creator's address to AT, instead of typical partner's address
|
|
||||||
byte[] messageData = PirateChainACCTv3.getInstance().buildCancelMessage(deployer.getAddress());
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress);
|
|
||||||
long messageFee = messageTransaction.getTransactionData().getFee();
|
|
||||||
|
|
||||||
// AT should process 'cancel' message in next block
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertTrue(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in CANCELLED mode
|
|
||||||
CrossChainTradeData tradeData = PirateChainACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.CANCELLED, tradeData.mode);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedMinimumBalance = deployersPostDeploymentBalance;
|
|
||||||
long expectedMaximumBalance = deployersInitialBalance - deployAtFee - messageFee;
|
|
||||||
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance);
|
|
||||||
assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance);
|
|
||||||
|
|
||||||
// Test orphaning
|
|
||||||
BlockUtils.orphanLastBlock(repository);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = deployersPostDeploymentBalance - messageFee;
|
|
||||||
actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Override
|
||||||
@Test
|
protected byte[] buildTradeMessage(String address, byte[] publicKey, byte[] hashOfSecretA, int lockTimeA, int refundTimeout) {
|
||||||
public void testOfferCancelInvalidLength() throws DataException {
|
return PirateChainACCTv3.buildTradeMessage(address, publicKey, hashOfSecretA, lockTimeA, refundTimeout);
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
|
|
||||||
// Instead of sending creator's address to AT, send too-short/invalid message
|
|
||||||
byte[] messageData = new byte[7];
|
|
||||||
RANDOM.nextBytes(messageData);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress);
|
|
||||||
long messageFee = messageTransaction.getTransactionData().getFee();
|
|
||||||
|
|
||||||
// AT should process 'cancel' message in next block
|
|
||||||
// As message is too short, it will be padded to 32bytes but cancel code doesn't care about message content, so should be ok
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertTrue(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in CANCELLED mode
|
|
||||||
CrossChainTradeData tradeData = PirateChainACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.CANCELLED, tradeData.mode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Override
|
||||||
@Test
|
protected byte[] buildRedeemMessage(byte[] secretA, String address) {
|
||||||
public void testTradingInfoProcessing() throws DataException {
|
return PirateChainACCTv3.buildRedeemMessage(secretA, address);
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = PirateChainACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = PirateChainACCTv3.buildTradeMessage(partner.getAddress(), pirateChainPublicKey, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
Block postDeploymentBlock = BlockUtils.mintBlock(repository);
|
|
||||||
int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight();
|
|
||||||
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
System.out.println(String.format("pirateChainPublicKey: %s", HashCode.fromBytes(pirateChainPublicKey)));
|
|
||||||
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
CrossChainTradeData tradeData = PirateChainACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
|
|
||||||
// AT should be in TRADE mode
|
|
||||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
|
||||||
|
|
||||||
// Check hashOfSecretA was extracted correctly
|
|
||||||
assertTrue(Arrays.equals(hashOfSecretA, tradeData.hashOfSecretA));
|
|
||||||
|
|
||||||
// Check trade partner Qortal address was extracted correctly
|
|
||||||
assertEquals(partner.getAddress(), tradeData.qortalPartnerAddress);
|
|
||||||
|
|
||||||
// Check trade partner's Litecoin PKH was extracted correctly
|
|
||||||
assertTrue(Arrays.equals(pirateChainPublicKey, tradeData.partnerForeignPKH));
|
|
||||||
|
|
||||||
// Test orphaning
|
|
||||||
BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = deployersPostDeploymentBalance;
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TEST SENDING TRADING INFO BUT NOT FROM AT CREATOR (SHOULD BE IGNORED)
|
@Override
|
||||||
@SuppressWarnings("unused")
|
protected byte[] getCodeBytesHash() {
|
||||||
@Test
|
return PirateChainACCTv3.CODE_BYTES_HASH;
|
||||||
public void testIncorrectTradeSender() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = PirateChainACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT BUT NOT FROM AT CREATOR
|
|
||||||
byte[] messageData = PirateChainACCTv3.buildTradeMessage(partner.getAddress(), pirateChainPublicKey, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, bystander, messageData, atAddress);
|
|
||||||
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
long expectedBalance = partnersInitialBalance;
|
|
||||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-initial-payout balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
CrossChainTradeData tradeData = PirateChainACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
|
|
||||||
// AT should still be in OFFER mode
|
|
||||||
assertEquals(AcctMode.OFFERING, tradeData.mode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Override
|
||||||
@Test
|
protected String getSymbol() {
|
||||||
public void testAutomaticTradeRefund() throws DataException {
|
return SYMBOL;
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = PirateChainACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = PirateChainACCTv3.buildTradeMessage(partner.getAddress(), pirateChainPublicKey, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
Block postDeploymentBlock = BlockUtils.mintBlock(repository);
|
|
||||||
int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight();
|
|
||||||
|
|
||||||
// Check refund
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
|
|
||||||
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertTrue(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in REFUNDED mode
|
|
||||||
CrossChainTradeData tradeData = PirateChainACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.REFUNDED, tradeData.mode);
|
|
||||||
|
|
||||||
// Test orphaning
|
|
||||||
BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = deployersPostDeploymentBalance;
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Override
|
||||||
@Test
|
protected String getName() {
|
||||||
public void testCorrectSecretCorrectSender() throws DataException {
|
return NAME;
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = PirateChainACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = PirateChainACCTv3.buildTradeMessage(partner.getAddress(), pirateChainPublicKey, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
// Give AT time to process message
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// Send correct secret to AT, from correct account
|
|
||||||
messageData = PirateChainACCTv3.buildRedeemMessage(secretA, partner.getAddress());
|
|
||||||
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
|
||||||
|
|
||||||
// AT should send funds in the next block
|
|
||||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertTrue(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in REDEEMED mode
|
|
||||||
CrossChainTradeData tradeData = PirateChainACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.REDEEMED, tradeData.mode);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee() + redeemAmount;
|
|
||||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-redeem balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Orphan redeem
|
|
||||||
BlockUtils.orphanLastBlock(repository);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee();
|
|
||||||
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-orphan/pre-redeem balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Check AT state
|
|
||||||
ATStateData postOrphanAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
|
|
||||||
assertTrue("AT states mismatch", Arrays.equals(preRedeemAtStateData.getStateData(), postOrphanAtStateData.getStateData()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testCorrectSecretIncorrectSender() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = PirateChainACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = PirateChainACCTv3.buildTradeMessage(partner.getAddress(), pirateChainPublicKey, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
// Give AT time to process message
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// Send correct secret to AT, but from wrong account
|
|
||||||
messageData = PirateChainACCTv3.buildRedeemMessage(secretA, partner.getAddress());
|
|
||||||
messageTransaction = sendMessage(repository, bystander, messageData, atAddress);
|
|
||||||
|
|
||||||
// AT should NOT send funds in the next block
|
|
||||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is NOT finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertFalse(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should still be in TRADE mode
|
|
||||||
CrossChainTradeData tradeData = PirateChainACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = partnersInitialBalance;
|
|
||||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Check eventual refund
|
|
||||||
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testIncorrectSecretCorrectSender() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = PirateChainACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = PirateChainACCTv3.buildTradeMessage(partner.getAddress(), pirateChainPublicKey, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
// Give AT time to process message
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// Send incorrect secret to AT, from correct account
|
|
||||||
byte[] wrongSecret = new byte[32];
|
|
||||||
RANDOM.nextBytes(wrongSecret);
|
|
||||||
messageData = PirateChainACCTv3.buildRedeemMessage(wrongSecret, partner.getAddress());
|
|
||||||
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
|
||||||
|
|
||||||
// AT should NOT send funds in the next block
|
|
||||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is NOT finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertFalse(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should still be in TRADE mode
|
|
||||||
CrossChainTradeData tradeData = PirateChainACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
|
||||||
|
|
||||||
long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee();
|
|
||||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Check eventual refund
|
|
||||||
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testCorrectSecretCorrectSenderInvalidMessageLength() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = PirateChainACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = PirateChainACCTv3.buildTradeMessage(partner.getAddress(), pirateChainPublicKey, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
// Give AT time to process message
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// Send correct secret to AT, from correct account, but missing receive address, hence incorrect length
|
|
||||||
messageData = Bytes.concat(secretA);
|
|
||||||
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
|
||||||
|
|
||||||
// AT should NOT send funds in the next block
|
|
||||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is NOT finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertFalse(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in TRADING mode
|
|
||||||
CrossChainTradeData tradeData = PirateChainACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testDescribeDeployed() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
|
|
||||||
List<ATData> executableAts = repository.getATRepository().getAllExecutableATs();
|
|
||||||
|
|
||||||
for (ATData atData : executableAts) {
|
|
||||||
String atAddress = atData.getATAddress();
|
|
||||||
byte[] codeBytes = atData.getCodeBytes();
|
|
||||||
byte[] codeHash = Crypto.digest(codeBytes);
|
|
||||||
|
|
||||||
System.out.println(String.format("%s: code length: %d byte%s, code hash: %s",
|
|
||||||
atAddress,
|
|
||||||
codeBytes.length,
|
|
||||||
(codeBytes.length != 1 ? "s": ""),
|
|
||||||
HashCode.fromBytes(codeHash)));
|
|
||||||
|
|
||||||
// Not one of ours?
|
|
||||||
if (!Arrays.equals(codeHash, PirateChainACCTv3.CODE_BYTES_HASH))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int calcTestLockTimeA(long messageTimestamp) {
|
|
||||||
return (int) (messageTimestamp / 1000L + tradeTimeout * 60);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer, String tradeAddress) throws DataException {
|
|
||||||
byte[] creationBytes = PirateChainACCTv3.buildQortalAT(tradeAddress, pirateChainPublicKey, redeemAmount, arrrAmount, tradeTimeout);
|
|
||||||
|
|
||||||
long txTimestamp = System.currentTimeMillis();
|
|
||||||
byte[] lastReference = deployer.getLastReference();
|
|
||||||
|
|
||||||
if (lastReference == null) {
|
|
||||||
System.err.println(String.format("Qortal account %s has no last reference", deployer.getAddress()));
|
|
||||||
System.exit(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
Long fee = null;
|
|
||||||
String name = "QORT-ARRR cross-chain trade";
|
|
||||||
String description = String.format("Qortal-PirateChain cross-chain trade");
|
|
||||||
String atType = "ACCT";
|
|
||||||
String tags = "QORT-ARRR ACCT";
|
|
||||||
|
|
||||||
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, deployer.getPublicKey(), fee, null);
|
|
||||||
TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, atType, tags, creationBytes, fundingAmount, Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData);
|
|
||||||
|
|
||||||
fee = deployAtTransaction.calcRecommendedFee();
|
|
||||||
deployAtTransactionData.setFee(fee);
|
|
||||||
|
|
||||||
TransactionUtils.signAndMint(repository, deployAtTransactionData, deployer);
|
|
||||||
|
|
||||||
return deployAtTransaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
private MessageTransaction sendMessage(Repository repository, PrivateKeyAccount sender, byte[] data, String recipient) throws DataException {
|
|
||||||
long txTimestamp = System.currentTimeMillis();
|
|
||||||
byte[] lastReference = sender.getLastReference();
|
|
||||||
|
|
||||||
if (lastReference == null) {
|
|
||||||
System.err.println(String.format("Qortal account %s has no last reference", sender.getAddress()));
|
|
||||||
System.exit(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
Long fee = null;
|
|
||||||
int version = 4;
|
|
||||||
int nonce = 0;
|
|
||||||
long amount = 0;
|
|
||||||
Long assetId = null; // because amount is zero
|
|
||||||
|
|
||||||
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, sender.getPublicKey(), fee, null);
|
|
||||||
TransactionData messageTransactionData = new MessageTransactionData(baseTransactionData, version, nonce, recipient, amount, assetId, data, false, false);
|
|
||||||
|
|
||||||
MessageTransaction messageTransaction = new MessageTransaction(repository, messageTransactionData);
|
|
||||||
|
|
||||||
fee = messageTransaction.calcRecommendedFee();
|
|
||||||
messageTransactionData.setFee(fee);
|
|
||||||
|
|
||||||
TransactionUtils.signAndMint(repository, messageTransactionData, sender);
|
|
||||||
|
|
||||||
return messageTransaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkTradeRefund(Repository repository, Account deployer, long deployersInitialBalance, long deployAtFee) throws DataException {
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
int refundTimeout = tradeTimeout / 2 + 1; // close enough
|
|
||||||
|
|
||||||
// AT should automatically refund deployer after 'refundTimeout' blocks
|
|
||||||
for (int blockCount = 0; blockCount <= refundTimeout; ++blockCount)
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// We don't bother to exactly calculate QORT spent running AT for several blocks, but we do know the expected range
|
|
||||||
long expectedMinimumBalance = deployersPostDeploymentBalance;
|
|
||||||
long expectedMaximumBalance = deployersInitialBalance - deployAtFee;
|
|
||||||
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance);
|
|
||||||
assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void describeAt(Repository repository, String atAddress) throws DataException {
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
CrossChainTradeData tradeData = PirateChainACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
|
|
||||||
Function<Long, String> epochMilliFormatter = (timestamp) -> LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneOffset.UTC).format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM));
|
|
||||||
int currentBlockHeight = repository.getBlockRepository().getBlockchainHeight();
|
|
||||||
|
|
||||||
System.out.print(String.format("%s:\n"
|
|
||||||
+ "\tmode: %s\n"
|
|
||||||
+ "\tcreator: %s,\n"
|
|
||||||
+ "\tcreation timestamp: %s,\n"
|
|
||||||
+ "\tcurrent balance: %s QORT,\n"
|
|
||||||
+ "\tis finished: %b,\n"
|
|
||||||
+ "\tredeem payout: %s QORT,\n"
|
|
||||||
+ "\texpected ARRR: %s ARRR,\n"
|
|
||||||
+ "\tcurrent block height: %d,\n",
|
|
||||||
tradeData.qortalAtAddress,
|
|
||||||
tradeData.mode,
|
|
||||||
tradeData.qortalCreator,
|
|
||||||
epochMilliFormatter.apply(tradeData.creationTimestamp),
|
|
||||||
Amounts.prettyAmount(tradeData.qortBalance),
|
|
||||||
atData.getIsFinished(),
|
|
||||||
Amounts.prettyAmount(tradeData.qortAmount),
|
|
||||||
Amounts.prettyAmount(tradeData.expectedForeignAmount),
|
|
||||||
currentBlockHeight));
|
|
||||||
|
|
||||||
if (tradeData.mode != AcctMode.OFFERING && tradeData.mode != AcctMode.CANCELLED) {
|
|
||||||
System.out.println(String.format("\trefund timeout: %d minutes,\n"
|
|
||||||
+ "\trefund height: block %d,\n"
|
|
||||||
+ "\tHASH160 of secret-A: %s,\n"
|
|
||||||
+ "\tPirate Chain P2SH-A nLockTime: %d (%s),\n"
|
|
||||||
+ "\ttrade partner: %s\n"
|
|
||||||
+ "\tpartner's receiving address: %s",
|
|
||||||
tradeData.refundTimeout,
|
|
||||||
tradeData.tradeRefundHeight,
|
|
||||||
HashCode.fromBytes(tradeData.hashOfSecretA).toString().substring(0, 40),
|
|
||||||
tradeData.lockTimeA, epochMilliFormatter.apply(tradeData.lockTimeA * 1000L),
|
|
||||||
tradeData.qortalPartnerAddress,
|
|
||||||
tradeData.qortalPartnerReceivingAddress));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private PrivateKeyAccount createTradeAccount(Repository repository) {
|
|
||||||
// We actually use a known test account with funds to avoid PoW compute
|
|
||||||
return Common.getTestAccount(repository, "alice");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,769 +1,58 @@
|
|||||||
package org.qortal.test.crosschain.ravencoinv3;
|
package org.qortal.test.crosschain.ravencoinv3;
|
||||||
|
|
||||||
import com.google.common.hash.HashCode;
|
import com.google.common.hash.HashCode;
|
||||||
import com.google.common.primitives.Bytes;
|
import org.qortal.crosschain.ACCT;
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.qortal.account.Account;
|
|
||||||
import org.qortal.account.PrivateKeyAccount;
|
|
||||||
import org.qortal.asset.Asset;
|
|
||||||
import org.qortal.block.Block;
|
|
||||||
import org.qortal.crosschain.AcctMode;
|
|
||||||
import org.qortal.crosschain.RavencoinACCTv3;
|
import org.qortal.crosschain.RavencoinACCTv3;
|
||||||
import org.qortal.crypto.Crypto;
|
import org.qortal.test.crosschain.ACCTTests;
|
||||||
import org.qortal.data.at.ATData;
|
|
||||||
import org.qortal.data.at.ATStateData;
|
|
||||||
import org.qortal.data.crosschain.CrossChainTradeData;
|
|
||||||
import org.qortal.data.transaction.BaseTransactionData;
|
|
||||||
import org.qortal.data.transaction.DeployAtTransactionData;
|
|
||||||
import org.qortal.data.transaction.MessageTransactionData;
|
|
||||||
import org.qortal.data.transaction.TransactionData;
|
|
||||||
import org.qortal.group.Group;
|
|
||||||
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.TransactionUtils;
|
|
||||||
import org.qortal.transaction.DeployAtTransaction;
|
|
||||||
import org.qortal.transaction.MessageTransaction;
|
|
||||||
import org.qortal.utils.Amounts;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
public class RavencoinACCTv3Tests extends ACCTTests {
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.ZoneOffset;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.time.format.FormatStyle;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Random;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
public class RavencoinACCTv3Tests extends Common {
|
|
||||||
|
|
||||||
public static final byte[] secretA = "This string is exactly 32 bytes!".getBytes();
|
|
||||||
public static final byte[] hashOfSecretA = Crypto.hash160(secretA); // daf59884b4d1aec8c1b17102530909ee43c0151a
|
|
||||||
public static final byte[] ravencoinPublicKeyHash = HashCode.fromString("bb00bb11bb22bb33bb44bb55bb66bb77bb88bb99").asBytes();
|
public static final byte[] ravencoinPublicKeyHash = HashCode.fromString("bb00bb11bb22bb33bb44bb55bb66bb77bb88bb99").asBytes();
|
||||||
public static final int tradeTimeout = 20; // blocks
|
private static final String SYMBOL = "RVN";
|
||||||
public static final long redeemAmount = 80_40200000L;
|
private static final String NAME = "Ravencoin";
|
||||||
public static final long fundingAmount = 123_45600000L;
|
|
||||||
public static final long ravencoinAmount = 864200L; // 0.00864200 RVN
|
|
||||||
|
|
||||||
private static final Random RANDOM = new Random();
|
@Override
|
||||||
|
protected byte[] getPublicKey() {
|
||||||
@Before
|
return ravencoinPublicKeyHash;
|
||||||
public void beforeTest() throws DataException {
|
|
||||||
Common.useDefaultSettings();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Override
|
||||||
public void testCompile() {
|
protected byte[] buildQortalAT(String address, byte[] publicKey, long redeemAmount, long foreignAmount, int tradeTimeout) {
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(null);
|
return RavencoinACCTv3.buildQortalAT(address, publicKey, redeemAmount, foreignAmount, tradeTimeout);
|
||||||
|
|
||||||
byte[] creationBytes = RavencoinACCTv3.buildQortalAT(tradeAccount.getAddress(), ravencoinPublicKeyHash, redeemAmount, ravencoinAmount, tradeTimeout);
|
|
||||||
assertNotNull(creationBytes);
|
|
||||||
|
|
||||||
System.out.println("AT creation bytes: " + HashCode.fromBytes(creationBytes).toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Override
|
||||||
public void testDeploy() throws DataException {
|
protected ACCT getInstance() {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
return RavencoinACCTv3.getInstance();
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
|
|
||||||
long expectedBalance = deployersInitialBalance - fundingAmount - deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
expectedBalance = fundingAmount;
|
|
||||||
actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("AT's post-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
expectedBalance = partnersInitialBalance;
|
|
||||||
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Test orphaning
|
|
||||||
BlockUtils.orphanLastBlock(repository);
|
|
||||||
|
|
||||||
expectedBalance = deployersInitialBalance;
|
|
||||||
actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
expectedBalance = 0;
|
|
||||||
actualBalance = deployAtTransaction.getATAccount().getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("AT's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
expectedBalance = partnersInitialBalance;
|
|
||||||
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-orphan/pre-deployment balance incorrect", expectedBalance, actualBalance);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Override
|
||||||
@Test
|
protected int calcRefundTimeout(long partnersOfferMessageTransactionTimestamp, int lockTimeA) {
|
||||||
public void testOfferCancel() throws DataException {
|
return RavencoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
|
|
||||||
// Send creator's address to AT, instead of typical partner's address
|
|
||||||
byte[] messageData = RavencoinACCTv3.getInstance().buildCancelMessage(deployer.getAddress());
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress);
|
|
||||||
long messageFee = messageTransaction.getTransactionData().getFee();
|
|
||||||
|
|
||||||
// AT should process 'cancel' message in next block
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertTrue(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in CANCELLED mode
|
|
||||||
CrossChainTradeData tradeData = RavencoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.CANCELLED, tradeData.mode);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedMinimumBalance = deployersPostDeploymentBalance;
|
|
||||||
long expectedMaximumBalance = deployersInitialBalance - deployAtFee - messageFee;
|
|
||||||
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance);
|
|
||||||
assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance);
|
|
||||||
|
|
||||||
// Test orphaning
|
|
||||||
BlockUtils.orphanLastBlock(repository);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = deployersPostDeploymentBalance - messageFee;
|
|
||||||
actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Override
|
||||||
@Test
|
protected byte[] buildTradeMessage(String address, byte[] publicKey, byte[] hashOfSecretA, int lockTimeA, int refundTimeout) {
|
||||||
public void testOfferCancelInvalidLength() throws DataException {
|
return RavencoinACCTv3.buildTradeMessage(address, publicKey, hashOfSecretA, lockTimeA, refundTimeout);
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
|
|
||||||
// Instead of sending creator's address to AT, send too-short/invalid message
|
|
||||||
byte[] messageData = new byte[7];
|
|
||||||
RANDOM.nextBytes(messageData);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, deployer, messageData, atAddress);
|
|
||||||
long messageFee = messageTransaction.getTransactionData().getFee();
|
|
||||||
|
|
||||||
// AT should process 'cancel' message in next block
|
|
||||||
// As message is too short, it will be padded to 32bytes but cancel code doesn't care about message content, so should be ok
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertTrue(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in CANCELLED mode
|
|
||||||
CrossChainTradeData tradeData = RavencoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.CANCELLED, tradeData.mode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Override
|
||||||
@Test
|
protected byte[] buildRedeemMessage(byte[] secretA, String address) {
|
||||||
public void testTradingInfoProcessing() throws DataException {
|
return RavencoinACCTv3.buildRedeemMessage(secretA, address);
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = RavencoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = RavencoinACCTv3.buildTradeMessage(partner.getAddress(), ravencoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
Block postDeploymentBlock = BlockUtils.mintBlock(repository);
|
|
||||||
int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight();
|
|
||||||
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
CrossChainTradeData tradeData = RavencoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
|
|
||||||
// AT should be in TRADE mode
|
|
||||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
|
||||||
|
|
||||||
// Check hashOfSecretA was extracted correctly
|
|
||||||
assertTrue(Arrays.equals(hashOfSecretA, tradeData.hashOfSecretA));
|
|
||||||
|
|
||||||
// Check trade partner Qortal address was extracted correctly
|
|
||||||
assertEquals(partner.getAddress(), tradeData.qortalPartnerAddress);
|
|
||||||
|
|
||||||
// Check trade partner's ravencoin PKH was extracted correctly
|
|
||||||
assertTrue(Arrays.equals(ravencoinPublicKeyHash, tradeData.partnerForeignPKH));
|
|
||||||
|
|
||||||
// Test orphaning
|
|
||||||
BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = deployersPostDeploymentBalance;
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TEST SENDING TRADING INFO BUT NOT FROM AT CREATOR (SHOULD BE IGNORED)
|
@Override
|
||||||
@SuppressWarnings("unused")
|
protected byte[] getCodeBytesHash() {
|
||||||
@Test
|
return RavencoinACCTv3.CODE_BYTES_HASH;
|
||||||
public void testIncorrectTradeSender() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = RavencoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT BUT NOT FROM AT CREATOR
|
|
||||||
byte[] messageData = RavencoinACCTv3.buildTradeMessage(partner.getAddress(), ravencoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, bystander, messageData, atAddress);
|
|
||||||
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
long expectedBalance = partnersInitialBalance;
|
|
||||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-initial-payout balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
CrossChainTradeData tradeData = RavencoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
|
|
||||||
// AT should still be in OFFER mode
|
|
||||||
assertEquals(AcctMode.OFFERING, tradeData.mode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Override
|
||||||
@Test
|
protected String getSymbol() {
|
||||||
public void testAutomaticTradeRefund() throws DataException {
|
return SYMBOL;
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = RavencoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = RavencoinACCTv3.buildTradeMessage(partner.getAddress(), ravencoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
Block postDeploymentBlock = BlockUtils.mintBlock(repository);
|
|
||||||
int postDeploymentBlockHeight = postDeploymentBlock.getBlockData().getHeight();
|
|
||||||
|
|
||||||
// Check refund
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
|
|
||||||
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertTrue(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in REFUNDED mode
|
|
||||||
CrossChainTradeData tradeData = RavencoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.REFUNDED, tradeData.mode);
|
|
||||||
|
|
||||||
// Test orphaning
|
|
||||||
BlockUtils.orphanToBlock(repository, postDeploymentBlockHeight);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = deployersPostDeploymentBalance;
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Deployer's post-orphan/pre-refund balance incorrect", expectedBalance, actualBalance);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@Override
|
||||||
@Test
|
protected String getName() {
|
||||||
public void testCorrectSecretCorrectSender() throws DataException {
|
return NAME;
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = RavencoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = RavencoinACCTv3.buildTradeMessage(partner.getAddress(), ravencoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
// Give AT time to process message
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// Send correct secret to AT, from correct account
|
|
||||||
messageData = RavencoinACCTv3.buildRedeemMessage(secretA, partner.getAddress());
|
|
||||||
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
|
||||||
|
|
||||||
// AT should send funds in the next block
|
|
||||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertTrue(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in REDEEMED mode
|
|
||||||
CrossChainTradeData tradeData = RavencoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.REDEEMED, tradeData.mode);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee() + redeemAmount;
|
|
||||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-redeem balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Orphan redeem
|
|
||||||
BlockUtils.orphanLastBlock(repository);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee();
|
|
||||||
actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's post-orphan/pre-redeem balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Check AT state
|
|
||||||
ATStateData postOrphanAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
|
|
||||||
assertTrue("AT states mismatch", Arrays.equals(preRedeemAtStateData.getStateData(), postOrphanAtStateData.getStateData()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testCorrectSecretIncorrectSender() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
PrivateKeyAccount bystander = Common.getTestAccount(repository, "bob");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = RavencoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = RavencoinACCTv3.buildTradeMessage(partner.getAddress(), ravencoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
// Give AT time to process message
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// Send correct secret to AT, but from wrong account
|
|
||||||
messageData = RavencoinACCTv3.buildRedeemMessage(secretA, partner.getAddress());
|
|
||||||
messageTransaction = sendMessage(repository, bystander, messageData, atAddress);
|
|
||||||
|
|
||||||
// AT should NOT send funds in the next block
|
|
||||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is NOT finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertFalse(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should still be in TRADE mode
|
|
||||||
CrossChainTradeData tradeData = RavencoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
|
||||||
|
|
||||||
// Check balances
|
|
||||||
long expectedBalance = partnersInitialBalance;
|
|
||||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Check eventual refund
|
|
||||||
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testIncorrectSecretCorrectSender() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
long deployAtFee = deployAtTransaction.getTransactionData().getFee();
|
|
||||||
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = RavencoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = RavencoinACCTv3.buildTradeMessage(partner.getAddress(), ravencoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
// Give AT time to process message
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// Send incorrect secret to AT, from correct account
|
|
||||||
byte[] wrongSecret = new byte[32];
|
|
||||||
RANDOM.nextBytes(wrongSecret);
|
|
||||||
messageData = RavencoinACCTv3.buildRedeemMessage(wrongSecret, partner.getAddress());
|
|
||||||
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
|
||||||
|
|
||||||
// AT should NOT send funds in the next block
|
|
||||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is NOT finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertFalse(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should still be in TRADE mode
|
|
||||||
CrossChainTradeData tradeData = RavencoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
|
||||||
|
|
||||||
long expectedBalance = partnersInitialBalance - messageTransaction.getTransactionData().getFee();
|
|
||||||
long actualBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertEquals("Partner's balance incorrect", expectedBalance, actualBalance);
|
|
||||||
|
|
||||||
// Check eventual refund
|
|
||||||
checkTradeRefund(repository, deployer, deployersInitialBalance, deployAtFee);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testCorrectSecretCorrectSenderInvalidMessageLength() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
Account at = deployAtTransaction.getATAccount();
|
|
||||||
String atAddress = at.getAddress();
|
|
||||||
|
|
||||||
long partnersOfferMessageTransactionTimestamp = System.currentTimeMillis();
|
|
||||||
int lockTimeA = calcTestLockTimeA(partnersOfferMessageTransactionTimestamp);
|
|
||||||
int refundTimeout = RavencoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
|
||||||
|
|
||||||
// Send trade info to AT
|
|
||||||
byte[] messageData = RavencoinACCTv3.buildTradeMessage(partner.getAddress(), ravencoinPublicKeyHash, hashOfSecretA, lockTimeA, refundTimeout);
|
|
||||||
MessageTransaction messageTransaction = sendMessage(repository, tradeAccount, messageData, atAddress);
|
|
||||||
|
|
||||||
// Give AT time to process message
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// Send correct secret to AT, from correct account, but missing receive address, hence incorrect length
|
|
||||||
messageData = Bytes.concat(secretA);
|
|
||||||
messageTransaction = sendMessage(repository, partner, messageData, atAddress);
|
|
||||||
|
|
||||||
// AT should NOT send funds in the next block
|
|
||||||
ATStateData preRedeemAtStateData = repository.getATRepository().getLatestATState(atAddress);
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
|
|
||||||
// Check AT is NOT finished
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
assertFalse(atData.getIsFinished());
|
|
||||||
|
|
||||||
// AT should be in TRADING mode
|
|
||||||
CrossChainTradeData tradeData = RavencoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
assertEquals(AcctMode.TRADING, tradeData.mode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Test
|
|
||||||
public void testDescribeDeployed() throws DataException {
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
|
||||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
|
||||||
|
|
||||||
PrivateKeyAccount partner = Common.getTestAccount(repository, "dilbert");
|
|
||||||
|
|
||||||
long deployersInitialBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = doDeploy(repository, deployer, tradeAccount.getAddress());
|
|
||||||
|
|
||||||
List<ATData> executableAts = repository.getATRepository().getAllExecutableATs();
|
|
||||||
|
|
||||||
for (ATData atData : executableAts) {
|
|
||||||
String atAddress = atData.getATAddress();
|
|
||||||
byte[] codeBytes = atData.getCodeBytes();
|
|
||||||
byte[] codeHash = Crypto.digest(codeBytes);
|
|
||||||
|
|
||||||
System.out.println(String.format("%s: code length: %d byte%s, code hash: %s",
|
|
||||||
atAddress,
|
|
||||||
codeBytes.length,
|
|
||||||
(codeBytes.length != 1 ? "s": ""),
|
|
||||||
HashCode.fromBytes(codeHash)));
|
|
||||||
|
|
||||||
// Not one of ours?
|
|
||||||
if (!Arrays.equals(codeHash, RavencoinACCTv3.CODE_BYTES_HASH))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
describeAt(repository, atAddress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int calcTestLockTimeA(long messageTimestamp) {
|
|
||||||
return (int) (messageTimestamp / 1000L + tradeTimeout * 60);
|
|
||||||
}
|
|
||||||
|
|
||||||
private DeployAtTransaction doDeploy(Repository repository, PrivateKeyAccount deployer, String tradeAddress) throws DataException {
|
|
||||||
byte[] creationBytes = RavencoinACCTv3.buildQortalAT(tradeAddress, ravencoinPublicKeyHash, redeemAmount, ravencoinAmount, tradeTimeout);
|
|
||||||
|
|
||||||
long txTimestamp = System.currentTimeMillis();
|
|
||||||
byte[] lastReference = deployer.getLastReference();
|
|
||||||
|
|
||||||
if (lastReference == null) {
|
|
||||||
System.err.println(String.format("Qortal account %s has no last reference", deployer.getAddress()));
|
|
||||||
System.exit(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
Long fee = null;
|
|
||||||
String name = "QORT-RVN cross-chain trade";
|
|
||||||
String description = String.format("Qortal-Ravencoin cross-chain trade");
|
|
||||||
String atType = "ACCT";
|
|
||||||
String tags = "QORT-RVN ACCT";
|
|
||||||
|
|
||||||
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, deployer.getPublicKey(), fee, null);
|
|
||||||
TransactionData deployAtTransactionData = new DeployAtTransactionData(baseTransactionData, name, description, atType, tags, creationBytes, fundingAmount, Asset.QORT);
|
|
||||||
|
|
||||||
DeployAtTransaction deployAtTransaction = new DeployAtTransaction(repository, deployAtTransactionData);
|
|
||||||
|
|
||||||
fee = deployAtTransaction.calcRecommendedFee();
|
|
||||||
deployAtTransactionData.setFee(fee);
|
|
||||||
|
|
||||||
TransactionUtils.signAndMint(repository, deployAtTransactionData, deployer);
|
|
||||||
|
|
||||||
return deployAtTransaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
private MessageTransaction sendMessage(Repository repository, PrivateKeyAccount sender, byte[] data, String recipient) throws DataException {
|
|
||||||
long txTimestamp = System.currentTimeMillis();
|
|
||||||
byte[] lastReference = sender.getLastReference();
|
|
||||||
|
|
||||||
if (lastReference == null) {
|
|
||||||
System.err.println(String.format("Qortal account %s has no last reference", sender.getAddress()));
|
|
||||||
System.exit(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
Long fee = null;
|
|
||||||
int version = 4;
|
|
||||||
int nonce = 0;
|
|
||||||
long amount = 0;
|
|
||||||
Long assetId = null; // because amount is zero
|
|
||||||
|
|
||||||
BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, sender.getPublicKey(), fee, null);
|
|
||||||
TransactionData messageTransactionData = new MessageTransactionData(baseTransactionData, version, nonce, recipient, amount, assetId, data, false, false);
|
|
||||||
|
|
||||||
MessageTransaction messageTransaction = new MessageTransaction(repository, messageTransactionData);
|
|
||||||
|
|
||||||
fee = messageTransaction.calcRecommendedFee();
|
|
||||||
messageTransactionData.setFee(fee);
|
|
||||||
|
|
||||||
TransactionUtils.signAndMint(repository, messageTransactionData, sender);
|
|
||||||
|
|
||||||
return messageTransaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkTradeRefund(Repository repository, Account deployer, long deployersInitialBalance, long deployAtFee) throws DataException {
|
|
||||||
long deployersPostDeploymentBalance = deployersInitialBalance - fundingAmount - deployAtFee;
|
|
||||||
int refundTimeout = tradeTimeout / 2 + 1; // close enough
|
|
||||||
|
|
||||||
// AT should automatically refund deployer after 'refundTimeout' blocks
|
|
||||||
for (int blockCount = 0; blockCount <= refundTimeout; ++blockCount)
|
|
||||||
BlockUtils.mintBlock(repository);
|
|
||||||
|
|
||||||
// We don't bother to exactly calculate QORT spent running AT for several blocks, but we do know the expected range
|
|
||||||
long expectedMinimumBalance = deployersPostDeploymentBalance;
|
|
||||||
long expectedMaximumBalance = deployersInitialBalance - deployAtFee;
|
|
||||||
|
|
||||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
|
||||||
|
|
||||||
assertTrue(String.format("Deployer's balance %s should be above minimum %s", actualBalance, expectedMinimumBalance), actualBalance > expectedMinimumBalance);
|
|
||||||
assertTrue(String.format("Deployer's balance %s should be below maximum %s", actualBalance, expectedMaximumBalance), actualBalance < expectedMaximumBalance);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void describeAt(Repository repository, String atAddress) throws DataException {
|
|
||||||
ATData atData = repository.getATRepository().fromATAddress(atAddress);
|
|
||||||
CrossChainTradeData tradeData = RavencoinACCTv3.getInstance().populateTradeData(repository, atData);
|
|
||||||
|
|
||||||
Function<Long, String> epochMilliFormatter = (timestamp) -> LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneOffset.UTC).format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM));
|
|
||||||
int currentBlockHeight = repository.getBlockRepository().getBlockchainHeight();
|
|
||||||
|
|
||||||
System.out.print(String.format("%s:\n"
|
|
||||||
+ "\tmode: %s\n"
|
|
||||||
+ "\tcreator: %s,\n"
|
|
||||||
+ "\tcreation timestamp: %s,\n"
|
|
||||||
+ "\tcurrent balance: %s QORT,\n"
|
|
||||||
+ "\tis finished: %b,\n"
|
|
||||||
+ "\tredeem payout: %s QORT,\n"
|
|
||||||
+ "\texpected ravencoin: %s RVN,\n"
|
|
||||||
+ "\tcurrent block height: %d,\n",
|
|
||||||
tradeData.qortalAtAddress,
|
|
||||||
tradeData.mode,
|
|
||||||
tradeData.qortalCreator,
|
|
||||||
epochMilliFormatter.apply(tradeData.creationTimestamp),
|
|
||||||
Amounts.prettyAmount(tradeData.qortBalance),
|
|
||||||
atData.getIsFinished(),
|
|
||||||
Amounts.prettyAmount(tradeData.qortAmount),
|
|
||||||
Amounts.prettyAmount(tradeData.expectedForeignAmount),
|
|
||||||
currentBlockHeight));
|
|
||||||
|
|
||||||
if (tradeData.mode != AcctMode.OFFERING && tradeData.mode != AcctMode.CANCELLED) {
|
|
||||||
System.out.println(String.format("\trefund timeout: %d minutes,\n"
|
|
||||||
+ "\trefund height: block %d,\n"
|
|
||||||
+ "\tHASH160 of secret-A: %s,\n"
|
|
||||||
+ "\tRavencoin P2SH-A nLockTime: %d (%s),\n"
|
|
||||||
+ "\ttrade partner: %s\n"
|
|
||||||
+ "\tpartner's receiving address: %s",
|
|
||||||
tradeData.refundTimeout,
|
|
||||||
tradeData.tradeRefundHeight,
|
|
||||||
HashCode.fromBytes(tradeData.hashOfSecretA).toString().substring(0, 40),
|
|
||||||
tradeData.lockTimeA, epochMilliFormatter.apply(tradeData.lockTimeA * 1000L),
|
|
||||||
tradeData.qortalPartnerAddress,
|
|
||||||
tradeData.qortalPartnerReceivingAddress));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private PrivateKeyAccount createTradeAccount(Repository repository) {
|
|
||||||
// We actually use a known test account with funds to avoid PoW compute
|
|
||||||
return Common.getTestAccount(repository, "alice");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ public class BuySellTests extends Common {
|
|||||||
bob = Common.getTestAccount(repository, "bob");
|
bob = Common.getTestAccount(repository, "bob");
|
||||||
|
|
||||||
name = "test name" + " " + random.nextInt(1_000_000);
|
name = "test name" + " " + random.nextInt(1_000_000);
|
||||||
price = random.nextInt(1000) * Amounts.MULTIPLIER;
|
price = (random.nextInt(1000) + 1) * Amounts.MULTIPLIER;
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
@ -20,6 +20,7 @@ import org.qortal.repository.Repository;
|
|||||||
import org.qortal.repository.RepositoryManager;
|
import org.qortal.repository.RepositoryManager;
|
||||||
import org.qortal.test.common.*;
|
import org.qortal.test.common.*;
|
||||||
import org.qortal.test.common.transaction.TestTransaction;
|
import org.qortal.test.common.transaction.TestTransaction;
|
||||||
|
import org.qortal.transaction.PaymentTransaction;
|
||||||
import org.qortal.transaction.RegisterNameTransaction;
|
import org.qortal.transaction.RegisterNameTransaction;
|
||||||
import org.qortal.transaction.Transaction;
|
import org.qortal.transaction.Transaction;
|
||||||
import org.qortal.transaction.Transaction.ValidationResult;
|
import org.qortal.transaction.Transaction.ValidationResult;
|
||||||
@ -329,15 +330,19 @@ public class MiscTests extends Common {
|
|||||||
public void testRegisterNameFeeIncrease() throws Exception {
|
public void testRegisterNameFeeIncrease() throws Exception {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
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();
|
UnitFeesByTimestamp futureFeeIncrease = new UnitFeesByTimestamp();
|
||||||
futureFeeIncrease.timestamp = 9999999999999L; // 20 Nov 2286
|
futureFeeIncrease.timestamp = 9999999999999L; // 20 Nov 2286
|
||||||
futureFeeIncrease.fee = new AmountTypeAdapter().unmarshal("5");
|
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));
|
assertEquals(futureFeeIncrease.fee, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(futureFeeIncrease.timestamp));
|
||||||
|
|
||||||
// Validate unit fees pre and post 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(10000000, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(futureFeeIncrease.timestamp - 1)); // 0.1 QORT
|
||||||
assertEquals(500000000, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(futureFeeIncrease.timestamp)); // 5 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.timestamp = now + (60 * 60 * 1000L); // 1 hour in the future
|
||||||
futureFeeIncrease.fee = new AmountTypeAdapter().unmarshal("10");
|
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(pastFeeIncrease.fee, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(pastFeeIncrease.timestamp));
|
||||||
assertEquals(futureFeeIncrease.fee, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(futureFeeIncrease.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,11 @@
|
|||||||
"transactionExpiryPeriod": 86400000,
|
"transactionExpiryPeriod": 86400000,
|
||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "0.1",
|
"unitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" }
|
||||||
|
],
|
||||||
"nameRegistrationUnitFees": [
|
"nameRegistrationUnitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" },
|
||||||
{ "timestamp": 1645372800000, "fee": "5" }
|
{ "timestamp": 1645372800000, "fee": "5" }
|
||||||
],
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
@ -15,6 +18,7 @@
|
|||||||
"onlineAccountSignaturesMinLifetime": 3600000,
|
"onlineAccountSignaturesMinLifetime": 3600000,
|
||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||||
|
"mempowTransactionUpdatesTimestamp": 0,
|
||||||
"rewardsByHeight": [
|
"rewardsByHeight": [
|
||||||
{ "height": 1, "reward": 100 },
|
{ "height": 1, "reward": 100 },
|
||||||
{ "height": 11, "reward": 10 },
|
{ "height": 11, "reward": 10 },
|
||||||
|
@ -4,8 +4,11 @@
|
|||||||
"transactionExpiryPeriod": 86400000,
|
"transactionExpiryPeriod": 86400000,
|
||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "0.1",
|
"unitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" }
|
||||||
|
],
|
||||||
"nameRegistrationUnitFees": [
|
"nameRegistrationUnitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" },
|
||||||
{ "timestamp": 1645372800000, "fee": "5" }
|
{ "timestamp": 1645372800000, "fee": "5" }
|
||||||
],
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
@ -19,6 +22,7 @@
|
|||||||
"onlineAccountSignaturesMinLifetime": 3600000,
|
"onlineAccountSignaturesMinLifetime": 3600000,
|
||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||||
|
"mempowTransactionUpdatesTimestamp": 0,
|
||||||
"rewardsByHeight": [
|
"rewardsByHeight": [
|
||||||
{ "height": 1, "reward": 100 },
|
{ "height": 1, "reward": 100 },
|
||||||
{ "height": 11, "reward": 10 },
|
{ "height": 11, "reward": 10 },
|
||||||
|
@ -4,8 +4,11 @@
|
|||||||
"transactionExpiryPeriod": 86400000,
|
"transactionExpiryPeriod": 86400000,
|
||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "0.1",
|
"unitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" }
|
||||||
|
],
|
||||||
"nameRegistrationUnitFees": [
|
"nameRegistrationUnitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" },
|
||||||
{ "timestamp": 1645372800000, "fee": "5" }
|
{ "timestamp": 1645372800000, "fee": "5" }
|
||||||
],
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
@ -20,6 +23,7 @@
|
|||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||||
|
"mempowTransactionUpdatesTimestamp": 0,
|
||||||
"rewardsByHeight": [
|
"rewardsByHeight": [
|
||||||
{ "height": 1, "reward": 100 },
|
{ "height": 1, "reward": 100 },
|
||||||
{ "height": 11, "reward": 10 },
|
{ "height": 11, "reward": 10 },
|
||||||
|
@ -4,8 +4,11 @@
|
|||||||
"transactionExpiryPeriod": 86400000,
|
"transactionExpiryPeriod": 86400000,
|
||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "0.1",
|
"unitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" }
|
||||||
|
],
|
||||||
"nameRegistrationUnitFees": [
|
"nameRegistrationUnitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" },
|
||||||
{ "timestamp": 1645372800000, "fee": "5" }
|
{ "timestamp": 1645372800000, "fee": "5" }
|
||||||
],
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
@ -20,6 +23,7 @@
|
|||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||||
|
"mempowTransactionUpdatesTimestamp": 0,
|
||||||
"rewardsByHeight": [
|
"rewardsByHeight": [
|
||||||
{ "height": 1, "reward": 100 },
|
{ "height": 1, "reward": 100 },
|
||||||
{ "height": 11, "reward": 10 },
|
{ "height": 11, "reward": 10 },
|
||||||
|
@ -4,8 +4,11 @@
|
|||||||
"transactionExpiryPeriod": 86400000,
|
"transactionExpiryPeriod": 86400000,
|
||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "0.1",
|
"unitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" }
|
||||||
|
],
|
||||||
"nameRegistrationUnitFees": [
|
"nameRegistrationUnitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" },
|
||||||
{ "timestamp": 1645372800000, "fee": "5" }
|
{ "timestamp": 1645372800000, "fee": "5" }
|
||||||
],
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
@ -20,6 +23,7 @@
|
|||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||||
|
"mempowTransactionUpdatesTimestamp": 9999999999999,
|
||||||
"rewardsByHeight": [
|
"rewardsByHeight": [
|
||||||
{ "height": 1, "reward": 100 },
|
{ "height": 1, "reward": 100 },
|
||||||
{ "height": 11, "reward": 10 },
|
{ "height": 11, "reward": 10 },
|
||||||
|
@ -4,8 +4,11 @@
|
|||||||
"transactionExpiryPeriod": 86400000,
|
"transactionExpiryPeriod": 86400000,
|
||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "0.1",
|
"unitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" }
|
||||||
|
],
|
||||||
"nameRegistrationUnitFees": [
|
"nameRegistrationUnitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" },
|
||||||
{ "timestamp": 1645372800000, "fee": "5" }
|
{ "timestamp": 1645372800000, "fee": "5" }
|
||||||
],
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
@ -20,6 +23,7 @@
|
|||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||||
|
"mempowTransactionUpdatesTimestamp": 0,
|
||||||
"rewardsByHeight": [
|
"rewardsByHeight": [
|
||||||
{ "height": 1, "reward": 100 },
|
{ "height": 1, "reward": 100 },
|
||||||
{ "height": 11, "reward": 10 },
|
{ "height": 11, "reward": 10 },
|
||||||
|
@ -4,8 +4,11 @@
|
|||||||
"transactionExpiryPeriod": 86400000,
|
"transactionExpiryPeriod": 86400000,
|
||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "0.1",
|
"unitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" }
|
||||||
|
],
|
||||||
"nameRegistrationUnitFees": [
|
"nameRegistrationUnitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" },
|
||||||
{ "timestamp": 1645372800000, "fee": "5" }
|
{ "timestamp": 1645372800000, "fee": "5" }
|
||||||
],
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
@ -20,6 +23,7 @@
|
|||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||||
|
"mempowTransactionUpdatesTimestamp": 0,
|
||||||
"rewardsByHeight": [
|
"rewardsByHeight": [
|
||||||
{ "height": 1, "reward": 100 },
|
{ "height": 1, "reward": 100 },
|
||||||
{ "height": 11, "reward": 10 },
|
{ "height": 11, "reward": 10 },
|
||||||
|
@ -4,8 +4,11 @@
|
|||||||
"transactionExpiryPeriod": 86400000,
|
"transactionExpiryPeriod": 86400000,
|
||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "0.1",
|
"unitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" }
|
||||||
|
],
|
||||||
"nameRegistrationUnitFees": [
|
"nameRegistrationUnitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" },
|
||||||
{ "timestamp": 1645372800000, "fee": "5" }
|
{ "timestamp": 1645372800000, "fee": "5" }
|
||||||
],
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
@ -20,6 +23,7 @@
|
|||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||||
|
"mempowTransactionUpdatesTimestamp": 0,
|
||||||
"rewardsByHeight": [
|
"rewardsByHeight": [
|
||||||
{ "height": 1, "reward": 100 },
|
{ "height": 1, "reward": 100 },
|
||||||
{ "height": 11, "reward": 10 },
|
{ "height": 11, "reward": 10 },
|
||||||
|
@ -4,8 +4,11 @@
|
|||||||
"transactionExpiryPeriod": 86400000,
|
"transactionExpiryPeriod": 86400000,
|
||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "0.1",
|
"unitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" }
|
||||||
|
],
|
||||||
"nameRegistrationUnitFees": [
|
"nameRegistrationUnitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" },
|
||||||
{ "timestamp": 1645372800000, "fee": "5" }
|
{ "timestamp": 1645372800000, "fee": "5" }
|
||||||
],
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
@ -20,6 +23,7 @@
|
|||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||||
|
"mempowTransactionUpdatesTimestamp": 0,
|
||||||
"rewardsByHeight": [
|
"rewardsByHeight": [
|
||||||
{ "height": 1, "reward": 100 },
|
{ "height": 1, "reward": 100 },
|
||||||
{ "height": 11, "reward": 10 },
|
{ "height": 11, "reward": 10 },
|
||||||
|
@ -4,8 +4,11 @@
|
|||||||
"transactionExpiryPeriod": 86400000,
|
"transactionExpiryPeriod": 86400000,
|
||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "0.1",
|
"unitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" }
|
||||||
|
],
|
||||||
"nameRegistrationUnitFees": [
|
"nameRegistrationUnitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" },
|
||||||
{ "timestamp": 1645372800000, "fee": "5" }
|
{ "timestamp": 1645372800000, "fee": "5" }
|
||||||
],
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
@ -20,6 +23,7 @@
|
|||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||||
|
"mempowTransactionUpdatesTimestamp": 0,
|
||||||
"rewardsByHeight": [
|
"rewardsByHeight": [
|
||||||
{ "height": 1, "reward": 100 },
|
{ "height": 1, "reward": 100 },
|
||||||
{ "height": 11, "reward": 10 },
|
{ "height": 11, "reward": 10 },
|
||||||
|
@ -4,8 +4,11 @@
|
|||||||
"transactionExpiryPeriod": 86400000,
|
"transactionExpiryPeriod": 86400000,
|
||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "0.1",
|
"unitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" }
|
||||||
|
],
|
||||||
"nameRegistrationUnitFees": [
|
"nameRegistrationUnitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" },
|
||||||
{ "timestamp": 1645372800000, "fee": "5" }
|
{ "timestamp": 1645372800000, "fee": "5" }
|
||||||
],
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
@ -19,6 +22,7 @@
|
|||||||
"onlineAccountSignaturesMinLifetime": 3600000,
|
"onlineAccountSignaturesMinLifetime": 3600000,
|
||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||||
|
"mempowTransactionUpdatesTimestamp": 0,
|
||||||
"rewardsByHeight": [
|
"rewardsByHeight": [
|
||||||
{ "height": 1, "reward": 100 },
|
{ "height": 1, "reward": 100 },
|
||||||
{ "height": 11, "reward": 10 },
|
{ "height": 11, "reward": 10 },
|
||||||
|
@ -4,8 +4,11 @@
|
|||||||
"transactionExpiryPeriod": 86400000,
|
"transactionExpiryPeriod": 86400000,
|
||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 0,
|
"maxBytesPerUnitFee": 0,
|
||||||
"unitFee": "0.00000001",
|
"unitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.00000001" }
|
||||||
|
],
|
||||||
"nameRegistrationUnitFees": [
|
"nameRegistrationUnitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.00000001" },
|
||||||
{ "timestamp": 1645372800000, "fee": "5" }
|
{ "timestamp": 1645372800000, "fee": "5" }
|
||||||
],
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
@ -20,6 +23,7 @@
|
|||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||||
|
"mempowTransactionUpdatesTimestamp": 0,
|
||||||
"rewardsByHeight": [
|
"rewardsByHeight": [
|
||||||
{ "height": 1, "reward": 100 },
|
{ "height": 1, "reward": 100 },
|
||||||
{ "height": 11, "reward": 10 },
|
{ "height": 11, "reward": 10 },
|
||||||
|
@ -4,9 +4,13 @@
|
|||||||
"transactionExpiryPeriod": 86400000,
|
"transactionExpiryPeriod": 86400000,
|
||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "0.1",
|
"unitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" },
|
||||||
|
{ "timestamp": 9999999999999, "fee": "1" }
|
||||||
|
],
|
||||||
"nameRegistrationUnitFees": [
|
"nameRegistrationUnitFees": [
|
||||||
{ "timestamp": 1645372800000, "fee": "5" }
|
{ "timestamp": 0, "fee": "0.1" },
|
||||||
|
{ "timestamp": 9999999999999, "fee": "5" }
|
||||||
],
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
"minAccountLevelToRewardShare": 5,
|
"minAccountLevelToRewardShare": 5,
|
||||||
@ -20,6 +24,7 @@
|
|||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
"onlineAccountsModulusV2Timestamp": 9999999999999,
|
||||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||||
|
"mempowTransactionUpdatesTimestamp": 0,
|
||||||
"rewardsByHeight": [
|
"rewardsByHeight": [
|
||||||
{ "height": 1, "reward": 100 },
|
{ "height": 1, "reward": 100 },
|
||||||
{ "height": 11, "reward": 10 },
|
{ "height": 11, "reward": 10 },
|
||||||
|
@ -4,7 +4,9 @@
|
|||||||
"transactionExpiryPeriod": 86400000,
|
"transactionExpiryPeriod": 86400000,
|
||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "0.001",
|
"unitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.001" }
|
||||||
|
],
|
||||||
"nameRegistrationUnitFees": [
|
"nameRegistrationUnitFees": [
|
||||||
{ "timestamp": 0, "fee": "1.25" }
|
{ "timestamp": 0, "fee": "1.25" }
|
||||||
],
|
],
|
||||||
@ -24,6 +26,7 @@
|
|||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
"onlineAccountsModulusV2Timestamp": 0,
|
"onlineAccountsModulusV2Timestamp": 0,
|
||||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||||
|
"mempowTransactionUpdatesTimestamp": 1692554400000,
|
||||||
"rewardsByHeight": [
|
"rewardsByHeight": [
|
||||||
{ "height": 1, "reward": 5.00 },
|
{ "height": 1, "reward": 5.00 },
|
||||||
{ "height": 259201, "reward": 4.75 },
|
{ "height": 259201, "reward": 4.75 },
|
||||||
|
@ -50,7 +50,7 @@ tx_json=$( cat <<TX_END
|
|||||||
{
|
{
|
||||||
"timestamp": ${timestamp},
|
"timestamp": ${timestamp},
|
||||||
"reference": "${lastref}",
|
"reference": "${lastref}",
|
||||||
"fee": 0.001,
|
"fee": 0.01,
|
||||||
"txGroupId": 0,
|
"txGroupId": 0,
|
||||||
"adminPublicKey": "${pubkey}",
|
"adminPublicKey": "${pubkey}",
|
||||||
"pendingSignature": "${sig}",
|
"pendingSignature": "${sig}",
|
||||||
|
@ -50,7 +50,7 @@ tx_json=$( cat <<TX_END
|
|||||||
{
|
{
|
||||||
"timestamp": ${timestamp},
|
"timestamp": ${timestamp},
|
||||||
"reference": "${lastref}",
|
"reference": "${lastref}",
|
||||||
"fee": 0.001,
|
"fee": 0.01,
|
||||||
"txGroupId": 0,
|
"txGroupId": 0,
|
||||||
"adminPublicKey": "${pubkey}",
|
"adminPublicKey": "${pubkey}",
|
||||||
"pendingSignature": "${sig}",
|
"pendingSignature": "${sig}",
|
||||||
|
@ -1,163 +0,0 @@
|
|||||||
#!/usr/bin/env perl
|
|
||||||
|
|
||||||
use strict;
|
|
||||||
use warnings;
|
|
||||||
use POSIX;
|
|
||||||
use Getopt::Std;
|
|
||||||
use File::Slurp;
|
|
||||||
|
|
||||||
sub usage() {
|
|
||||||
die("usage: $0 [-p api-port] dev-private-key [short-commit-hash]\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
my %opt;
|
|
||||||
getopts('p:', \%opt);
|
|
||||||
|
|
||||||
usage() if @ARGV < 1 || @ARGV > 2;
|
|
||||||
|
|
||||||
my $port = $opt{p} || 12391;
|
|
||||||
my $privkey = shift @ARGV;
|
|
||||||
my $commit_hash = shift @ARGV;
|
|
||||||
|
|
||||||
my $git_dir = `git rev-parse --show-toplevel`;
|
|
||||||
die("Cannot determine git top level dir\n") unless $git_dir;
|
|
||||||
|
|
||||||
chomp $git_dir;
|
|
||||||
chdir($git_dir) || die("Can't change directory to $git_dir: $!\n");
|
|
||||||
|
|
||||||
open(POM, '<', 'pom.xml') || die ("Can't open 'pom.xml': $!\n");
|
|
||||||
my $project;
|
|
||||||
while (<POM>) {
|
|
||||||
if (m/<artifactId>(\w+)<.artifactId>/o) {
|
|
||||||
$project = $1;
|
|
||||||
last;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close(POM);
|
|
||||||
|
|
||||||
my $apikey = read_file('apikey.txt');
|
|
||||||
|
|
||||||
# Do we need to determine commit hash?
|
|
||||||
unless ($commit_hash) {
|
|
||||||
# determine git branch
|
|
||||||
my $branch_name = ` git symbolic-ref -q HEAD `;
|
|
||||||
chomp $branch_name;
|
|
||||||
$branch_name =~ s|^refs/heads/||; # ${branch_name##refs/heads/}
|
|
||||||
|
|
||||||
# short-form commit hash on base branch (non-auto-update)
|
|
||||||
$commit_hash ||= `git show --no-patch --format=%h`;
|
|
||||||
die("Can't find commit hash\n") if ! defined $commit_hash;
|
|
||||||
chomp $commit_hash;
|
|
||||||
printf "Commit hash on '%s' branch: %s\n", $branch_name, $commit_hash;
|
|
||||||
} else {
|
|
||||||
printf "Using given commit hash: %s\n", $commit_hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
# build timestamp / commit timestamp on base branch
|
|
||||||
my $timestamp = `git show --no-patch --format=%ct ${commit_hash}`;
|
|
||||||
die("Can't determine commit timestamp\n") if ! defined $timestamp;
|
|
||||||
$timestamp *= 1000; # Convert to milliseconds
|
|
||||||
|
|
||||||
# locate sha256 utility
|
|
||||||
my $SHA256 = `which sha256sum || which sha256`;
|
|
||||||
chomp $SHA256;
|
|
||||||
die("Can't find sha256sum or sha256\n") unless length($SHA256) > 0;
|
|
||||||
|
|
||||||
# SHA256 of actual update file
|
|
||||||
my $sha256 = `git show auto-update-${commit_hash}:${project}.update | ${SHA256} | head -c 64`;
|
|
||||||
die("Can't calculate SHA256 of ${project}.update\n") unless $sha256 =~ m/(\S{64})/;
|
|
||||||
chomp $sha256;
|
|
||||||
|
|
||||||
# long-form commit hash of HEAD on auto-update branch
|
|
||||||
my $update_hash = `git rev-parse refs/heads/auto-update-${commit_hash}`;
|
|
||||||
die("Can't find commit hash for HEAD on auto-update-${commit_hash} branch\n") if ! defined $update_hash;
|
|
||||||
chomp $update_hash;
|
|
||||||
|
|
||||||
printf "Build timestamp (ms): %d / 0x%016x\n", $timestamp, $timestamp;
|
|
||||||
printf "Auto-update commit hash: %s\n", $update_hash;
|
|
||||||
printf "SHA256 of ${project}.update: %s\n", $sha256;
|
|
||||||
|
|
||||||
my $tx_type = 10;
|
|
||||||
my $tx_timestamp = time() * 1000;
|
|
||||||
my $tx_group_id = 1;
|
|
||||||
my $service = 1;
|
|
||||||
printf "\nARBITRARY(%d) transaction with timestamp %d, txGroupID %d and service %d\n", $tx_type, $tx_timestamp, $tx_group_id, $service;
|
|
||||||
|
|
||||||
my $data_hex = sprintf "%016x%s%s", $timestamp, $update_hash, $sha256;
|
|
||||||
printf "\nARBITRARY transaction data payload: %s\n", $data_hex;
|
|
||||||
|
|
||||||
my $n_payments = 0;
|
|
||||||
my $data_type = 1; # RAW_DATA
|
|
||||||
my $data_length = length($data_hex) / 2; # two hex chars per byte
|
|
||||||
my $fee = 0;
|
|
||||||
my $nonce = 0;
|
|
||||||
my $name_length = 0;
|
|
||||||
my $identifier_length = 0;
|
|
||||||
my $method = 0; # PUT
|
|
||||||
my $secret_length = 0;
|
|
||||||
my $compression = 0; # None
|
|
||||||
my $metadata_hash_length = 0;
|
|
||||||
|
|
||||||
die("Something's wrong: data length is not 60 bytes!\n") if $data_length != 60;
|
|
||||||
|
|
||||||
my $pubkey = `curl --silent --url http://localhost:${port}/utils/publickey --data ${privkey}`;
|
|
||||||
die("Can't convert private key to public key:\n$pubkey\n") unless $pubkey =~ m/^\w{44}$/;
|
|
||||||
printf "\nPublic key: %s\n", $pubkey;
|
|
||||||
|
|
||||||
my $pubkey_hex = `curl --silent --url http://localhost:${port}/utils/frombase58 --data ${pubkey}`;
|
|
||||||
die("Can't convert base58 public key to hex:\n$pubkey_hex\n") unless $pubkey_hex =~ m/^[A-Za-z0-9]{64}$/;
|
|
||||||
printf "Public key hex: %s\n", $pubkey_hex;
|
|
||||||
|
|
||||||
my $address = `curl --silent --url http://localhost:${port}/addresses/convert/${pubkey}`;
|
|
||||||
die("Can't convert base58 public key to address:\n$address\n") unless $address =~ m/^\w{33,34}$/;
|
|
||||||
printf "Address: %s\n", $address;
|
|
||||||
|
|
||||||
my $reference = `curl --silent --url http://localhost:${port}/addresses/lastreference/${address}`;
|
|
||||||
die("Can't fetch last reference for $address:\n$reference\n") unless $reference =~ m/^\w{87,88}$/;
|
|
||||||
printf "Last reference: %s\n", $reference;
|
|
||||||
|
|
||||||
my $reference_hex = `curl --silent --url http://localhost:${port}/utils/frombase58 --data ${reference}`;
|
|
||||||
die("Can't convert base58 reference to hex:\n$reference_hex\n") unless $reference_hex =~ m/^[A-Za-z0-9]{128}$/;
|
|
||||||
printf "Last reference hex: %s\n", $reference_hex;
|
|
||||||
|
|
||||||
my $raw_tx_hex = sprintf("%08x%016x%08x%s%s%08x%08x%08x%08x%08x%08x%08x%08x%02x%08x%s%08x%08x%016x", $tx_type, $tx_timestamp, $tx_group_id, $reference_hex, $pubkey_hex, $nonce, $name_length, $identifier_length, $method, $secret_length, $compression, $n_payments, $service, $data_type, $data_length, $data_hex, $data_length, $metadata_hash_length, $fee);
|
|
||||||
printf "\nRaw transaction hex:\n%s\n", $raw_tx_hex;
|
|
||||||
|
|
||||||
my $raw_tx = `curl --silent --url http://localhost:${port}/utils/tobase58/${raw_tx_hex}`;
|
|
||||||
die("Can't convert raw transaction hex to base58:\n$raw_tx\n") unless $raw_tx =~ m/^\w{300,320}$/; # Roughly 305 to 320 base58 chars
|
|
||||||
printf "\nRaw transaction (base58):\n%s\n", $raw_tx;
|
|
||||||
|
|
||||||
my $computed_tx = `curl --silent -X POST --url http://localhost:${port}/arbitrary/compute -H "X-API-KEY: ${apikey}" -d "${raw_tx}"`;
|
|
||||||
die("Can't compute nonce for transaction:\n$computed_tx\n") unless $computed_tx =~ m/^\w{300,320}$/; # Roughly 300 to 320 base58 chars
|
|
||||||
printf "\nRaw computed transaction (base58):\n%s\n", $computed_tx;
|
|
||||||
|
|
||||||
my $sign_data = qq|' { "privateKey": "${privkey}", "transactionBytes": "${computed_tx}" } '|;
|
|
||||||
my $signed_tx = `curl --silent -H "accept: text/plain" -H "Content-Type: application/json" --url http://localhost:${port}/transactions/sign --data ${sign_data}`;
|
|
||||||
die("Can't sign raw transaction:\n$signed_tx\n") unless $signed_tx =~ m/^\w{390,410}$/; # +90ish longer than $raw_tx
|
|
||||||
printf "\nSigned transaction:\n%s\n", $signed_tx;
|
|
||||||
|
|
||||||
# Check we can actually fetch update
|
|
||||||
my $origin = `git remote get-url origin`;
|
|
||||||
die("Unable to get github url for 'origin'?\n") unless $origin && $origin =~ m/:(.*)\.git$/;
|
|
||||||
my $repo = $1;
|
|
||||||
my $update_url = "https://github.com/${repo}/raw/${update_hash}/${project}.update";
|
|
||||||
|
|
||||||
my $fetch_result = `curl --silent -o /dev/null --location --range 0-1 --head --write-out '%{http_code}' --url ${update_url}`;
|
|
||||||
die("\nUnable to fetch update from ${update_url}\n") if $fetch_result ne '200';
|
|
||||||
printf "\nUpdate fetchable from ${update_url}\n";
|
|
||||||
|
|
||||||
# Flush STDOUT after every output
|
|
||||||
$| = 1;
|
|
||||||
print "\n";
|
|
||||||
for (my $delay = 5; $delay > 0; --$delay) {
|
|
||||||
printf "\rSubmitting transaction in %d second%s... CTRL-C to abort ", $delay, ($delay != 1 ? 's' : '');
|
|
||||||
sleep 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
printf "\rSubmitting transaction NOW... \n";
|
|
||||||
my $result = `curl --silent --url http://localhost:${port}/transactions/process --data ${signed_tx}`;
|
|
||||||
chomp $result;
|
|
||||||
die("Transaction wasn't accepted:\n$result\n") unless $result eq 'true';
|
|
||||||
|
|
||||||
my $decoded_tx = `curl --silent -H "Content-Type: application/json" --url http://localhost:${port}/transactions/decode --data ${signed_tx}`;
|
|
||||||
printf "\nTransaction accepted:\n$decoded_tx\n";
|
|
@ -4,6 +4,7 @@ use strict;
|
|||||||
use warnings;
|
use warnings;
|
||||||
use POSIX;
|
use POSIX;
|
||||||
use Getopt::Std;
|
use Getopt::Std;
|
||||||
|
use File::Slurp;
|
||||||
|
|
||||||
sub usage() {
|
sub usage() {
|
||||||
die("usage: $0 [-p api-port] dev-private-key [short-commit-hash]\n");
|
die("usage: $0 [-p api-port] dev-private-key [short-commit-hash]\n");
|
||||||
@ -34,6 +35,8 @@ while (<POM>) {
|
|||||||
}
|
}
|
||||||
close(POM);
|
close(POM);
|
||||||
|
|
||||||
|
my $apikey = read_file('apikey.txt');
|
||||||
|
|
||||||
# Do we need to determine commit hash?
|
# Do we need to determine commit hash?
|
||||||
unless ($commit_hash) {
|
unless ($commit_hash) {
|
||||||
# determine git branch
|
# determine git branch
|
||||||
@ -84,9 +87,16 @@ my $data_hex = sprintf "%016x%s%s", $timestamp, $update_hash, $sha256;
|
|||||||
printf "\nARBITRARY transaction data payload: %s\n", $data_hex;
|
printf "\nARBITRARY transaction data payload: %s\n", $data_hex;
|
||||||
|
|
||||||
my $n_payments = 0;
|
my $n_payments = 0;
|
||||||
my $is_raw = 1; # RAW_DATA
|
my $data_type = 1; # RAW_DATA
|
||||||
my $data_length = length($data_hex) / 2; # two hex chars per byte
|
my $data_length = length($data_hex) / 2; # two hex chars per byte
|
||||||
my $fee = 0.001 * 1e8;
|
my $fee = 0.01 * 1e8;
|
||||||
|
my $nonce = 0;
|
||||||
|
my $name_length = 0;
|
||||||
|
my $identifier_length = 0;
|
||||||
|
my $method = 0; # PUT
|
||||||
|
my $secret_length = 0;
|
||||||
|
my $compression = 0; # None
|
||||||
|
my $metadata_hash_length = 0;
|
||||||
|
|
||||||
die("Something's wrong: data length is not 60 bytes!\n") if $data_length != 60;
|
die("Something's wrong: data length is not 60 bytes!\n") if $data_length != 60;
|
||||||
|
|
||||||
@ -110,16 +120,16 @@ my $reference_hex = `curl --silent --url http://localhost:${port}/utils/frombase
|
|||||||
die("Can't convert base58 reference to hex:\n$reference_hex\n") unless $reference_hex =~ m/^[A-Za-z0-9]{128}$/;
|
die("Can't convert base58 reference to hex:\n$reference_hex\n") unless $reference_hex =~ m/^[A-Za-z0-9]{128}$/;
|
||||||
printf "Last reference hex: %s\n", $reference_hex;
|
printf "Last reference hex: %s\n", $reference_hex;
|
||||||
|
|
||||||
my $raw_tx_hex = sprintf("%08x%016x%08x%s%s%08x%08x%02x%08x%s%016x", $tx_type, $tx_timestamp, $tx_group_id, $reference_hex, $pubkey_hex, $n_payments, $service, $is_raw, $data_length, $data_hex, $fee);
|
my $raw_tx_hex = sprintf("%08x%016x%08x%s%s%08x%08x%08x%08x%08x%08x%08x%08x%02x%08x%s%08x%08x%016x", $tx_type, $tx_timestamp, $tx_group_id, $reference_hex, $pubkey_hex, $nonce, $name_length, $identifier_length, $method, $secret_length, $compression, $n_payments, $service, $data_type, $data_length, $data_hex, $data_length, $metadata_hash_length, $fee);
|
||||||
printf "\nRaw transaction hex:\n%s\n", $raw_tx_hex;
|
printf "\nRaw transaction hex:\n%s\n", $raw_tx_hex;
|
||||||
|
|
||||||
my $raw_tx = `curl --silent --url http://localhost:${port}/utils/tobase58/${raw_tx_hex}`;
|
my $raw_tx = `curl --silent --url http://localhost:${port}/utils/tobase58/${raw_tx_hex}`;
|
||||||
die("Can't convert raw transaction hex to base58:\n$raw_tx\n") unless $raw_tx =~ m/^\w{255,265}$/; # Roughly 255 to 265 base58 chars
|
die("Can't convert raw transaction hex to base58:\n$raw_tx\n") unless $raw_tx =~ m/^\w{300,320}$/; # Roughly 305 to 320 base58 chars
|
||||||
printf "\nRaw transaction (base58):\n%s\n", $raw_tx;
|
printf "\nRaw transaction (base58):\n%s\n", $raw_tx;
|
||||||
|
|
||||||
my $sign_data = qq|' { "privateKey": "${privkey}", "transactionBytes": "${raw_tx}" } '|;
|
my $sign_data = qq|' { "privateKey": "${privkey}", "transactionBytes": "${raw_tx}" } '|;
|
||||||
my $signed_tx = `curl --silent -H "accept: text/plain" -H "Content-Type: application/json" --url http://localhost:${port}/transactions/sign --data ${sign_data}`;
|
my $signed_tx = `curl --silent -H "accept: text/plain" -H "Content-Type: application/json" --url http://localhost:${port}/transactions/sign --data ${sign_data}`;
|
||||||
die("Can't sign raw transaction:\n$signed_tx\n") unless $signed_tx =~ m/^\w{345,355}$/; # +90ish longer than $raw_tx
|
die("Can't sign raw transaction:\n$signed_tx\n") unless $signed_tx =~ m/^\w{390,410}$/; # +90ish longer than $raw_tx
|
||||||
printf "\nSigned transaction:\n%s\n", $signed_tx;
|
printf "\nSigned transaction:\n%s\n", $signed_tx;
|
||||||
|
|
||||||
# Check we can actually fetch update
|
# Check we can actually fetch update
|
||||||
|
122
tools/qdn
122
tools/qdn
@ -5,10 +5,10 @@ host="localhost"
|
|||||||
port=12391
|
port=12391
|
||||||
|
|
||||||
if [ -z "$*" ]; then
|
if [ -z "$*" ]; then
|
||||||
echo "Usage:"
|
echo "Usage:"
|
||||||
echo
|
echo
|
||||||
echo "Host/update data:"
|
echo "Host/update data:"
|
||||||
echo "qdn POST [service] [name] PATH [dirpath] <identifier>"
|
echo "qdn POST [service] [name] PATH [dirpath] <identifier> <title> <description> <tags=tag1,tag2,tag3> <category> <fee> <preview (true or false)>"
|
||||||
echo "qdn POST [service] [name] STRING [data-string] <identifier>"
|
echo "qdn POST [service] [name] STRING [data-string] <identifier>"
|
||||||
echo
|
echo
|
||||||
echo "Fetch data:"
|
echo "Fetch data:"
|
||||||
@ -22,6 +22,21 @@ if [ -z "$*" ]; then
|
|||||||
exit
|
exit
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Default ports for Qortal
|
||||||
|
mainnet_port=12391
|
||||||
|
testnet_port=62391
|
||||||
|
|
||||||
|
# Check if the '-t' operator is passed, if so change to utilizing testnet.
|
||||||
|
if [[ "$1" == "-t" ]]; then
|
||||||
|
# Use testnet port
|
||||||
|
port=$testnet_port
|
||||||
|
shift
|
||||||
|
else
|
||||||
|
# Use mainnet port
|
||||||
|
port=$mainnet_port
|
||||||
|
fi
|
||||||
|
|
||||||
script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
script_dir=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||||
|
|
||||||
if [ -f "apikey.txt" ]; then
|
if [ -f "apikey.txt" ]; then
|
||||||
@ -37,32 +52,46 @@ service=$2
|
|||||||
name=$3
|
name=$3
|
||||||
|
|
||||||
if [ -z "${method}" ]; then
|
if [ -z "${method}" ]; then
|
||||||
echo "Error: missing method"; exit
|
echo "Error: missing method"
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
if [ -z "${service}" ]; then
|
if [ -z "${service}" ]; then
|
||||||
echo "Error: missing service"; exit
|
echo "Error: missing service"
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
if [ -z "${name}" ]; then
|
if [ -z "${name}" ]; then
|
||||||
echo "Error: missing name"; exit
|
echo "Error: missing name"
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
if [[ "${method}" == "POST" ]]; then
|
if [[ "${method}" == "POST" ]]; then
|
||||||
type=$4
|
type=$4
|
||||||
data=$5
|
data=$5
|
||||||
identifier=$6
|
identifier=$6
|
||||||
|
title=$7
|
||||||
|
description=$8
|
||||||
|
tags=$9
|
||||||
|
category=${10}
|
||||||
|
fee=${11}
|
||||||
|
preview=${12}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if [ -z "${data}" ]; then
|
if [ -z "${data}" ]; then
|
||||||
if [[ "${type}" == "PATH" ]]; then
|
if [[ "${type}" == "PATH" ]]; then
|
||||||
echo "Error: missing directory"; exit
|
echo "Error: missing directory - please use a path to a directory with a SINGLE file wishing to be published"
|
||||||
|
exit 1
|
||||||
elif [[ "${type}" == "STRING" ]]; then
|
elif [[ "${type}" == "STRING" ]]; then
|
||||||
echo "Error: missing data string"; exit
|
echo "Error: missing data string - please input the data string you wish to publish"
|
||||||
|
exit 1
|
||||||
else
|
else
|
||||||
echo "Error: unrecognized type"; exit
|
echo "Error: unrecognized type"
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
if [ -z "${QORTAL_PRIVKEY}" ]; then
|
if [ -z "${QORTAL_PRIVKEY}" ]; then
|
||||||
echo "Error: missing private key. Set it by running: export QORTAL_PRIVKEY=privkeyhere"; exit
|
echo "Error: missing private key. Set it by running: export QORTAL_PRIVKEY=privkeyhere"
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -z "${identifier}" ]; then
|
if [ -z "${identifier}" ]; then
|
||||||
@ -75,30 +104,88 @@ if [[ "${method}" == "POST" ]]; then
|
|||||||
elif [[ "${type}" == "STRING" ]]; then
|
elif [[ "${type}" == "STRING" ]]; then
|
||||||
type_component="/string"
|
type_component="/string"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Create tags component in URL, comma-separated list of tags, will be added to the tags call.
|
||||||
|
tags_component=""
|
||||||
|
if [ -n "${tags}" ]; then
|
||||||
|
IFS=',' read -ra tag_array <<< "${tags}"
|
||||||
|
for tag in "${tag_array[@]}"; do
|
||||||
|
tags_component+="&tags=${tag}"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z ${tags_component} ]; then
|
||||||
|
tags_component=""
|
||||||
|
echo "nothing in tags, using empty tags"
|
||||||
|
fi
|
||||||
|
|
||||||
|
#Create category component with pre-defined list of categories. Error if category is specified but not in list.
|
||||||
|
allowed_categories=("ART" "AUTOMOTIVE" "BEAUTY" "BOOKS" "BUSINESS" "COMMUNICATIONS" "CRYPTOCURRENCY" "CULTURE" "DATING" "DESIGN" "ENTERTAINMENT" "EVENTS" "FAITH" "FASHION" "FINANCE" "FOOD" "GAMING" "GEOGRAPHY" "HEALTH" "HISTORY" "HOME" "KNOWLEDGE" "LANGUAGE" "LIFESTYLE" "MANUFACTURING" "MAPS" "MUSIC" "NEWS" "OTHER" "PETS" "PHILOSOPHY" "PHOTOGRAPHY" "POLITICS" "PRODUCE" "PRODUCTIVITY" "PSYCHOLOGY" "QORTAL" "SCIENCE" "SELF_CARE" "SELF_SUFFICIENCY" "SHOPPING" "SOCIAL" "SOFTWARE" "SPIRITUALITY" "SPORTS" "STORYTELLING" "TECHNOLOGY" "TOOLS" "TRAVEL" "UNCATEGORIZED" "VIDEO" "WEATHER")
|
||||||
|
|
||||||
|
if [[ -n "$category" && ! " ${allowed_categories[@]} " =~ " $category " ]]; then
|
||||||
|
echo "Error: Invalid category. Allowed categories are: ${allowed_categories[*]} be sure to place your overall script inputs in the correct order"
|
||||||
|
exit 1
|
||||||
|
elif [ -z "$category" ]; then
|
||||||
|
category=""
|
||||||
|
echo "No category is being set"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$fee" ]; then
|
||||||
|
if [[ "$fee" == "1" || "$fee" == ".01" ]]; then
|
||||||
|
fee="1000000"
|
||||||
|
elif [ -z "$fee" ]; then
|
||||||
|
fee=""
|
||||||
|
else
|
||||||
|
echo "Error: Invalid fee value. Expected '1', '.01' or no input."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
final_fee="${fee}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# check that preview is true/false
|
||||||
|
if [[ -n "$preview" && ! ( "$preview" == "true" || "$preview" == "false" ) ]]; then
|
||||||
|
echo "Error: Invalid preview value. Expected 'true' or 'false'. Please retry with boolean as preview entry."
|
||||||
|
exit 1
|
||||||
|
elif [ -z "$preview" ]; then
|
||||||
|
preview=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build the API URL
|
||||||
|
api_url="http://${host}:${port}/arbitrary/${service}/${name}/${identifier}${type_component}"
|
||||||
|
api_url+="?title=${title}&description=${description}&tags=${tags_component}&category=${category}&fee=${final_fee}&preview=${preview}"
|
||||||
|
|
||||||
|
|
||||||
echo "Creating transaction - this can take a while..."
|
echo "Creating transaction - this can take a while..."
|
||||||
tx_data=$(curl --silent --insecure -X ${method} "http://${host}:${port}/arbitrary/${service}/${name}/${identifier}${type_component}" -H "X-API-KEY: ${apikey}" -d "${data}")
|
tx_data=$(curl --silent --insecure -X ${method} "${api_url}" -H "accept: text/plain" -H "X-API-KEY: ${apikey}" -H "Content-Type: text/plain" -d "${data}")
|
||||||
|
|
||||||
if [[ "${tx_data}" == *"error"* || "${tx_data}" == *"ERROR"* ]]; then
|
if [[ "${tx_data}" == *"error"* || "${tx_data}" == *"ERROR"* ]]; then
|
||||||
echo "${tx_data}"; exit
|
echo "Error creating transaction: ${tx_data}"
|
||||||
|
exit 1
|
||||||
elif [ -z "${tx_data}" ]; then
|
elif [ -z "${tx_data}" ]; then
|
||||||
echo "Error: no transaction data returned"; exit
|
echo "Error: no transaction data returned"
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Computing nonce..."
|
echo "Computing nonce..."
|
||||||
computed_tx_data=$(curl --silent --insecure -X POST "http://${host}:${port}/arbitrary/compute" -H "Content-Type: application/json" -H "X-API-KEY: ${apikey}" -d "${tx_data}")
|
computed_tx_data=$(curl --silent --insecure -X POST "http://${host}:${port}/arbitrary/compute" -H "Content-Type: application/json" -H "X-API-KEY: ${apikey}" -d "${tx_data}")
|
||||||
|
|
||||||
if [[ "${computed_tx_data}" == *"error"* || "${computed_tx_data}" == *"ERROR"* ]]; then
|
if [[ "${computed_tx_data}" == *"error"* || "${computed_tx_data}" == *"ERROR"* ]]; then
|
||||||
echo "${computed_tx_data}"; exit
|
echo "Error computing nonce: ${computed_tx_data}"
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Signing..."
|
echo "Signing..."
|
||||||
signed_tx_data=$(curl --silent --insecure -X POST "http://${host}:${port}/transactions/sign" -H "Content-Type: application/json" -d "{\"privateKey\":\"${QORTAL_PRIVKEY}\",\"transactionBytes\":\"${computed_tx_data}\"}")
|
signed_tx_data=$(curl --silent --insecure -X POST "http://${host}:${port}/transactions/sign" -H "Content-Type: application/json" -d "{\"privateKey\":\"${QORTAL_PRIVKEY}\",\"transactionBytes\":\"${computed_tx_data}\"}")
|
||||||
|
|
||||||
if [[ "${signed_tx_data}" == *"error"* || "${signed_tx_data}" == *"ERROR"* ]]; then
|
if [[ "${signed_tx_data}" == *"error"* || "${signed_tx_data}" == *"ERROR"* ]]; then
|
||||||
echo "${signed_tx_data}"; exit
|
echo "Error signing transaction: ${signed_tx_data}"
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Broadcasting..."
|
echo "Broadcasting..."
|
||||||
success=$(curl --silent --insecure -X POST "http://${host}:${port}/transactions/process" -H "Content-Type: text/plain" -d "${signed_tx_data}")
|
success=$(curl --silent --insecure -X POST "http://${host}:${port}/transactions/process" -H "Content-Type: text/plain" -d "${signed_tx_data}")
|
||||||
|
|
||||||
if [[ "${success}" == "true" ]]; then
|
if [[ "${success}" == "true" ]]; then
|
||||||
echo "Transaction broadcast successfully"
|
echo "Transaction broadcast successfully"
|
||||||
else
|
else
|
||||||
@ -131,9 +218,10 @@ elif [[ "${method}" == "GET" ]]; then
|
|||||||
echo "Empty response from ${host}:${port}"
|
echo "Empty response from ${host}:${port}"
|
||||||
fi
|
fi
|
||||||
if [[ "${response}" == *"error"* || "${response}" == *"ERROR"* ]]; then
|
if [[ "${response}" == *"error"* || "${response}" == *"ERROR"* ]]; then
|
||||||
echo "${response}"; exit
|
echo "${response}"
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "${response}"
|
echo "${response}"
|
||||||
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ our %b58 = map { $b58[$_] => $_ } 0 .. 57;
|
|||||||
our %reverseb58 = reverse %b58;
|
our %reverseb58 = reverse %b58;
|
||||||
|
|
||||||
our $BASE_URL = $ENV{BASE_URL} || ($opt{t} ? 'http://localhost:62391' : 'http://localhost:12391');
|
our $BASE_URL = $ENV{BASE_URL} || ($opt{t} ? 'http://localhost:62391' : 'http://localhost:12391');
|
||||||
our $DEFAULT_FEE = 0.001;
|
our $DEFAULT_FEE = 0.01;
|
||||||
|
|
||||||
our %TRANSACTION_TYPES = (
|
our %TRANSACTION_TYPES = (
|
||||||
payment => {
|
payment => {
|
||||||
@ -92,7 +92,7 @@ our %TRANSACTION_TYPES = (
|
|||||||
},
|
},
|
||||||
remove_group_admin => {
|
remove_group_admin => {
|
||||||
url => 'groups/removeadmin',
|
url => 'groups/removeadmin',
|
||||||
required => [qw(groupId txGroupId member)],
|
required => [qw(groupId txGroupId admin)],
|
||||||
key_name => 'ownerPublicKey',
|
key_name => 'ownerPublicKey',
|
||||||
},
|
},
|
||||||
group_approval => {
|
group_approval => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user