mirror of
https://github.com/Qortal/qortal.git
synced 2025-07-31 06:01:22 +00:00
Compare commits
43 Commits
add-create
...
v4.3.1
Author | SHA1 | Date | |
---|---|---|---|
|
033b6adb72 | ||
|
53c3f7899a | ||
|
f7f2d70b77 | ||
|
5cbd4490cc | ||
|
7103f41e36 | ||
|
2d599ec3c5 | ||
|
e40d884c81 | ||
|
7cbeb00c1a | ||
|
2af8199d9c | ||
|
404c5d0300 | ||
|
db7b17e52e | ||
|
6d202b2b48 | ||
|
499e2ac3f4 | ||
|
bf270a63ff | ||
|
9454031b48 | ||
|
cae3fdcb06 | ||
|
1fbb1659a3 | ||
|
9959985a13 | ||
|
15c073edfe | ||
|
d453e80c6b | ||
|
4fb799ba38 | ||
|
29e56158ae | ||
|
f74c9672f6 | ||
|
a7ca306d1b | ||
|
1a9087984a | ||
|
94e9f86245 | ||
|
bd05578035 | ||
|
c0ed4022a5 | ||
|
12dbff79c9 | ||
|
6be3897fdb | ||
|
43921e6ab8 | ||
|
b92c7cc866 | ||
|
053d56d01d | ||
|
eb6a834fd9 | ||
|
a08f10ece3 | ||
|
ad51073f25 | ||
|
5983e6ccc9 | ||
|
760788e82b | ||
|
d39131ffa9 | ||
|
26ac7e5be5 | ||
|
086a9afa0e | ||
|
ac433b1527 | ||
|
0693e26cda |
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
|
||||
# On Windows, uncomment next line to set dirname:
|
||||
# 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.rolling.ref = FILE
|
||||
@@ -59,11 +59,14 @@ appender.console.filter.threshold.level = error
|
||||
|
||||
appender.rolling.type = RollingFile
|
||||
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.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.size = 4MB
|
||||
appender.rolling.policy.size = 10MB
|
||||
appender.rolling.strategy.type = DefaultRolloverStrategy
|
||||
appender.rolling.strategy.max = 7
|
||||
# Set the immediate flush to true (default)
|
||||
# appender.rolling.immediateFlush = true
|
||||
# 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>
|
||||
<artifactId>AT</artifactId>
|
||||
<versioning>
|
||||
<release>1.4.0</release>
|
||||
<release>1.4.1</release>
|
||||
<versions>
|
||||
<version>1.3.4</version>
|
||||
<version>1.3.5</version>
|
||||
@@ -11,7 +11,8 @@
|
||||
<version>1.3.7</version>
|
||||
<version>1.3.8</version>
|
||||
<version>1.4.0</version>
|
||||
<version>1.4.1</version>
|
||||
</versions>
|
||||
<lastUpdated>20221105114346</lastUpdated>
|
||||
<lastUpdated>20230821074325</lastUpdated>
|
||||
</versioning>
|
||||
</metadata>
|
||||
|
@@ -1,7 +1,7 @@
|
||||
rootLogger.level = info
|
||||
# On Windows, uncomment next line to set dirname:
|
||||
# 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.rolling.ref = FILE
|
||||
@@ -59,11 +59,14 @@ appender.console.filter.threshold.level = error
|
||||
|
||||
appender.rolling.type = RollingFile
|
||||
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.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.size = 4MB
|
||||
appender.rolling.policy.size = 10MB
|
||||
appender.rolling.strategy.type = DefaultRolloverStrategy
|
||||
appender.rolling.strategy.max = 7
|
||||
# Set the immediate flush to true (default)
|
||||
# appender.rolling.immediateFlush = true
|
||||
# Set the append to true (default), should not overwrite
|
||||
|
132
pom.xml
132
pom.xml
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.qortal</groupId>
|
||||
<artifactId>qortal</artifactId>
|
||||
<version>4.2.4</version>
|
||||
<version>4.3.1</version>
|
||||
<packaging>jar</packaging>
|
||||
<properties>
|
||||
<skipTests>true</skipTests>
|
||||
@@ -11,32 +11,52 @@
|
||||
<bitcoinj.version>0.15.10</bitcoinj.version>
|
||||
<bouncycastle.version>1.69</bouncycastle.version>
|
||||
<build.timestamp>${maven.build.timestamp}</build.timestamp>
|
||||
<ciyam-at.version>1.4.0</ciyam-at.version>
|
||||
<commons-net.version>3.6</commons-net.version>
|
||||
<commons-text.version>1.8</commons-text.version>
|
||||
<commons-io.version>2.6</commons-io.version>
|
||||
<commons-compress.version>1.21</commons-compress.version>
|
||||
<commons-lang3.version>3.12.0</commons-lang3.version>
|
||||
<ciyam-at.version>1.4.1</ciyam-at.version>
|
||||
<commons-net.version>3.8.0</commons-net.version>
|
||||
<commons-text.version>1.10.0</commons-text.version>
|
||||
<commons-io.version>2.11.0</commons-io.version>
|
||||
<commons-compress.version>1.24.0</commons-compress.version>
|
||||
<commons-lang3.version>3.13.0</commons-lang3.version>
|
||||
<xz.version>1.9</xz.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>
|
||||
<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>
|
||||
<jersey.version>2.29.1</jersey.version>
|
||||
<jetty.version>9.4.29.v20200521</jetty.version>
|
||||
<log4j.version>2.17.1</log4j.version>
|
||||
<jaxb-runtime.version>2.3.3</jaxb-runtime.version>
|
||||
<jersey.version>2.40</jersey.version>
|
||||
<jetty.version>9.4.53.v20231009</jetty.version>
|
||||
<log4j.version>2.20.0</log4j.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<slf4j.version>1.7.12</slf4j.version>
|
||||
<swagger-api.version>2.0.9</swagger-api.version>
|
||||
<swagger-ui.version>3.23.8</swagger-ui.version>
|
||||
<slf4j.version>1.7.36</slf4j.version>
|
||||
<swagger-api.version>2.0.10</swagger-api.version>
|
||||
<swagger-ui.version>3.52.5</swagger-ui.version>
|
||||
<package-info-maven-plugin.version>1.1.0</package-info-maven-plugin.version>
|
||||
<jsoup.version>1.13.1</jsoup.version>
|
||||
<java-diff-utils.version>4.10</java-diff-utils.version>
|
||||
<grpc.version>1.45.1</grpc.version>
|
||||
<protobuf.version>3.19.4</protobuf.version>
|
||||
<jsoup.version>1.16.1</jsoup.version>
|
||||
<java-diff-utils.version>4.12</java-diff-utils.version>
|
||||
<grpc.version>1.58.0</grpc.version>
|
||||
<protobuf.version>3.24.4</protobuf.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>
|
||||
<build>
|
||||
<sourceDirectory>src/main/java</sourceDirectory>
|
||||
@@ -51,14 +71,14 @@
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>versions-maven-plugin</artifactId>
|
||||
<version>2.5</version>
|
||||
<version>${versions-maven-plugin.version}</version>
|
||||
<configuration>
|
||||
<generateBackupPoms>false</generateBackupPoms>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.0</version>
|
||||
<version>${maven-compiler-plugin.version}</version>
|
||||
<configuration>
|
||||
<release>11</release>
|
||||
</configuration>
|
||||
@@ -89,7 +109,7 @@
|
||||
<plugin>
|
||||
<groupId>pl.project13.maven</groupId>
|
||||
<artifactId>git-commit-id-plugin</artifactId>
|
||||
<version>4.0.0</version>
|
||||
<version>${git-commit-id-plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>get-the-git-infos</id>
|
||||
@@ -121,7 +141,7 @@
|
||||
<plugin>
|
||||
<groupId>com.google.code.maven-replacer-plugin</groupId>
|
||||
<artifactId>replacer</artifactId>
|
||||
<version>1.5.3</version>
|
||||
<version>${replacer.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>replace-swagger-ui</id>
|
||||
@@ -164,11 +184,13 @@
|
||||
<configuration>
|
||||
<file>${project.build.outputDirectory}/git.properties</file>
|
||||
<regex>true</regex>
|
||||
<regexFlags><regexFlag>MULTILINE</regexFlag></regexFlags>
|
||||
<regexFlags>
|
||||
<regexFlag>MULTILINE</regexFlag>
|
||||
</regexFlags>
|
||||
<replacements>
|
||||
<replacement>
|
||||
<token>^(#.*$[\n\r]*)</token>
|
||||
<value></value>
|
||||
<value/>
|
||||
</replacement>
|
||||
</replacements>
|
||||
</configuration>
|
||||
@@ -178,7 +200,10 @@
|
||||
<!-- add swagger-ui as resource to output package -->
|
||||
<plugin>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<version>${maven-resources-plugin.version}</version>
|
||||
<configuration>
|
||||
<propertiesEncoding>ISO-8859-1</propertiesEncoding>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>copy-resources</id>
|
||||
@@ -232,7 +257,7 @@
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>build-helper-maven-plugin</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<version>${build-helper-maven-plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>generate-sources</phase>
|
||||
@@ -250,7 +275,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<version>${maven-jar-plugin.version}</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
@@ -268,13 +293,12 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>2.4.3</version>
|
||||
<version>${maven-shade-plugin.version}</version>
|
||||
<configuration>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
<artifactSet>
|
||||
<excludes>
|
||||
<!-- Don't include original swagger-UI as we're including our own
|
||||
modified version -->
|
||||
<!-- Don't include original swagger-UI as we're including our own modified version -->
|
||||
<exclude>org.webjars:swagger-ui</exclude>
|
||||
<!-- Don't include JUnit as it's for testing only! -->
|
||||
<exclude>junit:junit</exclude>
|
||||
@@ -318,7 +342,7 @@
|
||||
<plugin>
|
||||
<groupId>io.github.zlika</groupId>
|
||||
<artifactId>reproducible-build-maven-plugin</artifactId>
|
||||
<version>0.11</version>
|
||||
<version>${reproducible-build-maven-plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
@@ -335,7 +359,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.22.2</version>
|
||||
<version>${maven-surefire-plugin.version}</version>
|
||||
<configuration>
|
||||
<skipTests>${skipTests}</skipTests>
|
||||
</configuration>
|
||||
@@ -347,7 +371,7 @@
|
||||
<plugin>
|
||||
<groupId>org.eclipse.m2e</groupId>
|
||||
<artifactId>lifecycle-mapping</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<version>${lifecycle-mapping.version}</version>
|
||||
<configuration>
|
||||
<lifecycleMappingMetadata>
|
||||
<pluginExecutions>
|
||||
@@ -360,14 +384,14 @@
|
||||
maven-dependency-plugin
|
||||
</artifactId>
|
||||
<versionRange>
|
||||
[2.8,)
|
||||
[3.6.0,)
|
||||
</versionRange>
|
||||
<goals>
|
||||
<goal>unpack</goal>
|
||||
</goals>
|
||||
</pluginExecutionFilter>
|
||||
<action>
|
||||
<execute></execute>
|
||||
<execute/>
|
||||
</action>
|
||||
</pluginExecution>
|
||||
<pluginExecution>
|
||||
@@ -386,7 +410,7 @@
|
||||
</goals>
|
||||
</pluginExecutionFilter>
|
||||
<action>
|
||||
<execute></execute>
|
||||
<execute/>
|
||||
</action>
|
||||
</pluginExecution>
|
||||
</pluginExecutions>
|
||||
@@ -413,15 +437,17 @@
|
||||
<dependency>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>build-helper-maven-plugin</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<scope>provided</scope><!-- needed for build, not for runtime -->
|
||||
<version>${build-helper-maven-plugin.version}</version>
|
||||
<scope>provided</scope>
|
||||
<!-- needed for build, not for runtime -->
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/com.github.bohnman/package-info-maven-plugin -->
|
||||
<dependency>
|
||||
<groupId>com.github.bohnman</groupId>
|
||||
<artifactId>package-info-maven-plugin</artifactId>
|
||||
<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>
|
||||
<!-- HSQLDB for repository -->
|
||||
<dependency>
|
||||
@@ -457,12 +483,12 @@
|
||||
<dependency>
|
||||
<groupId>com.googlecode.json-simple</groupId>
|
||||
<artifactId>json-simple</artifactId>
|
||||
<version>1.1.1</version>
|
||||
<version>${json-simple.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>20210307</version>
|
||||
<version>${json.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
@@ -493,7 +519,7 @@
|
||||
<dependency>
|
||||
<groupId>io.druid</groupId>
|
||||
<artifactId>extendedset</artifactId>
|
||||
<version>0.12.3</version>
|
||||
<version>${extendedset.version}</version>
|
||||
<exclusions>
|
||||
<!-- exclude old versions of jackson-annotations / jackson-core -->
|
||||
<exclusion>
|
||||
@@ -564,12 +590,12 @@
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>4.0.1</version>
|
||||
<version>${javax.servlet-api.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.mail</groupId>
|
||||
<artifactId>mail</artifactId>
|
||||
<version>1.5.0-b01</version>
|
||||
<version>${mail.version}</version>
|
||||
</dependency>
|
||||
<!-- Unicode homoglyph utilities -->
|
||||
<dependency>
|
||||
@@ -638,7 +664,8 @@
|
||||
<artifactId>jersey-hk2</artifactId>
|
||||
<version>${jersey.version}</version>
|
||||
<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>
|
||||
<artifactId>javax.inject</artifactId>
|
||||
</exclusion>
|
||||
@@ -665,7 +692,8 @@
|
||||
<artifactId>swagger-jaxrs2-servlet-initializer</artifactId>
|
||||
<version>${swagger-api.version}</version>
|
||||
<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>
|
||||
<artifactId>swagger-integration</artifactId>
|
||||
</exclusion>
|
||||
@@ -681,12 +709,12 @@
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<version>5.3.1</version>
|
||||
<version>${junit-jupiter-engine.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
<artifactId>hamcrest-library</artifactId>
|
||||
<version>1.3</version>
|
||||
<version>${hamcrest-library.version}</version>
|
||||
</dependency>
|
||||
-->
|
||||
<!-- BouncyCastle for crypto, including TLS secure networking -->
|
||||
@@ -735,5 +763,11 @@
|
||||
<artifactId>simplemagic</artifactId>
|
||||
<version>${simplemagic.version}</version>
|
||||
</dependency>
|
||||
<!-- JAXB runtime for WADL support -->
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jaxb</groupId>
|
||||
<artifactId>jaxb-runtime</artifactId>
|
||||
<version>${jaxb-runtime.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
@@ -16,11 +16,6 @@ import java.net.InetSocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.file.Files;
|
||||
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.concurrent.TimeUnit;
|
||||
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.appender.RollingFileAppender;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.qortal.account.Account;
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.api.*;
|
||||
@@ -269,7 +263,7 @@ public class AdminResource {
|
||||
@GET
|
||||
@Path("/summary")
|
||||
@Operation(
|
||||
summary = "Summary of activity since midnight, UTC",
|
||||
summary = "Summary of activity past 24 hours",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
content = @Content(schema = @Schema(implementation = ActivitySummary.class))
|
||||
@@ -282,23 +276,21 @@ public class AdminResource {
|
||||
Security.checkApiCallAllowed(request);
|
||||
|
||||
ActivitySummary summary = new ActivitySummary();
|
||||
|
||||
LocalDate date = LocalDate.now();
|
||||
LocalTime time = LocalTime.of(0, 0);
|
||||
ZoneOffset offset = ZoneOffset.UTC;
|
||||
long start = OffsetDateTime.of(date, time, offset).toInstant().toEpochMilli();
|
||||
|
||||
long now = NTP.getTime();
|
||||
long oneday = now - 24 * 60 * 60 * 1000L;
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
int startHeight = repository.getBlockRepository().getHeightFromTimestamp(start);
|
||||
int startHeight = repository.getBlockRepository().getHeightFromTimestamp(oneday);
|
||||
int endHeight = repository.getBlockRepository().getBlockchainHeight();
|
||||
|
||||
summary.setBlockCount(endHeight - startHeight);
|
||||
|
||||
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;
|
||||
} catch (DataException e) {
|
||||
|
@@ -712,30 +712,16 @@ public class TradeBot implements Listener {
|
||||
}
|
||||
|
||||
try {
|
||||
List<byte[]> signatures = repository.getTransactionRepository().getSignaturesMatchingCriteria(null, null, null, Arrays.asList(Transaction.TransactionType.MESSAGE), null, null, crossChainTradeData.qortalCreatorTradeAddress, TransactionsResource.ConfirmationStatus.CONFIRMED, null, null, null);
|
||||
if (signatures.size() < getMaxTradeOfferAttempts) {
|
||||
// Less than 3 (or user-specified number of) MESSAGE transactions relate to this trade, so assume it is ok
|
||||
validTrades.put(crossChainTradeData.qortalAtAddress, now);
|
||||
continue;
|
||||
}
|
||||
List<TransactionData> transactions = repository.getTransactionRepository().getUnconfirmedTransactions(Arrays.asList(Transaction.TransactionType.MESSAGE), null, null, null, null);
|
||||
|
||||
List<TransactionData> transactions = new ArrayList<>(signatures.size());
|
||||
for (byte[] signature : signatures) {
|
||||
transactions.add(repository.getTransactionRepository().fromSignature(signature));
|
||||
}
|
||||
transactions.sort(Transaction.getDataComparator());
|
||||
|
||||
// Get timestamp of the first MESSAGE transaction
|
||||
long firstMessageTimestamp = transactions.get(0).getTimestamp();
|
||||
|
||||
// Treat as failed if first buy attempt was more than 60 mins ago (as it's still in the OFFERING state)
|
||||
boolean isFailed = (now - firstMessageTimestamp > 60*60*1000L);
|
||||
if (isFailed) {
|
||||
failedTrades.put(crossChainTradeData.qortalAtAddress, now);
|
||||
updatedCrossChainTrades.remove(crossChainTradeData);
|
||||
}
|
||||
else {
|
||||
validTrades.put(crossChainTradeData.qortalAtAddress, now);
|
||||
for (TransactionData transactionData : transactions) {
|
||||
// Treat as failed if buy attempt was more than 60 mins ago (as it's still in the OFFERING state)
|
||||
if (transactionData.getRecipient().equals(crossChainTradeData.qortalCreatorTradeAddress) && now - transactionData.getTimestamp() > 60*60*1000L) {
|
||||
failedTrades.put(crossChainTradeData.qortalAtAddress, now);
|
||||
updatedCrossChainTrades.remove(crossChainTradeData);
|
||||
} else {
|
||||
validTrades.put(crossChainTradeData.qortalAtAddress, now);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (DataException e) {
|
||||
|
@@ -44,89 +44,78 @@ public class Bitcoin extends Bitcoiny {
|
||||
@Override
|
||||
public Collection<ElectrumX.Server> getServers() {
|
||||
return Arrays.asList(
|
||||
// Servers chosen on NO BASIS WHATSOEVER from various sources!
|
||||
// Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=btc
|
||||
//CLOSED new Server("bitcoin.grey.pw", Server.ConnectionType.SSL, 50002),
|
||||
//CLOSED new Server("btc.litepay.ch", Server.ConnectionType.SSL, 50002),
|
||||
//CLOSED new Server("electrum.pabu.io", Server.ConnectionType.SSL, 50002),
|
||||
//CLOSED new Server("electrumx.dev", Server.ConnectionType.SSL, 50002),
|
||||
//CLOSED new Server("electrumx.hodlwallet.com", Server.ConnectionType.SSL, 50002),
|
||||
//CLOSED new Server("gd42.org", Server.ConnectionType.SSL, 50002),
|
||||
//CLOSED new Server("korea.electrum-server.com", Server.ConnectionType.SSL, 50002),
|
||||
//CLOSED new Server("prospero.bitsrc.net", Server.ConnectionType.SSL, 50002),
|
||||
//1.15.0 new Server("alviss.coinjoined.com", Server.ConnectionType.SSL, 50002),
|
||||
//1.15.0 new Server("electrum.acinq.co", Server.ConnectionType.SSL, 50002),
|
||||
//1.14.0 new Server("electrum.coinext.com.br", Server.ConnectionType.SSL, 50002),
|
||||
//F1.7.0 new Server("btc.lastingcoin.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("104.248.139.211", Server.ConnectionType.SSL, 50002),
|
||||
new Server("128.0.190.26", Server.ConnectionType.SSL, 50002),
|
||||
new Server("142.93.6.38", Server.ConnectionType.SSL, 50002),
|
||||
new Server("157.245.172.236", Server.ConnectionType.SSL, 50002),
|
||||
new Server("167.172.226.175", Server.ConnectionType.SSL, 50002),
|
||||
new Server("167.172.42.31", Server.ConnectionType.SSL, 50002),
|
||||
new Server("178.62.80.20", Server.ConnectionType.SSL, 50002),
|
||||
new Server("185.64.116.15", Server.ConnectionType.SSL, 50002),
|
||||
new Server("188.165.206.215", Server.ConnectionType.SSL, 50002),
|
||||
new Server("188.165.211.112", Server.ConnectionType.SSL, 50002),
|
||||
new Server("2azzarita.hopto.org", Server.ConnectionType.SSL, 50002),
|
||||
new Server("2electrumx.hopto.me", Server.ConnectionType.SSL, 56022),
|
||||
new Server("2ex.digitaleveryware.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("65.39.140.37", Server.ConnectionType.SSL, 50002),
|
||||
new Server("68.183.188.105", Server.ConnectionType.SSL, 50002),
|
||||
new Server("71.73.14.254", Server.ConnectionType.SSL, 50002),
|
||||
new Server("94.23.247.135", Server.ConnectionType.SSL, 50002),
|
||||
new Server("assuredly.not.fyi", Server.ConnectionType.SSL, 50002),
|
||||
new Server("ax101.blockeng.ch", Server.ConnectionType.SSL, 50002),
|
||||
new Server("ax102.blockeng.ch", Server.ConnectionType.SSL, 50002),
|
||||
new Server("b.1209k.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("b6.1209k.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("bitcoin.dermichi.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("bitcoin.lu.ke", Server.ConnectionType.SSL, 50002),
|
||||
new Server("bitcoin.lukechilds.co", Server.ConnectionType.SSL, 50002),
|
||||
new Server("blkhub.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("btc.electroncash.dk", Server.ConnectionType.SSL, 60002),
|
||||
new Server("btc.ocf.sh", Server.ConnectionType.SSL, 50002),
|
||||
new Server("btce.iiiiiii.biz", Server.ConnectionType.SSL, 50002),
|
||||
new Server("caleb.vegas", Server.ConnectionType.SSL, 50002),
|
||||
new Server("eai.coincited.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum.bhoovd.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum.bitaroo.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum.bitcoinlizard.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum.blockstream.info", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum.emzy.de", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum.exan.tech", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum.kendigisland.xyz", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum.mmitech.info", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum.petrkr.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum.stippy.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum.thomasfischbach.de", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum0.snel.it", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum1.cipig.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum2.cipig.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum3.cipig.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrumx.alexridevski.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrumx-core.1209k.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("elx.bitske.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("ex03.axalgo.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("ex05.axalgo.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("ex07.axalgo.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("fortress.qtornado.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("fulcrum.grey.pw", Server.ConnectionType.SSL, 50002),
|
||||
new Server("fulcrum.sethforprivacy.com", Server.ConnectionType.SSL, 51002),
|
||||
new Server("guichet.centure.cc", Server.ConnectionType.SSL, 50002),
|
||||
new Server("hodlers.beer", 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));
|
||||
// Servers chosen on NO BASIS WHATSOEVER from various sources!
|
||||
// Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=btc
|
||||
new Server("104.248.139.211", Server.ConnectionType.SSL, 50002),
|
||||
new Server("128.0.190.26", Server.ConnectionType.SSL, 50002),
|
||||
new Server("142.93.6.38", Server.ConnectionType.SSL, 50002),
|
||||
new Server("157.245.172.236", Server.ConnectionType.SSL, 50002),
|
||||
new Server("167.172.226.175", Server.ConnectionType.SSL, 50002),
|
||||
new Server("167.172.42.31", Server.ConnectionType.SSL, 50002),
|
||||
new Server("178.62.80.20", Server.ConnectionType.SSL, 50002),
|
||||
new Server("185.64.116.15", Server.ConnectionType.SSL, 50002),
|
||||
new Server("188.165.206.215", Server.ConnectionType.SSL, 50002),
|
||||
new Server("188.165.211.112", Server.ConnectionType.SSL, 50002),
|
||||
new Server("2azzarita.hopto.org", Server.ConnectionType.SSL, 50002),
|
||||
new Server("2electrumx.hopto.me", Server.ConnectionType.SSL, 56022),
|
||||
new Server("2ex.digitaleveryware.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("65.39.140.37", Server.ConnectionType.SSL, 50002),
|
||||
new Server("68.183.188.105", Server.ConnectionType.SSL, 50002),
|
||||
new Server("71.73.14.254", Server.ConnectionType.SSL, 50002),
|
||||
new Server("94.23.247.135", Server.ConnectionType.SSL, 50002),
|
||||
new Server("assuredly.not.fyi", Server.ConnectionType.SSL, 50002),
|
||||
new Server("ax101.blockeng.ch", Server.ConnectionType.SSL, 50002),
|
||||
new Server("ax102.blockeng.ch", Server.ConnectionType.SSL, 50002),
|
||||
new Server("b.1209k.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("b6.1209k.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("bitcoin.dermichi.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("bitcoin.lu.ke", Server.ConnectionType.SSL, 50002),
|
||||
new Server("bitcoin.lukechilds.co", Server.ConnectionType.SSL, 50002),
|
||||
new Server("blkhub.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("btc.electroncash.dk", Server.ConnectionType.SSL, 60002),
|
||||
new Server("btc.ocf.sh", Server.ConnectionType.SSL, 50002),
|
||||
new Server("btce.iiiiiii.biz", Server.ConnectionType.SSL, 50002),
|
||||
new Server("caleb.vegas", Server.ConnectionType.SSL, 50002),
|
||||
new Server("eai.coincited.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum.bhoovd.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum.bitaroo.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum.bitcoinlizard.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum.blockstream.info", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum.emzy.de", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum.exan.tech", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum.kendigisland.xyz", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum.mmitech.info", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum.petrkr.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum.stippy.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum.thomasfischbach.de", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum0.snel.it", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum1.cipig.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum2.cipig.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum3.cipig.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrumx.alexridevski.net", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrumx-core.1209k.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("elx.bitske.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("ex03.axalgo.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("ex05.axalgo.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("ex07.axalgo.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("fortress.qtornado.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("fulcrum.grey.pw", Server.ConnectionType.SSL, 50002),
|
||||
new Server("fulcrum.sethforprivacy.com", Server.ConnectionType.SSL, 51002),
|
||||
new Server("guichet.centure.cc", Server.ConnectionType.SSL, 50002),
|
||||
new Server("hodlers.beer", 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
|
||||
@@ -152,12 +141,13 @@ public class Bitcoin extends Bitcoiny {
|
||||
@Override
|
||||
public Collection<ElectrumX.Server> getServers() {
|
||||
return Arrays.asList(
|
||||
new Server("tn.not.fyi", Server.ConnectionType.SSL, 55002),
|
||||
new Server("electrumx-test.1209k.com", Server.ConnectionType.SSL, 50002),
|
||||
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.SSL, 51002),
|
||||
new Server("testnet.hsmiths.com", Server.ConnectionType.SSL, 53012));
|
||||
new Server("tn.not.fyi", Server.ConnectionType.SSL, 55002),
|
||||
new Server("electrumx-test.1209k.com", Server.ConnectionType.SSL, 50002),
|
||||
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.SSL, 51002),
|
||||
new Server("testnet.hsmiths.com", Server.ConnectionType.SSL, 53012)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -179,8 +169,9 @@ public class Bitcoin extends Bitcoiny {
|
||||
@Override
|
||||
public Collection<ElectrumX.Server> getServers() {
|
||||
return Arrays.asList(
|
||||
new Server("localhost", Server.ConnectionType.TCP, 50001),
|
||||
new Server("localhost", Server.ConnectionType.SSL, 50002));
|
||||
new Server("localhost", Server.ConnectionType.TCP, 50001),
|
||||
new Server("localhost", Server.ConnectionType.SSL, 50002)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -43,14 +43,17 @@ public class Digibyte extends Bitcoiny {
|
||||
@Override
|
||||
public Collection<Server> getServers() {
|
||||
return Arrays.asList(
|
||||
// Servers chosen on NO BASIS WHATSOEVER from various sources!
|
||||
// Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=dgb
|
||||
new Server("electrum.qortal.link", Server.ConnectionType.SSL, 55002),
|
||||
new Server("electrum-dgb.qortal.online", ConnectionType.SSL, 50002),
|
||||
new Server("electrum1-dgb.qortal.online", ConnectionType.SSL, 50002),
|
||||
new Server("electrum1.cipig.net", ConnectionType.SSL, 20059),
|
||||
new Server("electrum2.cipig.net", ConnectionType.SSL, 20059),
|
||||
new Server("electrum3.cipig.net", ConnectionType.SSL, 20059));
|
||||
// Servers chosen on NO BASIS WHATSOEVER from various sources!
|
||||
// Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=dgb
|
||||
new Server("electrum.qortal.link", Server.ConnectionType.SSL, 55002),
|
||||
new Server("electrum1-dgb.qortal.online", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum2-dgb.qortal.online", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum3-dgb.qortal.online", Server.ConnectionType.SSL, 40002),
|
||||
new Server("electrum4-dgb.qortal.online", Server.ConnectionType.SSL, 40002),
|
||||
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
|
||||
@@ -94,8 +97,9 @@ public class Digibyte extends Bitcoiny {
|
||||
@Override
|
||||
public Collection<Server> getServers() {
|
||||
return Arrays.asList(
|
||||
new Server("localhost", ConnectionType.TCP, 50001),
|
||||
new Server("localhost", ConnectionType.SSL, 50002));
|
||||
new Server("localhost", Server.ConnectionType.TCP, 50001),
|
||||
new Server("localhost", Server.ConnectionType.SSL, 50002)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -4,7 +4,6 @@ import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.Context;
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
import org.libdohj.params.DogecoinMainNetParams;
|
||||
//import org.libdohj.params.DogecoinRegTestParams;
|
||||
import org.libdohj.params.DogecoinTestNet3Params;
|
||||
import org.qortal.crosschain.ElectrumX.Server;
|
||||
import org.qortal.crosschain.ElectrumX.Server.ConnectionType;
|
||||
@@ -44,14 +43,17 @@ public class Dogecoin extends Bitcoiny {
|
||||
@Override
|
||||
public Collection<Server> getServers() {
|
||||
return Arrays.asList(
|
||||
// Servers chosen on NO BASIS WHATSOEVER from various sources!
|
||||
// Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=doge
|
||||
new Server("electrum.qortal.link", Server.ConnectionType.SSL, 54002),
|
||||
new Server("electrum-doge.qortal.online", ConnectionType.SSL, 50002),
|
||||
new Server("electrum1-doge.qortal.online", ConnectionType.SSL, 50002),
|
||||
new Server("electrum1.cipig.net", ConnectionType.SSL, 20060),
|
||||
new Server("electrum2.cipig.net", ConnectionType.SSL, 20060),
|
||||
new Server("electrum3.cipig.net", ConnectionType.SSL, 20060));
|
||||
// Servers chosen on NO BASIS WHATSOEVER from various sources!
|
||||
// Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=doge
|
||||
new Server("electrum.qortal.link", Server.ConnectionType.SSL, 54002),
|
||||
new Server("electrum1-doge.qortal.online", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum2-doge.qortal.online", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum3-doge.qortal.online", Server.ConnectionType.SSL, 30002),
|
||||
new Server("electrum4-doge.qortal.online", Server.ConnectionType.SSL, 30002),
|
||||
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
|
||||
@@ -95,8 +97,9 @@ public class Dogecoin extends Bitcoiny {
|
||||
@Override
|
||||
public Collection<Server> getServers() {
|
||||
return Arrays.asList(
|
||||
new Server("localhost", ConnectionType.TCP, 50001),
|
||||
new Server("localhost", ConnectionType.SSL, 50002));
|
||||
new Server("localhost", Server.ConnectionType.TCP, 50001),
|
||||
new Server("localhost", Server.ConnectionType.SSL, 50002)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -43,22 +43,21 @@ public class Litecoin extends Bitcoiny {
|
||||
@Override
|
||||
public Collection<ElectrumX.Server> getServers() {
|
||||
return Arrays.asList(
|
||||
// Servers chosen on NO BASIS WHATSOEVER from various sources!
|
||||
// Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=ltc
|
||||
//CLOSED new Server("electrum-ltc.someguy123.net", Server.ConnectionType.SSL, 50002),
|
||||
//CLOSED new Server("ltc.litepay.ch", Server.ConnectionType.SSL, 50022),
|
||||
//BEHIND new Server("62.171.169.176", Server.ConnectionType.SSL, 50002),
|
||||
//PHISHY new Server("electrum-ltc.bysh.me", Server.ConnectionType.SSL, 50002),
|
||||
new Server("backup.electrum-ltc.org", Server.ConnectionType.SSL, 443),
|
||||
new Server("electrum.qortal.link", 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.qortal.online", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum1-ltc.qortal.online", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum1.cipig.net", Server.ConnectionType.SSL, 20063),
|
||||
new Server("electrum2.cipig.net", Server.ConnectionType.SSL, 20063),
|
||||
new Server("electrum3.cipig.net", Server.ConnectionType.SSL, 20063),
|
||||
new Server("ltc.rentonrisk.com", Server.ConnectionType.SSL, 50002));
|
||||
// Servers chosen on NO BASIS WHATSOEVER from various sources!
|
||||
// Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=ltc
|
||||
new Server("electrum.qortal.link", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum1-ltc.qortal.online", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum2-ltc.qortal.online", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum3-ltc.qortal.online", Server.ConnectionType.SSL, 20002),
|
||||
new Server("electrum4-ltc.qortal.online", Server.ConnectionType.SSL, 20002),
|
||||
new Server("backup.electrum-ltc.org", Server.ConnectionType.SSL, 443),
|
||||
new Server("electrum.ltc.xurious.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum-ltc.petrkr.net", Server.ConnectionType.SSL, 60002),
|
||||
new Server("electrum1.cipig.net", Server.ConnectionType.SSL, 20063),
|
||||
new Server("electrum2.cipig.net", Server.ConnectionType.SSL, 20063),
|
||||
new Server("electrum3.cipig.net", Server.ConnectionType.SSL, 20063),
|
||||
new Server("ltc.rentonrisk.com", Server.ConnectionType.SSL, 50002)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -81,10 +80,11 @@ public class Litecoin extends Bitcoiny {
|
||||
@Override
|
||||
public Collection<ElectrumX.Server> getServers() {
|
||||
return Arrays.asList(
|
||||
new Server("electrum-ltc.bysh.me", Server.ConnectionType.TCP, 51001),
|
||||
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.SSL, 51002));
|
||||
new Server("electrum-ltc.bysh.me", Server.ConnectionType.TCP, 51001),
|
||||
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.SSL, 51002)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -106,8 +106,9 @@ public class Litecoin extends Bitcoiny {
|
||||
@Override
|
||||
public Collection<ElectrumX.Server> getServers() {
|
||||
return Arrays.asList(
|
||||
new Server("localhost", Server.ConnectionType.TCP, 50001),
|
||||
new Server("localhost", Server.ConnectionType.SSL, 50002));
|
||||
new Server("localhost", Server.ConnectionType.TCP, 50001),
|
||||
new Server("localhost", Server.ConnectionType.SSL, 50002)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -56,11 +56,14 @@ public class PirateChain extends Bitcoiny {
|
||||
@Override
|
||||
public Collection<Server> getServers() {
|
||||
return Arrays.asList(
|
||||
// Servers chosen on NO BASIS WHATSOEVER from various sources!
|
||||
new Server("wallet-arrr1.qortal.online", ConnectionType.SSL, 443),
|
||||
new Server("wallet-arrr2.qortal.online", ConnectionType.SSL, 443),
|
||||
new Server("wallet-arrr3.qortal.online", ConnectionType.SSL, 443),
|
||||
new Server("lightd.pirate.black", ConnectionType.SSL, 443));
|
||||
// Servers chosen on NO BASIS WHATSOEVER from various sources!
|
||||
new Server("wallet-arrr1.qortal.online", Server.ConnectionType.SSL, 443),
|
||||
new Server("wallet-arrr2.qortal.online", Server.ConnectionType.SSL, 443),
|
||||
new Server("wallet-arrr3.qortal.online", Server.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
|
||||
@@ -104,8 +107,9 @@ public class PirateChain extends Bitcoiny {
|
||||
@Override
|
||||
public Collection<Server> getServers() {
|
||||
return Arrays.asList(
|
||||
new Server("localhost", ConnectionType.TCP, 9067),
|
||||
new Server("localhost", ConnectionType.SSL, 443));
|
||||
new Server("localhost", Server.ConnectionType.TCP, 9067),
|
||||
new Server("localhost", Server.ConnectionType.SSL, 443)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -43,19 +43,19 @@ public class Ravencoin extends Bitcoiny {
|
||||
@Override
|
||||
public Collection<Server> getServers() {
|
||||
return Arrays.asList(
|
||||
// Servers chosen on NO BASIS WHATSOEVER from various sources!
|
||||
// Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=rvn
|
||||
//CLOSED new Server("aethyn.com", ConnectionType.SSL, 50002),
|
||||
//CLOSED new Server("electrum2.rvn.rocks", ConnectionType.SSL, 50002),
|
||||
//BEHIND new Server("electrum3.rvn.rocks", ConnectionType.SSL, 50002),
|
||||
new Server("electrum.qortal.link", Server.ConnectionType.SSL, 56002),
|
||||
new Server("electrum-rvn.qortal.online", ConnectionType.SSL, 50002),
|
||||
new Server("electrum1-rvn.qortal.online", ConnectionType.SSL, 50002),
|
||||
new Server("electrum1.cipig.net", ConnectionType.SSL, 20051),
|
||||
new Server("electrum2.cipig.net", ConnectionType.SSL, 20051),
|
||||
new Server("electrum3.cipig.net", ConnectionType.SSL, 20051),
|
||||
new Server("rvn-dashboard.com", ConnectionType.SSL, 50002),
|
||||
new Server("rvn4lyfe.com", ConnectionType.SSL, 50002));
|
||||
// Servers chosen on NO BASIS WHATSOEVER from various sources!
|
||||
// Status verified at https://1209k.com/bitcoin-eye/ele.php?chain=rvn
|
||||
new Server("electrum.qortal.link", Server.ConnectionType.SSL, 56002),
|
||||
new Server("electrum1-rvn.qortal.online", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum2-rvn.qortal.online", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum3-rvn.qortal.online", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum4-rvn.qortal.online", Server.ConnectionType.SSL, 50002),
|
||||
new Server("electrum1.cipig.net", Server.ConnectionType.SSL, 20051),
|
||||
new Server("electrum2.cipig.net", Server.ConnectionType.SSL, 20051),
|
||||
new Server("electrum3.cipig.net", Server.ConnectionType.SSL, 20051),
|
||||
new Server("rvn-dashboard.com", Server.ConnectionType.SSL, 50002),
|
||||
new Server("rvn4lyfe.com", Server.ConnectionType.SSL, 50002)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -99,8 +99,9 @@ public class Ravencoin extends Bitcoiny {
|
||||
@Override
|
||||
public Collection<Server> getServers() {
|
||||
return Arrays.asList(
|
||||
new Server("localhost", ConnectionType.TCP, 50001),
|
||||
new Server("localhost", ConnectionType.SSL, 50002));
|
||||
new Server("localhost", Server.ConnectionType.TCP, 50001),
|
||||
new Server("localhost", Server.ConnectionType.SSL, 50002)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -75,6 +75,9 @@ public abstract class TransactionData {
|
||||
@Schema(description = "groupID for this transaction")
|
||||
protected int txGroupId;
|
||||
|
||||
@Schema(description = "recipient for this transaction")
|
||||
protected String recipient;
|
||||
|
||||
// Not always present
|
||||
@Schema(accessMode = AccessMode.READ_ONLY, hidden = true, description = "height of block containing transaction")
|
||||
protected Integer blockHeight;
|
||||
@@ -105,7 +108,7 @@ public abstract class TransactionData {
|
||||
/** Constructor for use by transaction subclasses. */
|
||||
protected TransactionData(TransactionType type, BaseTransactionData baseTransactionData) {
|
||||
this.type = type;
|
||||
|
||||
this.recipient = baseTransactionData.recipient;
|
||||
this.timestamp = baseTransactionData.timestamp;
|
||||
this.txGroupId = baseTransactionData.txGroupId;
|
||||
this.reference = baseTransactionData.reference;
|
||||
@@ -136,6 +139,10 @@ public abstract class TransactionData {
|
||||
return this.txGroupId;
|
||||
}
|
||||
|
||||
public String getRecipient() {
|
||||
return this.recipient;
|
||||
}
|
||||
|
||||
public void setTxGroupId(int txGroupId) {
|
||||
this.txGroupId = txGroupId;
|
||||
}
|
||||
@@ -250,5 +257,4 @@ public abstract class TransactionData {
|
||||
|
||||
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.controller.Controller;
|
||||
import org.qortal.controller.arbitrary.ArbitraryDataFileListManager;
|
||||
import org.qortal.controller.arbitrary.ArbitraryDataManager;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.data.block.BlockData;
|
||||
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
|
||||
|
||||
|
||||
/**
|
||||
* 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 String bindAddress = null;
|
||||
@@ -240,6 +255,16 @@ public class 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() {
|
||||
return SingletonContainer.INSTANCE;
|
||||
}
|
||||
@@ -952,6 +977,37 @@ public class Network {
|
||||
|
||||
// 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
|
||||
switch (message.getType()) {
|
||||
case GET_PEERS:
|
||||
@@ -979,6 +1035,15 @@ public class Network {
|
||||
Controller.getInstance().onNetworkMessage(peer, message);
|
||||
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) {
|
||||
|
@@ -1014,5 +1014,4 @@ public class HSQLDBDatabaseUpdates {
|
||||
LOGGER.info(() -> String.format("HSQLDB repository updated to version %d", databaseVersion + 1));
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -29,6 +29,7 @@ import org.qortal.crosschain.Dogecoin.DogecoinNet;
|
||||
import org.qortal.crosschain.Digibyte.DigibyteNet;
|
||||
import org.qortal.crosschain.Ravencoin.RavencoinNet;
|
||||
import org.qortal.crosschain.PirateChain.PirateChainNet;
|
||||
import org.qortal.network.message.MessageType;
|
||||
import org.qortal.utils.EnumUtils;
|
||||
|
||||
// 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 TESTNET_GATEWAY_PORT = 8080;
|
||||
|
||||
private static final int MAINNET_DEV_PROXY_PORT = 12393;
|
||||
private static final int TESTNET_DEV_PROXY_PORT = 62393;
|
||||
private static final int MAINNET_DEV_PROXY_PORT = 12393;
|
||||
private static final int TESTNET_DEV_PROXY_PORT = 62393;
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(Settings.class);
|
||||
private static final String SETTINGS_FILENAME = "settings.json";
|
||||
@@ -110,10 +111,9 @@ public class Settings {
|
||||
private boolean gatewayLoggingEnabled = false;
|
||||
private boolean gatewayLoopbackEnabled = false;
|
||||
|
||||
// Developer Proxy
|
||||
// Developer Proxy
|
||||
private Integer devProxyPort;
|
||||
private boolean devProxyLoggingEnabled = false;
|
||||
|
||||
private boolean devProxyLoggingEnabled = false;
|
||||
|
||||
// Specific to this node
|
||||
private boolean wipeUnconfirmedOnStart = false;
|
||||
@@ -186,7 +186,6 @@ public class Settings {
|
||||
* This has a significant effect on execution time. */
|
||||
private int blockPruneBatchSize = 10000; // blocks
|
||||
|
||||
|
||||
/** Whether we should archive old data to reduce the database size */
|
||||
private boolean archiveEnabled = true;
|
||||
/** How often to attempt archiving (ms). */
|
||||
@@ -194,15 +193,12 @@ public class Settings {
|
||||
/** Serialization version to use when building an archive */
|
||||
private int defaultArchiveVersion = 2;
|
||||
|
||||
|
||||
/** Whether to automatically bootstrap instead of syncing from genesis */
|
||||
private boolean bootstrap = true;
|
||||
|
||||
|
||||
/** Registered names integrity check */
|
||||
private boolean namesIntegrityCheckEnabled = false;
|
||||
|
||||
|
||||
// Peer-to-peer related
|
||||
private boolean isTestNet = false;
|
||||
/** Single node testnet mode */
|
||||
@@ -230,7 +226,7 @@ public class Settings {
|
||||
public long recoveryModeTimeout = 9999999999999L;
|
||||
|
||||
/** Minimum peer version number required in order to sync with them */
|
||||
private String minPeerVersion = "4.1.2";
|
||||
private String minPeerVersion = "4.3.0";
|
||||
/** Whether to allow connections with peers below minPeerVersion
|
||||
* If true, we won't sync with them but they can still sync with us, and will show in the peers list
|
||||
* If false, sync will be blocked both ways, and they will not appear in the peers list */
|
||||
@@ -289,10 +285,10 @@ public class Settings {
|
||||
|
||||
// Bootstrap sources
|
||||
private String[] bootstrapHosts = new String[] {
|
||||
"http://bootstrap.qortal.org",
|
||||
"http://bootstrap2.qortal.org",
|
||||
"http://bootstrap3.qortal.org",
|
||||
"http://bootstrap.qortal.online"
|
||||
"http://bootstrap.qortal.org",
|
||||
"http://bootstrap2.qortal.org",
|
||||
"http://bootstrap3.qortal.org",
|
||||
"http://bootstrap.qortal.online"
|
||||
};
|
||||
|
||||
// Auto-update sources
|
||||
@@ -311,17 +307,35 @@ public class Settings {
|
||||
"1.pool.ntp.org",
|
||||
"2.pool.ntp.org",
|
||||
"3.pool.ntp.org",
|
||||
"cn.pool.ntp.org",
|
||||
"0.cn.pool.ntp.org",
|
||||
"1.cn.pool.ntp.org",
|
||||
"2.cn.pool.ntp.org",
|
||||
"3.cn.pool.ntp.org"
|
||||
"asia.pool.ntp.org",
|
||||
"0.asia.pool.ntp.org",
|
||||
"1.asia.pool.ntp.org",
|
||||
"2.asia.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() */
|
||||
private Long testNtpOffset = null;
|
||||
|
||||
|
||||
|
||||
/* Foreign chains */
|
||||
|
||||
/** 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) */
|
||||
private int bitcoinjLookaheadSize = 50;
|
||||
|
||||
|
||||
|
||||
// Data storage (QDN)
|
||||
|
||||
/** Data storage enabled/disabled*/
|
||||
@@ -371,6 +383,58 @@ public class Settings {
|
||||
/** Whether to serve QDN data without authentication */
|
||||
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
|
||||
public static class DomainMap {
|
||||
private String domain;
|
||||
@@ -396,7 +460,6 @@ public class Settings {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Constructors
|
||||
|
||||
private Settings() {
|
||||
@@ -497,6 +560,9 @@ public class Settings {
|
||||
}
|
||||
} while (settings.userPath != null);
|
||||
|
||||
// Set some additional defaults if needed
|
||||
settings.setAdditionalDefaults();
|
||||
|
||||
// Validate settings
|
||||
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
|
||||
|
||||
public String getUserPath() {
|
||||
@@ -660,17 +742,16 @@ public class Settings {
|
||||
}
|
||||
|
||||
|
||||
public int getDevProxyPort() {
|
||||
if (this.devProxyPort != null)
|
||||
return this.devProxyPort;
|
||||
public int getDevProxyPort() {
|
||||
if (this.devProxyPort != null)
|
||||
return this.devProxyPort;
|
||||
|
||||
return this.isTestNet ? TESTNET_DEV_PROXY_PORT : MAINNET_DEV_PROXY_PORT;
|
||||
}
|
||||
|
||||
public boolean isDevProxyLoggingEnabled() {
|
||||
return this.devProxyLoggingEnabled;
|
||||
}
|
||||
return this.isTestNet ? TESTNET_DEV_PROXY_PORT : MAINNET_DEV_PROXY_PORT;
|
||||
}
|
||||
|
||||
public boolean isDevProxyLoggingEnabled() {
|
||||
return this.devProxyLoggingEnabled;
|
||||
}
|
||||
|
||||
public boolean getWipeUnconfirmedOnStart() {
|
||||
return this.wipeUnconfirmedOnStart;
|
||||
@@ -1054,4 +1135,20 @@ public class Settings {
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@@ -22,14 +22,15 @@
|
||||
"maxRewardSharesPerFounderMintingAccount": 6,
|
||||
"maxRewardSharesByTimestamp": [
|
||||
{ "timestamp": 0, "maxShares": 6 },
|
||||
{ "timestamp": 1657382400000, "maxShares": 3 }
|
||||
{ "timestamp": 1657382400000, "maxShares": 3 },
|
||||
{ "timestamp": 1698508800000, "maxShares": 2 }
|
||||
],
|
||||
"founderEffectiveMintingLevel": 10,
|
||||
"onlineAccountSignaturesMinLifetime": 43200000,
|
||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||
"onlineAccountsModulusV2Timestamp": 1659801600000,
|
||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 1670230000000,
|
||||
"mempowTransactionUpdatesTimestamp": 9999999999999,
|
||||
"mempowTransactionUpdatesTimestamp": 1693558800000,
|
||||
"rewardsByHeight": [
|
||||
{ "height": 1, "reward": 5.00 },
|
||||
{ "height": 259201, "reward": 4.75 },
|
||||
|
@@ -4,52 +4,52 @@
|
||||
# "localeLang": "de",
|
||||
|
||||
### 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
|
||||
|
||||
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 ###
|
||||
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
|
||||
|
||||
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 ###
|
||||
BLOCK_UNKNOWN = block unbekannt
|
||||
BLOCK_UNKNOWN = Block unbekannt
|
||||
|
||||
### Transactions ###
|
||||
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
|
||||
TRANSACTION_INVALID = Transaktion ungültig: %s (%s)
|
||||
@@ -58,19 +58,19 @@ TRANSACTION_INVALID = Transaktion ungültig: %s (%s)
|
||||
NAME_UNKNOWN = Name unbekannt
|
||||
|
||||
### 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 ###
|
||||
GROUP_UNKNOWN = Gruppe unbekannt
|
||||
|
||||
### 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)
|
||||
|
||||
@@ -80,4 +80,4 @@ ORDER_SIZE_TOO_SMALL = Bestellmenge zu niedrig
|
||||
### Data ###
|
||||
FILE_NOT_FOUND = Datei nicht gefunden
|
||||
|
||||
NO_REPLY = Peer hat nicht mit Daten verbinden
|
||||
NO_REPLY = Peer hat nicht in vorgegebener Zeit geantwortet
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/)
|
||||
# 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
|
||||
|
||||
@@ -19,7 +19,7 @@ CONNECTION = Verbindung
|
||||
|
||||
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
|
||||
|
||||
@@ -31,18 +31,18 @@ EXIT = Verlassen
|
||||
|
||||
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,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
|
||||
|
||||
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_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_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
|
||||
|
||||
@@ -64,81 +64,81 @@ INVALID_ADDRESS = ungültige Adresse
|
||||
|
||||
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_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_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_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_OWNER = ungültiger Besitzername
|
||||
INVALID_NAME_OWNER = ungültiger Eigentümer des Namens
|
||||
|
||||
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_PAYMENTS_COUNT = Anzahl ungültiger Zahlungen
|
||||
INVALID_PAYMENTS_COUNT = ungültige Anzahl der Zahlungen
|
||||
|
||||
INVALID_PUBLIC_KEY = ungültiger öffentlicher Schlüssel
|
||||
|
||||
INVALID_QUANTITY = unzulässige Menge
|
||||
INVALID_QUANTITY = ungültige Menge
|
||||
|
||||
INVALID_REFERENCE = ungültige Referenz
|
||||
|
||||
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_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
|
||||
|
||||
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
|
||||
|
||||
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_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
|
||||
|
||||
@@ -150,46 +150,46 @@ NEGATIVE_PRICE = ungültiger/negativer Preis
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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_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
|
||||
|
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");
|
||||
}
|
||||
}
|
@@ -2,507 +2,89 @@ package org.qortal.test.crosschain.bitcoinv1;
|
||||
|
||||
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 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.BitcoinACCTv1;
|
||||
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.test.crosschain.ACCTTests;
|
||||
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.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[] hashOfSecretB = Crypto.hash160(secretB); // 31f0dd71decf59bbc8ef0661f4030479255cfa58
|
||||
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
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings();
|
||||
private static final String NAME = "Bitcoin";
|
||||
|
||||
@Override
|
||||
protected byte[] getPublicKey() {
|
||||
return bitcoinPublicKeyHash;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompile() {
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(null);
|
||||
|
||||
byte[] creationBytes = BitcoinACCTv1.buildQortalAT(tradeAccount.getAddress(), bitcoinPublicKeyHash, hashOfSecretB, redeemAmount, bitcoinAmount, tradeTimeout);
|
||||
assertNotNull(creationBytes);
|
||||
|
||||
System.out.println("AT creation bytes: " + HashCode.fromBytes(creationBytes).toString());
|
||||
@Override
|
||||
protected byte[] buildQortalAT(String address, byte[] publicKey, long redeemAmount, long foreignAmount, int tradeTimeout) {
|
||||
return BitcoinACCTv1.buildQortalAT(address,publicKey, hashOfSecretB, redeemAmount, foreignAmount,tradeTimeout);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeploy() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||
@Override
|
||||
protected ACCT getInstance() {
|
||||
return BitcoinACCTv1.getInstance();
|
||||
}
|
||||
|
||||
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);
|
||||
long partnersInitialBalance = partner.getConfirmedBalance(Asset.QORT);
|
||||
@Override
|
||||
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();
|
||||
long actualBalance = deployer.getConfirmedBalance(Asset.QORT);
|
||||
@Override
|
||||
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;
|
||||
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);
|
||||
}
|
||||
@Override
|
||||
protected String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Override
|
||||
@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 = 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 {
|
||||
public void testIncorrectSecretCorrectSender() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount deployer = Common.getTestAccount(repository, "chloe");
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(repository);
|
||||
@@ -582,197 +164,8 @@ public class BitcoinACCTv1Tests extends Common {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test
|
||||
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));
|
||||
|
||||
@Override
|
||||
protected void describeRefundAt(CrossChainTradeData tradeData, Function<Long, String> epochMilliFormatter) {
|
||||
if (tradeData.mode != AcctMode.OFFERING && tradeData.mode != AcctMode.CANCELLED) {
|
||||
System.out.println(String.format("\trefund height: block %d,\n"
|
||||
+ "\tHASH160 of secret-A: %s,\n"
|
||||
@@ -786,10 +179,4 @@ public class BitcoinACCTv1Tests extends Common {
|
||||
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;
|
||||
|
||||
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.AcctMode;
|
||||
import org.qortal.crosschain.ACCT;
|
||||
import org.qortal.crosschain.BitcoinACCTv3;
|
||||
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 org.qortal.test.crosschain.ACCTTests;
|
||||
|
||||
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;
|
||||
public class BitcoinACCTv3Tests extends ACCTTests {
|
||||
|
||||
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 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 String SYMBOL = "BTC";
|
||||
private static final String NAME = "Bitcoin";
|
||||
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings();
|
||||
@Override
|
||||
protected byte[] getPublicKey() {
|
||||
return bitcoinPublicKeyHash;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompile() {
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(null);
|
||||
|
||||
byte[] creationBytes = BitcoinACCTv3.buildQortalAT(tradeAccount.getAddress(), bitcoinPublicKeyHash, redeemAmount, bitcoinAmount, tradeTimeout);
|
||||
assertNotNull(creationBytes);
|
||||
|
||||
System.out.println("AT creation bytes: " + HashCode.fromBytes(creationBytes).toString());
|
||||
@Override
|
||||
protected byte[] buildQortalAT(String address, byte[] publicKey, long redeemAmount, long foreignAmount, int tradeTimeout) {
|
||||
return BitcoinACCTv3.buildQortalAT(address, publicKey, redeemAmount, foreignAmount, tradeTimeout);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
@Override
|
||||
protected ACCT getInstance() {
|
||||
return BitcoinACCTv3.getInstance();
|
||||
}
|
||||
|
||||
@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 = 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);
|
||||
}
|
||||
@Override
|
||||
protected int calcRefundTimeout(long partnersOfferMessageTransactionTimestamp, int lockTimeA) {
|
||||
return BitcoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||
}
|
||||
|
||||
@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 = BitcoinACCTv3.getInstance().populateTradeData(repository, atData);
|
||||
assertEquals(AcctMode.CANCELLED, tradeData.mode);
|
||||
}
|
||||
@Override
|
||||
protected byte[] buildTradeMessage(String address, byte[] publicKey, byte[] hashOfSecretA, int lockTimeA, int refundTimeout) {
|
||||
return BitcoinACCTv3.buildTradeMessage(address, publicKey, hashOfSecretA, lockTimeA, refundTimeout);
|
||||
}
|
||||
|
||||
@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 = 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);
|
||||
}
|
||||
@Override
|
||||
protected byte[] buildRedeemMessage(byte[] secretA, String address) {
|
||||
return BitcoinACCTv3.buildRedeemMessage(secretA, address);
|
||||
}
|
||||
|
||||
// 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 = 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);
|
||||
}
|
||||
@Override
|
||||
protected byte[] getCodeBytesHash() {
|
||||
return BitcoinACCTv3.CODE_BYTES_HASH;
|
||||
}
|
||||
|
||||
@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 = 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);
|
||||
}
|
||||
@Override
|
||||
protected String getSymbol() {
|
||||
return SYMBOL;
|
||||
}
|
||||
|
||||
@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 = 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()));
|
||||
}
|
||||
@Override
|
||||
protected String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@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;
|
||||
|
||||
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.AcctMode;
|
||||
import org.qortal.crosschain.ACCT;
|
||||
import org.qortal.crosschain.DigibyteACCTv3;
|
||||
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 org.qortal.test.crosschain.ACCTTests;
|
||||
|
||||
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;
|
||||
public class DigibyteACCTv3Tests extends ACCTTests {
|
||||
|
||||
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 int tradeTimeout = 20; // blocks
|
||||
public static final long redeemAmount = 80_40200000L;
|
||||
public static final long fundingAmount = 123_45600000L;
|
||||
public static final long digibyteAmount = 864200L; // 0.00864200 DGB
|
||||
private static final String SYMBOL = "DGB";
|
||||
private static final String NAME = "DigiByte";
|
||||
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings();
|
||||
@Override
|
||||
protected byte[] getPublicKey() {
|
||||
return digibytePublicKeyHash;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompile() {
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(null);
|
||||
|
||||
byte[] creationBytes = DigibyteACCTv3.buildQortalAT(tradeAccount.getAddress(), digibytePublicKeyHash, redeemAmount, digibyteAmount, tradeTimeout);
|
||||
assertNotNull(creationBytes);
|
||||
|
||||
System.out.println("AT creation bytes: " + HashCode.fromBytes(creationBytes).toString());
|
||||
@Override
|
||||
protected byte[] buildQortalAT(String address, byte[] publicKey, long redeemAmount, long foreignAmount, int tradeTimeout) {
|
||||
return DigibyteACCTv3.buildQortalAT(address, publicKey, redeemAmount, foreignAmount, tradeTimeout);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
@Override
|
||||
protected ACCT getInstance() {
|
||||
return DigibyteACCTv3.getInstance();
|
||||
}
|
||||
|
||||
@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 = 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);
|
||||
}
|
||||
@Override
|
||||
protected int calcRefundTimeout(long partnersOfferMessageTransactionTimestamp, int lockTimeA) {
|
||||
return DigibyteACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||
}
|
||||
|
||||
@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 = DigibyteACCTv3.getInstance().populateTradeData(repository, atData);
|
||||
assertEquals(AcctMode.CANCELLED, tradeData.mode);
|
||||
}
|
||||
@Override
|
||||
protected byte[] buildTradeMessage(String address, byte[] publicKey, byte[] hashOfSecretA, int lockTimeA, int refundTimeout) {
|
||||
return DigibyteACCTv3.buildTradeMessage(address, publicKey, hashOfSecretA, lockTimeA, refundTimeout);
|
||||
}
|
||||
|
||||
@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 = 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);
|
||||
}
|
||||
@Override
|
||||
protected byte[] buildRedeemMessage(byte[] secretA, String address) {
|
||||
return DigibyteACCTv3.buildRedeemMessage(secretA, address);
|
||||
}
|
||||
|
||||
// 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 = 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);
|
||||
}
|
||||
@Override
|
||||
protected byte[] getCodeBytesHash() {
|
||||
return DigibyteACCTv3.CODE_BYTES_HASH;
|
||||
}
|
||||
|
||||
@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 = 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);
|
||||
}
|
||||
@Override
|
||||
protected String getSymbol() {
|
||||
return SYMBOL;
|
||||
}
|
||||
|
||||
@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 = 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()));
|
||||
}
|
||||
@Override
|
||||
protected String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@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;
|
||||
|
||||
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.AcctMode;
|
||||
import org.qortal.crosschain.ACCT;
|
||||
import org.qortal.crosschain.DogecoinACCTv3;
|
||||
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 org.qortal.test.crosschain.ACCTTests;
|
||||
|
||||
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;
|
||||
public class DogecoinACCTv3Tests extends ACCTTests {
|
||||
|
||||
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 int tradeTimeout = 20; // blocks
|
||||
public static final long redeemAmount = 80_40200000L;
|
||||
public static final long fundingAmount = 123_45600000L;
|
||||
public static final long dogecoinAmount = 864200L; // 0.00864200 DOGE
|
||||
private static final String SYMBOL = "DOGE";
|
||||
private static final String NAME = "Dogecoin";
|
||||
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings();
|
||||
@Override
|
||||
protected byte[] getPublicKey() {
|
||||
return dogecoinPublicKeyHash;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompile() {
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(null);
|
||||
|
||||
byte[] creationBytes = DogecoinACCTv3.buildQortalAT(tradeAccount.getAddress(), dogecoinPublicKeyHash, redeemAmount, dogecoinAmount, tradeTimeout);
|
||||
assertNotNull(creationBytes);
|
||||
|
||||
System.out.println("AT creation bytes: " + HashCode.fromBytes(creationBytes).toString());
|
||||
@Override
|
||||
protected byte[] buildQortalAT(String address, byte[] publicKey, long redeemAmount, long foreignAmount, int tradeTimeout) {
|
||||
return DogecoinACCTv3.buildQortalAT(address, publicKey, redeemAmount, foreignAmount, tradeTimeout);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
@Override
|
||||
protected ACCT getInstance() {
|
||||
return DogecoinACCTv3.getInstance();
|
||||
}
|
||||
|
||||
@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 = 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);
|
||||
}
|
||||
@Override
|
||||
protected int calcRefundTimeout(long partnersOfferMessageTransactionTimestamp, int lockTimeA) {
|
||||
return DogecoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||
}
|
||||
|
||||
@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 = DogecoinACCTv3.getInstance().populateTradeData(repository, atData);
|
||||
assertEquals(AcctMode.CANCELLED, tradeData.mode);
|
||||
}
|
||||
@Override
|
||||
protected byte[] buildTradeMessage(String address, byte[] publicKey, byte[] hashOfSecretA, int lockTimeA, int refundTimeout) {
|
||||
return DogecoinACCTv3.buildTradeMessage(address, publicKey, hashOfSecretA, lockTimeA, refundTimeout);
|
||||
}
|
||||
|
||||
@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 = 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);
|
||||
}
|
||||
@Override
|
||||
protected byte[] buildRedeemMessage(byte[] secretA, String address) {
|
||||
return DogecoinACCTv3.buildRedeemMessage(secretA, address);
|
||||
}
|
||||
|
||||
// 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 = 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);
|
||||
}
|
||||
@Override
|
||||
protected byte[] getCodeBytesHash() {
|
||||
return DogecoinACCTv3.CODE_BYTES_HASH;
|
||||
}
|
||||
|
||||
@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 = 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);
|
||||
}
|
||||
@Override
|
||||
protected String getSymbol() {
|
||||
return SYMBOL;
|
||||
}
|
||||
|
||||
@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 = 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()));
|
||||
}
|
||||
@Override
|
||||
protected String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@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;
|
||||
|
||||
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 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.LitecoinACCTv1;
|
||||
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 org.qortal.test.crosschain.ACCTTests;
|
||||
|
||||
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 int tradeTimeout = 20; // blocks
|
||||
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 String SYMBOL = "LTC";
|
||||
|
||||
private static final Random RANDOM = new Random();
|
||||
private static final String NAME = "Litecoin";
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings();
|
||||
@Override
|
||||
protected byte[] getPublicKey() {
|
||||
return litecoinPublicKeyHash;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompile() {
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(null);
|
||||
|
||||
byte[] creationBytes = LitecoinACCTv1.buildQortalAT(tradeAccount.getAddress(), litecoinPublicKeyHash, redeemAmount, litecoinAmount, tradeTimeout);
|
||||
assertNotNull(creationBytes);
|
||||
|
||||
System.out.println("AT creation bytes: " + HashCode.fromBytes(creationBytes).toString());
|
||||
@Override
|
||||
protected byte[] buildQortalAT(String address, byte[] publicKey, long redeemAmount, long foreignAmount, int tradeTimeout) {
|
||||
return LitecoinACCTv1.buildQortalAT(address, publicKey, redeemAmount, foreignAmount, tradeTimeout);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
@Override
|
||||
protected ACCT getInstance() {
|
||||
return LitecoinACCTv1.getInstance();
|
||||
}
|
||||
|
||||
@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 = 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);
|
||||
}
|
||||
@Override
|
||||
protected int calcRefundTimeout(long partnersOfferMessageTransactionTimestamp, int lockTimeA) {
|
||||
return LitecoinACCTv1.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||
}
|
||||
|
||||
@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 = LitecoinACCTv1.getInstance().populateTradeData(repository, atData);
|
||||
assertEquals(AcctMode.CANCELLED, tradeData.mode);
|
||||
}
|
||||
@Override
|
||||
protected byte[] buildTradeMessage(String address, byte[] publicKey, byte[] hashOfSecretA, int lockTimeA, int refundTimeout) {
|
||||
return LitecoinACCTv1.buildTradeMessage(address, publicKey, hashOfSecretA, lockTimeA, refundTimeout);
|
||||
}
|
||||
|
||||
@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 = 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);
|
||||
}
|
||||
@Override
|
||||
protected byte[] buildRedeemMessage(byte[] secretA, String address) {
|
||||
return LitecoinACCTv1.buildRedeemMessage(secretA, address);
|
||||
}
|
||||
|
||||
// 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 = 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);
|
||||
}
|
||||
@Override
|
||||
protected byte[] getCodeBytesHash() {
|
||||
return LitecoinACCTv1.CODE_BYTES_HASH;
|
||||
}
|
||||
|
||||
@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 = 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);
|
||||
}
|
||||
@Override
|
||||
protected String getSymbol() {
|
||||
return SYMBOL;
|
||||
}
|
||||
|
||||
@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 = 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()));
|
||||
}
|
||||
@Override
|
||||
protected String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@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;
|
||||
|
||||
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.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 org.qortal.crosschain.ACCT;
|
||||
import org.qortal.crosschain.LitecoinACCTv1;
|
||||
import org.qortal.test.crosschain.ACCTTests;
|
||||
|
||||
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;
|
||||
public class LitecoinACCTv3Tests extends ACCTTests {
|
||||
|
||||
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 int tradeTimeout = 20; // blocks
|
||||
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 String SYMBOL = "LTC";
|
||||
private static final String NAME = "Litecoin";
|
||||
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings();
|
||||
@Override
|
||||
protected byte[] getPublicKey() {
|
||||
return litecoinPublicKeyHash;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompile() {
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(null);
|
||||
|
||||
byte[] creationBytes = LitecoinACCTv3.buildQortalAT(tradeAccount.getAddress(), litecoinPublicKeyHash, redeemAmount, litecoinAmount, tradeTimeout);
|
||||
assertNotNull(creationBytes);
|
||||
|
||||
System.out.println("AT creation bytes: " + HashCode.fromBytes(creationBytes).toString());
|
||||
@Override
|
||||
protected byte[] buildQortalAT(String address, byte[] publicKey, long redeemAmount, long foreignAmount, int tradeTimeout) {
|
||||
return LitecoinACCTv1.buildQortalAT(address, publicKey, redeemAmount, foreignAmount, tradeTimeout);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
@Override
|
||||
protected ACCT getInstance() {
|
||||
return LitecoinACCTv1.getInstance();
|
||||
}
|
||||
|
||||
@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 = 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);
|
||||
}
|
||||
@Override
|
||||
protected int calcRefundTimeout(long partnersOfferMessageTransactionTimestamp, int lockTimeA) {
|
||||
return LitecoinACCTv1.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||
}
|
||||
|
||||
@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 = LitecoinACCTv3.getInstance().populateTradeData(repository, atData);
|
||||
assertEquals(AcctMode.CANCELLED, tradeData.mode);
|
||||
}
|
||||
@Override
|
||||
protected byte[] buildTradeMessage(String address, byte[] publicKey, byte[] hashOfSecretA, int lockTimeA, int refundTimeout) {
|
||||
return LitecoinACCTv1.buildTradeMessage(address, publicKey, hashOfSecretA, lockTimeA, refundTimeout);
|
||||
}
|
||||
|
||||
@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 = 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);
|
||||
}
|
||||
@Override
|
||||
protected byte[] buildRedeemMessage(byte[] secretA, String address) {
|
||||
return LitecoinACCTv1.buildRedeemMessage(secretA, address);
|
||||
}
|
||||
|
||||
// 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 = 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);
|
||||
}
|
||||
@Override
|
||||
protected byte[] getCodeBytesHash() {
|
||||
return LitecoinACCTv1.CODE_BYTES_HASH;
|
||||
}
|
||||
|
||||
@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 = 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);
|
||||
}
|
||||
@Override
|
||||
protected String getSymbol() {
|
||||
return SYMBOL;
|
||||
}
|
||||
|
||||
@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 = 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()));
|
||||
}
|
||||
@Override
|
||||
protected String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@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;
|
||||
|
||||
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.AcctMode;
|
||||
import org.qortal.crosschain.ACCT;
|
||||
import org.qortal.crosschain.PirateChainACCTv3;
|
||||
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 org.qortal.test.crosschain.ACCTTests;
|
||||
|
||||
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;
|
||||
public class PirateChainACCTv3Tests extends ACCTTests {
|
||||
|
||||
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 int tradeTimeout = 20; // blocks
|
||||
public static final long redeemAmount = 80_40200000L;
|
||||
public static final long fundingAmount = 123_45600000L;
|
||||
public static final long arrrAmount = 864200L; // 0.00864200 ARRR
|
||||
private static final String SYMBOL = "ARRR";
|
||||
private static final String NAME = "Pirate Chain";
|
||||
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings();
|
||||
@Override
|
||||
protected byte[] getPublicKey() {
|
||||
return pirateChainPublicKey;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompile() {
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(null);
|
||||
|
||||
byte[] creationBytes = PirateChainACCTv3.buildQortalAT(tradeAccount.getAddress(), pirateChainPublicKey, redeemAmount, arrrAmount, tradeTimeout);
|
||||
assertNotNull(creationBytes);
|
||||
|
||||
System.out.println("AT creation bytes: " + HashCode.fromBytes(creationBytes).toString());
|
||||
@Override
|
||||
protected byte[] buildQortalAT(String address, byte[] publicKey, long redeemAmount, long foreignAmount, int tradeTimeout) {
|
||||
return PirateChainACCTv3.buildQortalAT(address, publicKey, redeemAmount, foreignAmount, tradeTimeout);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
@Override
|
||||
protected ACCT getInstance() {
|
||||
return PirateChainACCTv3.getInstance();
|
||||
}
|
||||
|
||||
@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 = 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);
|
||||
}
|
||||
@Override
|
||||
protected int calcRefundTimeout(long partnersOfferMessageTransactionTimestamp, int lockTimeA) {
|
||||
return PirateChainACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||
}
|
||||
|
||||
@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 = PirateChainACCTv3.getInstance().populateTradeData(repository, atData);
|
||||
assertEquals(AcctMode.CANCELLED, tradeData.mode);
|
||||
}
|
||||
@Override
|
||||
protected byte[] buildTradeMessage(String address, byte[] publicKey, byte[] hashOfSecretA, int lockTimeA, int refundTimeout) {
|
||||
return PirateChainACCTv3.buildTradeMessage(address, publicKey, hashOfSecretA, lockTimeA, refundTimeout);
|
||||
}
|
||||
|
||||
@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 = 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);
|
||||
}
|
||||
@Override
|
||||
protected byte[] buildRedeemMessage(byte[] secretA, String address) {
|
||||
return PirateChainACCTv3.buildRedeemMessage(secretA, address);
|
||||
}
|
||||
|
||||
// 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 = 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);
|
||||
}
|
||||
@Override
|
||||
protected byte[] getCodeBytesHash() {
|
||||
return PirateChainACCTv3.CODE_BYTES_HASH;
|
||||
}
|
||||
|
||||
@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 = 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);
|
||||
}
|
||||
@Override
|
||||
protected String getSymbol() {
|
||||
return SYMBOL;
|
||||
}
|
||||
|
||||
@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 = 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()));
|
||||
}
|
||||
@Override
|
||||
protected String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@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;
|
||||
|
||||
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.AcctMode;
|
||||
import org.qortal.crosschain.ACCT;
|
||||
import org.qortal.crosschain.RavencoinACCTv3;
|
||||
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 org.qortal.test.crosschain.ACCTTests;
|
||||
|
||||
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;
|
||||
public class RavencoinACCTv3Tests extends ACCTTests {
|
||||
|
||||
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 int tradeTimeout = 20; // blocks
|
||||
public static final long redeemAmount = 80_40200000L;
|
||||
public static final long fundingAmount = 123_45600000L;
|
||||
public static final long ravencoinAmount = 864200L; // 0.00864200 RVN
|
||||
private static final String SYMBOL = "RVN";
|
||||
private static final String NAME = "Ravencoin";
|
||||
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings();
|
||||
@Override
|
||||
protected byte[] getPublicKey() {
|
||||
return ravencoinPublicKeyHash;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompile() {
|
||||
PrivateKeyAccount tradeAccount = createTradeAccount(null);
|
||||
|
||||
byte[] creationBytes = RavencoinACCTv3.buildQortalAT(tradeAccount.getAddress(), ravencoinPublicKeyHash, redeemAmount, ravencoinAmount, tradeTimeout);
|
||||
assertNotNull(creationBytes);
|
||||
|
||||
System.out.println("AT creation bytes: " + HashCode.fromBytes(creationBytes).toString());
|
||||
@Override
|
||||
protected byte[] buildQortalAT(String address, byte[] publicKey, long redeemAmount, long foreignAmount, int tradeTimeout) {
|
||||
return RavencoinACCTv3.buildQortalAT(address, publicKey, redeemAmount, foreignAmount, tradeTimeout);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
@Override
|
||||
protected ACCT getInstance() {
|
||||
return RavencoinACCTv3.getInstance();
|
||||
}
|
||||
|
||||
@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 = 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);
|
||||
}
|
||||
@Override
|
||||
protected int calcRefundTimeout(long partnersOfferMessageTransactionTimestamp, int lockTimeA) {
|
||||
return RavencoinACCTv3.calcRefundTimeout(partnersOfferMessageTransactionTimestamp, lockTimeA);
|
||||
}
|
||||
|
||||
@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 = RavencoinACCTv3.getInstance().populateTradeData(repository, atData);
|
||||
assertEquals(AcctMode.CANCELLED, tradeData.mode);
|
||||
}
|
||||
@Override
|
||||
protected byte[] buildTradeMessage(String address, byte[] publicKey, byte[] hashOfSecretA, int lockTimeA, int refundTimeout) {
|
||||
return RavencoinACCTv3.buildTradeMessage(address, publicKey, hashOfSecretA, lockTimeA, refundTimeout);
|
||||
}
|
||||
|
||||
@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 = 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);
|
||||
}
|
||||
@Override
|
||||
protected byte[] buildRedeemMessage(byte[] secretA, String address) {
|
||||
return RavencoinACCTv3.buildRedeemMessage(secretA, address);
|
||||
}
|
||||
|
||||
// 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 = 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);
|
||||
}
|
||||
@Override
|
||||
protected byte[] getCodeBytesHash() {
|
||||
return RavencoinACCTv3.CODE_BYTES_HASH;
|
||||
}
|
||||
|
||||
@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 = 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);
|
||||
}
|
||||
@Override
|
||||
protected String getSymbol() {
|
||||
return SYMBOL;
|
||||
}
|
||||
|
||||
@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 = 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()));
|
||||
}
|
||||
@Override
|
||||
protected String getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@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");
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -26,6 +26,7 @@
|
||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||
"onlineAccountsModulusV2Timestamp": 0,
|
||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||
"mempowTransactionUpdatesTimestamp": 1692554400000,
|
||||
"rewardsByHeight": [
|
||||
{ "height": 1, "reward": 5.00 },
|
||||
{ "height": 259201, "reward": 4.75 },
|
||||
|
@@ -50,7 +50,7 @@ tx_json=$( cat <<TX_END
|
||||
{
|
||||
"timestamp": ${timestamp},
|
||||
"reference": "${lastref}",
|
||||
"fee": 0.001,
|
||||
"fee": 0.01,
|
||||
"txGroupId": 0,
|
||||
"adminPublicKey": "${pubkey}",
|
||||
"pendingSignature": "${sig}",
|
||||
|
@@ -50,7 +50,7 @@ tx_json=$( cat <<TX_END
|
||||
{
|
||||
"timestamp": ${timestamp},
|
||||
"reference": "${lastref}",
|
||||
"fee": 0.001,
|
||||
"fee": 0.01,
|
||||
"txGroupId": 0,
|
||||
"adminPublicKey": "${pubkey}",
|
||||
"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 POSIX;
|
||||
use Getopt::Std;
|
||||
use File::Slurp;
|
||||
|
||||
sub usage() {
|
||||
die("usage: $0 [-p api-port] dev-private-key [short-commit-hash]\n");
|
||||
@@ -34,6 +35,8 @@ while (<POM>) {
|
||||
}
|
||||
close(POM);
|
||||
|
||||
my $apikey = read_file('apikey.txt');
|
||||
|
||||
# Do we need to determine commit hash?
|
||||
unless ($commit_hash) {
|
||||
# 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;
|
||||
|
||||
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 $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;
|
||||
|
||||
@@ -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}$/;
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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}`;
|
||||
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;
|
||||
|
||||
# Check we can actually fetch update
|
||||
|
122
tools/qdn
122
tools/qdn
@@ -5,10 +5,10 @@ host="localhost"
|
||||
port=12391
|
||||
|
||||
if [ -z "$*" ]; then
|
||||
echo "Usage:"
|
||||
echo "Usage:"
|
||||
echo
|
||||
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
|
||||
echo "Fetch data:"
|
||||
@@ -22,6 +22,21 @@ if [ -z "$*" ]; then
|
||||
exit
|
||||
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 )
|
||||
|
||||
if [ -f "apikey.txt" ]; then
|
||||
@@ -37,32 +52,46 @@ service=$2
|
||||
name=$3
|
||||
|
||||
if [ -z "${method}" ]; then
|
||||
echo "Error: missing method"; exit
|
||||
echo "Error: missing method"
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "${service}" ]; then
|
||||
echo "Error: missing service"; exit
|
||||
echo "Error: missing service"
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "${name}" ]; then
|
||||
echo "Error: missing name"; exit
|
||||
echo "Error: missing name"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
if [[ "${method}" == "POST" ]]; then
|
||||
type=$4
|
||||
data=$5
|
||||
identifier=$6
|
||||
title=$7
|
||||
description=$8
|
||||
tags=$9
|
||||
category=${10}
|
||||
fee=${11}
|
||||
preview=${12}
|
||||
|
||||
|
||||
|
||||
if [ -z "${data}" ]; 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
|
||||
echo "Error: missing data string"; exit
|
||||
echo "Error: missing data string - please input the data string you wish to publish"
|
||||
exit 1
|
||||
else
|
||||
echo "Error: unrecognized type"; exit
|
||||
echo "Error: unrecognized type"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
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
|
||||
|
||||
if [ -z "${identifier}" ]; then
|
||||
@@ -75,30 +104,88 @@ if [[ "${method}" == "POST" ]]; then
|
||||
elif [[ "${type}" == "STRING" ]]; then
|
||||
type_component="/string"
|
||||
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..."
|
||||
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
|
||||
echo "${tx_data}"; exit
|
||||
echo "Error creating transaction: ${tx_data}"
|
||||
exit 1
|
||||
elif [ -z "${tx_data}" ]; then
|
||||
echo "Error: no transaction data returned"; exit
|
||||
echo "Error: no transaction data returned"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
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}")
|
||||
|
||||
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
|
||||
|
||||
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}\"}")
|
||||
|
||||
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
|
||||
|
||||
echo "Broadcasting..."
|
||||
success=$(curl --silent --insecure -X POST "http://${host}:${port}/transactions/process" -H "Content-Type: text/plain" -d "${signed_tx_data}")
|
||||
|
||||
if [[ "${success}" == "true" ]]; then
|
||||
echo "Transaction broadcast successfully"
|
||||
else
|
||||
@@ -131,9 +218,10 @@ elif [[ "${method}" == "GET" ]]; then
|
||||
echo "Empty response from ${host}:${port}"
|
||||
fi
|
||||
if [[ "${response}" == *"error"* || "${response}" == *"ERROR"* ]]; then
|
||||
echo "${response}"; exit
|
||||
echo "${response}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "${response}"
|
||||
|
||||
fi
|
||||
|
||||
|
@@ -40,7 +40,7 @@ our %b58 = map { $b58[$_] => $_ } 0 .. 57;
|
||||
our %reverseb58 = reverse %b58;
|
||||
|
||||
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 = (
|
||||
payment => {
|
||||
@@ -92,7 +92,7 @@ our %TRANSACTION_TYPES = (
|
||||
},
|
||||
remove_group_admin => {
|
||||
url => 'groups/removeadmin',
|
||||
required => [qw(groupId txGroupId member)],
|
||||
required => [qw(groupId txGroupId admin)],
|
||||
key_name => 'ownerPublicKey',
|
||||
},
|
||||
group_approval => {
|
||||
|
Reference in New Issue
Block a user