diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..7b016a89 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.compile.nullAnalysis.mode": "automatic" +} \ No newline at end of file diff --git a/WindowsInstaller/Install Files/log4j2.properties b/WindowsInstaller/Install Files/log4j2.properties index 44e1b1e3..54f295c1 100755 --- a/WindowsInstaller/Install Files/log4j2.properties +++ b/WindowsInstaller/Install Files/log4j2.properties @@ -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 diff --git a/lib/org/ciyam/AT/1.4.1/AT-1.4.1.jar b/lib/org/ciyam/AT/1.4.1/AT-1.4.1.jar new file mode 100644 index 00000000..05c548c8 Binary files /dev/null and b/lib/org/ciyam/AT/1.4.1/AT-1.4.1.jar differ diff --git a/lib/org/ciyam/AT/1.4.1/AT-1.4.1.pom b/lib/org/ciyam/AT/1.4.1/AT-1.4.1.pom new file mode 100644 index 00000000..16f644b9 --- /dev/null +++ b/lib/org/ciyam/AT/1.4.1/AT-1.4.1.pom @@ -0,0 +1,124 @@ + + 4.0.0 + org.ciyam + AT + 1.4.1 + jar + + + UTF-8 + false + + 3.8.1 + 3.2.0 + 3.3.1 + 3.0.0-M4 + 3.2.0 + + 1.64 + + + + src/main/java + src/test/java + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + 11 + 11 + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + ${skipTests} + + + + org.apache.maven.plugins + maven-source-plugin + ${maven-source-plugin.version} + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + + attach-javadoc + + jar + + + + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + + test-jar + + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + + org.apache.maven.plugins + maven-source-plugin + ${maven-source-plugin.version} + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + org.bouncycastle + bcprov-jdk15on + ${bouncycastle.version} + test + + + junit + junit + 4.13 + test + + + diff --git a/lib/org/ciyam/AT/maven-metadata-local.xml b/lib/org/ciyam/AT/maven-metadata-local.xml index 063c735d..d8f3dd34 100644 --- a/lib/org/ciyam/AT/maven-metadata-local.xml +++ b/lib/org/ciyam/AT/maven-metadata-local.xml @@ -3,7 +3,7 @@ org.ciyam AT - 1.4.0 + 1.4.1 1.3.4 1.3.5 @@ -11,7 +11,8 @@ 1.3.7 1.3.8 1.4.0 + 1.4.1 - 20221105114346 + 20230821074325 diff --git a/log4j2.properties b/log4j2.properties index 44e1b1e3..54f295c1 100644 --- a/log4j2.properties +++ b/log4j2.properties @@ -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 diff --git a/pom.xml b/pom.xml index 30ad1e04..e623a5dd 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.qortal qortal - 4.2.2 + 4.3.1 jar true @@ -11,32 +11,52 @@ 0.15.10 1.69 ${maven.build.timestamp} - 1.4.0 - 3.6 - 1.8 - 2.6 - 1.21 - 3.12.0 + 1.4.1 + 3.8.0 + 1.10.0 + 2.11.0 + 1.24.0 + 3.13.0 1.9 1.2.2 - 28.1-jre + 32.1.3-jre 2.5.1 1.2.1 - 70.1 + 73.2 1.1 - 2.29.1 - 9.4.29.v20200521 - 2.17.1 + 2.3.3 + 2.40 + 9.4.53.v20231009 + 2.20.0 UTF-8 - 1.7.12 - 2.0.9 - 3.23.8 + 1.7.36 + 2.0.10 + 3.52.5 1.1.0 - 1.13.1 - 4.10 - 1.45.1 - 3.19.4 + 1.16.1 + 4.12 + 1.58.0 + 3.24.4 1.17 + 2.16.1 + 3.11.0 + 4.9.10 + 1.5.3 + 3.3.1 + 3.4.0 + 3.3.0 + 3.5.1 + 0.16 + 3.1.2 + 1.0.0 + 3.4.0 + 1.1.1 + 20231013 + 0.12.3 + 4.0.1 + 1.5.0-b01 + 5.3.1 + 1.3 src/main/java @@ -51,14 +71,14 @@ org.codehaus.mojo versions-maven-plugin - 2.5 + ${versions-maven-plugin.version} false maven-compiler-plugin - 3.8.0 + ${maven-compiler-plugin.version} 11 @@ -89,7 +109,7 @@ pl.project13.maven git-commit-id-plugin - 4.0.0 + ${git-commit-id-plugin.version} get-the-git-infos @@ -121,7 +141,7 @@ com.google.code.maven-replacer-plugin replacer - 1.5.3 + ${replacer.version} replace-swagger-ui @@ -164,11 +184,13 @@ ${project.build.outputDirectory}/git.properties true - MULTILINE + + MULTILINE + ^(#.*$[\n\r]*) - + @@ -178,7 +200,10 @@ maven-resources-plugin - 3.1.0 + ${maven-resources-plugin.version} + + ISO-8859-1 + copy-resources @@ -232,7 +257,7 @@ org.codehaus.mojo build-helper-maven-plugin - 3.0.0 + ${build-helper-maven-plugin.version} generate-sources @@ -250,7 +275,7 @@ org.apache.maven.plugins maven-jar-plugin - 3.2.0 + ${maven-jar-plugin.version} @@ -268,13 +293,12 @@ org.apache.maven.plugins maven-shade-plugin - 2.4.3 + ${maven-shade-plugin.version} false - + org.webjars:swagger-ui junit:junit @@ -318,7 +342,7 @@ io.github.zlika reproducible-build-maven-plugin - 0.11 + ${reproducible-build-maven-plugin.version} package @@ -335,7 +359,7 @@ org.apache.maven.plugins maven-surefire-plugin - 2.22.2 + ${maven-surefire-plugin.version} ${skipTests} @@ -347,7 +371,7 @@ org.eclipse.m2e lifecycle-mapping - 1.0.0 + ${lifecycle-mapping.version} @@ -360,14 +384,14 @@ maven-dependency-plugin - [2.8,) + [3.6.0,) unpack - + @@ -386,7 +410,7 @@ - + @@ -413,15 +437,17 @@ org.codehaus.mojo build-helper-maven-plugin - 3.0.0 - provided + ${build-helper-maven-plugin.version} + provided + com.github.bohnman package-info-maven-plugin ${package-info-maven-plugin.version} - provided + provided + @@ -457,12 +483,12 @@ com.googlecode.json-simple json-simple - 1.1.1 + ${json-simple.version} org.json json - 20210307 + ${json.version} org.apache.commons @@ -493,7 +519,7 @@ io.druid extendedset - 0.12.3 + ${extendedset.version} @@ -564,12 +590,12 @@ javax.servlet javax.servlet-api - 4.0.1 + ${javax.servlet-api.version} javax.mail mail - 1.5.0-b01 + ${mail.version} @@ -638,7 +664,8 @@ jersey-hk2 ${jersey.version} - + + javax.inject javax.inject @@ -665,7 +692,8 @@ swagger-jaxrs2-servlet-initializer ${swagger-api.version} - + + io.swagger.core.v3 swagger-integration @@ -681,12 +709,12 @@ org.junit.jupiter junit-jupiter-engine - 5.3.1 + ${junit-jupiter-engine.version} org.hamcrest hamcrest-library - 1.3 + ${hamcrest-library.version} --> @@ -735,5 +763,11 @@ simplemagic ${simplemagic.version} + + + org.glassfish.jaxb + jaxb-runtime + ${jaxb-runtime.version} + diff --git a/src/main/java/org/qortal/api/model/AtCreationRequest.java b/src/main/java/org/qortal/api/model/AtCreationRequest.java new file mode 100644 index 00000000..14ccdaa2 --- /dev/null +++ b/src/main/java/org/qortal/api/model/AtCreationRequest.java @@ -0,0 +1,102 @@ +package org.qortal.api.model; + +import io.swagger.v3.oas.annotations.media.Schema; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlTransient; + +import org.bouncycastle.util.encoders.Base64; +import org.bouncycastle.util.encoders.DecoderException; + +@XmlAccessorType(XmlAccessType.FIELD) +public class AtCreationRequest { + + @Schema(description = "CIYAM AT version", example = "2") + private short ciyamAtVersion; + + @Schema(description = "base64-encoded code bytes", example = "") + private String codeBytesBase64; + + @Schema(description = "base64-encoded data bytes", example = "") + private String dataBytesBase64; + + private short numCallStackPages; + private short numUserStackPages; + private long minActivationAmount; + + // Default constructor for JSON deserialization + public AtCreationRequest() {} + + // Getters and setters + public short getCiyamAtVersion() { + return ciyamAtVersion; + } + + public void setCiyamAtVersion(short ciyamAtVersion) { + this.ciyamAtVersion = ciyamAtVersion; + } + + + public String getCodeBytesBase64() { + return this.codeBytesBase64; + } + + @XmlTransient + @Schema(hidden = true) + public byte[] getCodeBytes() { + if (this.codeBytesBase64 != null) { + try { + return Base64.decode(this.codeBytesBase64); + } + catch (DecoderException e) { + return null; + } + } + return null; + } + + + public String getDataBytesBase64() { + return this.dataBytesBase64; + } + + @XmlTransient + @Schema(hidden = true) + public byte[] getDataBytes() { + if (this.dataBytesBase64 != null) { + try { + return Base64.decode(this.dataBytesBase64); + } + catch (DecoderException e) { + return null; + } + } + return null; + } + + + public short getNumCallStackPages() { + return numCallStackPages; + } + + public void setNumCallStackPages(short numCallStackPages) { + this.numCallStackPages = numCallStackPages; + } + + public short getNumUserStackPages() { + return numUserStackPages; + } + + public void setNumUserStackPages(short numUserStackPages) { + this.numUserStackPages = numUserStackPages; + } + + public long getMinActivationAmount() { + return minActivationAmount; + } + + public void setMinActivationAmount(long minActivationAmount) { + this.minActivationAmount = minActivationAmount; + } +} diff --git a/src/main/java/org/qortal/api/resource/ArbitraryResource.java b/src/main/java/org/qortal/api/resource/ArbitraryResource.java index 9be74a2f..441492a8 100644 --- a/src/main/java/org/qortal/api/resource/ArbitraryResource.java +++ b/src/main/java/org/qortal/api/resource/ArbitraryResource.java @@ -706,7 +706,7 @@ public class ArbitraryResource { ArbitraryDataResource resource = new ArbitraryDataResource(name, ResourceIdType.NAME, service, identifier); try { - ArbitraryDataTransactionMetadata transactionMetadata = ArbitraryMetadataManager.getInstance().fetchMetadata(resource, false); + ArbitraryDataTransactionMetadata transactionMetadata = ArbitraryMetadataManager.getInstance().fetchMetadata(resource, true); if (transactionMetadata != null) { ArbitraryResourceMetadata resourceMetadata = ArbitraryResourceMetadata.fromTransactionMetadata(transactionMetadata, true); if (resourceMetadata != null) { diff --git a/src/main/java/org/qortal/api/resource/AtResource.java b/src/main/java/org/qortal/api/resource/AtResource.java index 29a2344d..13bfec83 100644 --- a/src/main/java/org/qortal/api/resource/AtResource.java +++ b/src/main/java/org/qortal/api/resource/AtResource.java @@ -27,6 +27,7 @@ import org.qortal.api.ApiException; import org.qortal.api.ApiExceptionFactory; import org.qortal.data.at.ATData; import org.qortal.data.at.ATStateData; +import org.qortal.api.model.AtCreationRequest; import org.qortal.data.transaction.DeployAtTransactionData; import org.qortal.repository.DataException; import org.qortal.repository.Repository; @@ -38,9 +39,14 @@ import org.qortal.transform.TransformationException; import org.qortal.transform.transaction.DeployAtTransactionTransformer; import org.qortal.utils.Base58; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + @Path("/at") @Tag(name = "Automated Transactions") public class AtResource { + private static final Logger logger = LoggerFactory.getLogger(AtResource.class); @Context HttpServletRequest request; @@ -156,6 +162,52 @@ public class AtResource { } } + @POST + @Path("/create") + @Operation( + summary = "Create base58-encoded AT creation bytes from the provided parameters", + requestBody = @RequestBody( + required = true, + content = @Content( + mediaType = MediaType.APPLICATION_JSON, + schema = @Schema( + implementation = AtCreationRequest.class + ) + ) + ), + responses = { + @ApiResponse( + description = "AT creation bytes suitable for use in a DEPLOY_AT transaction", + content = @Content( + mediaType = MediaType.TEXT_PLAIN, + schema = @Schema( + type = "string" + ) + ) + ) + } + ) + public String create(AtCreationRequest atCreationRequest) { + if (atCreationRequest.getCiyamAtVersion() < 2) { + throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, "ciyamAtVersion must be at least 2"); + } + if (atCreationRequest.getCodeBytes() == null) { + throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, "Valid codeBytesBase64 must be supplied"); + } + if (atCreationRequest.getDataBytes() == null) { + throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, "Valid dataBytesBase64 must be supplied"); + } + + byte[] creationBytes = MachineState.toCreationBytes( + atCreationRequest.getCiyamAtVersion(), + atCreationRequest.getCodeBytes(), + atCreationRequest.getDataBytes(), + atCreationRequest.getNumCallStackPages(), + atCreationRequest.getNumUserStackPages(), + atCreationRequest.getMinActivationAmount() + ); + return Base58.encode(creationBytes); + } @POST @Operation( summary = "Build raw, unsigned, DEPLOY_AT transaction", diff --git a/src/main/java/org/qortal/api/restricted/resource/AdminResource.java b/src/main/java/org/qortal/api/restricted/resource/AdminResource.java index ecb8c6c9..178d7547 100644 --- a/src/main/java/org/qortal/api/restricted/resource/AdminResource.java +++ b/src/main/java/org/qortal/api/restricted/resource/AdminResource.java @@ -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) { diff --git a/src/main/java/org/qortal/arbitrary/ArbitraryDataRenderer.java b/src/main/java/org/qortal/arbitrary/ArbitraryDataRenderer.java index 0936b3ec..0f513403 100644 --- a/src/main/java/org/qortal/arbitrary/ArbitraryDataRenderer.java +++ b/src/main/java/org/qortal/arbitrary/ArbitraryDataRenderer.java @@ -129,6 +129,11 @@ public class ArbitraryDataRenderer { String filename = this.getFilename(unzippedPath, inPath); Path filePath = Paths.get(unzippedPath, filename); boolean usingCustomRouting = false; + if (Files.isDirectory(filePath) && (!inPath.endsWith("/"))) { + inPath = inPath + "/"; + filename = this.getFilename(unzippedPath, inPath); + filePath = Paths.get(unzippedPath, filename); + } // If the file doesn't exist, we may need to route the request elsewhere, or cleanup if (!Files.exists(filePath)) { diff --git a/src/main/java/org/qortal/block/Block.java b/src/main/java/org/qortal/block/Block.java index e030e6a6..16c061da 100644 --- a/src/main/java/org/qortal/block/Block.java +++ b/src/main/java/org/qortal/block/Block.java @@ -84,6 +84,7 @@ public class Block { TRANSACTION_PROCESSING_FAILED(53), TRANSACTION_ALREADY_PROCESSED(54), TRANSACTION_NEEDS_APPROVAL(55), + TRANSACTION_NOT_CONFIRMABLE(56), AT_STATES_MISMATCH(61), ONLINE_ACCOUNTS_INVALID(70), ONLINE_ACCOUNT_UNKNOWN(71), @@ -130,6 +131,9 @@ public class Block { /** Locally-generated AT fees */ protected long ourAtFees; // Generated locally + /** Cached online accounts validation decision, to avoid revalidating when true */ + private boolean onlineAccountsAlreadyValid = false; + @FunctionalInterface private interface BlockRewardDistributor { long distribute(long amount, Map balanceChanges) throws DataException; @@ -563,6 +567,13 @@ public class Block { } + /** + * Force online accounts to be revalidated, e.g. at final stage of block minting. + */ + public void clearOnlineAccountsValidationCache() { + this.onlineAccountsAlreadyValid = false; + } + // More information /** @@ -1043,6 +1054,10 @@ public class Block { if (this.blockData.getHeight() != null && this.blockData.getHeight() == 1) return ValidationResult.OK; + // Don't bother revalidating if accounts have already been validated in this block + if (this.onlineAccountsAlreadyValid) + return ValidationResult.OK; + // Expand block's online accounts indexes into actual accounts ConciseSet accountIndexes = BlockTransformer.decodeOnlineAccounts(this.blockData.getEncodedOnlineAccounts()); // We use count of online accounts to validate decoded account indexes @@ -1130,6 +1145,9 @@ public class Block { // All online accounts valid, so save our list of online accounts for potential later use this.cachedOnlineRewardShares = onlineRewardShares; + // Remember that the accounts are valid, to speed up subsequent checks + this.onlineAccountsAlreadyValid = true; + return ValidationResult.OK; } @@ -1234,6 +1252,13 @@ public class Block { || transaction.getDeadline() <= this.blockData.getTimestamp()) return ValidationResult.TRANSACTION_TIMESTAMP_INVALID; + // After feature trigger, check that this transaction is confirmable + if (transactionData.getTimestamp() >= BlockChain.getInstance().getMemPoWTransactionUpdatesTimestamp()) { + if (!transaction.isConfirmable()) { + return ValidationResult.TRANSACTION_NOT_CONFIRMABLE; + } + } + // Check transaction isn't already included in a block if (this.repository.getTransactionRepository().isConfirmed(transactionData.getSignature())) return ValidationResult.TRANSACTION_ALREADY_PROCESSED; diff --git a/src/main/java/org/qortal/block/BlockChain.java b/src/main/java/org/qortal/block/BlockChain.java index 218fb14d..540e6cf4 100644 --- a/src/main/java/org/qortal/block/BlockChain.java +++ b/src/main/java/org/qortal/block/BlockChain.java @@ -48,9 +48,6 @@ public class BlockChain { /** Transaction expiry period, starting from transaction's timestamp, in milliseconds. */ private long transactionExpiryPeriod; - @XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class) - private long unitFee; - private int maxBytesPerUnitFee; /** Maximum acceptable timestamp disagreement offset in milliseconds. */ @@ -89,6 +86,7 @@ public class BlockChain { @XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class) public long fee; } + private List unitFees; private List nameRegistrationUnitFees; /** Map of which blockchain features are enabled when (height/timestamp) */ @@ -211,6 +209,9 @@ public class BlockChain { /** Snapshot timestamp for self sponsorship algo V1 */ private long selfSponsorshipAlgoV1SnapshotTimestamp; + /** Feature-trigger timestamp to modify behaviour of various transactions that support mempow */ + private long mempowTransactionUpdatesTimestamp; + /** Max reward shares by block height */ public static class MaxRewardSharesByTimestamp { public long timestamp; @@ -346,10 +347,6 @@ public class BlockChain { return this.isTestChain; } - public long getUnitFee() { - return this.unitFee; - } - public int getMaxBytesPerUnitFee() { return this.maxBytesPerUnitFee; } @@ -376,6 +373,11 @@ public class BlockChain { return this.selfSponsorshipAlgoV1SnapshotTimestamp; } + // Feature-trigger timestamp to modify behaviour of various transactions that support mempow + public long getMemPoWTransactionUpdatesTimestamp() { + return this.mempowTransactionUpdatesTimestamp; + } + /** Returns true if approval-needing transaction types require a txGroupId other than NO_GROUP. */ public boolean getRequireGroupForApproval() { return this.requireGroupForApproval; @@ -547,13 +549,22 @@ public class BlockChain { throw new IllegalStateException(String.format("No block timing info available for height %d", ourHeight)); } + public long getUnitFeeAtTimestamp(long ourTimestamp) { + for (int i = unitFees.size() - 1; i >= 0; --i) + if (unitFees.get(i).timestamp <= ourTimestamp) + return unitFees.get(i).fee; + + // Shouldn't happen, but set a sensible default just in case + return 100000; + } + public long getNameRegistrationUnitFeeAtTimestamp(long ourTimestamp) { for (int i = nameRegistrationUnitFees.size() - 1; i >= 0; --i) if (nameRegistrationUnitFees.get(i).timestamp <= ourTimestamp) return nameRegistrationUnitFees.get(i).fee; - // Default to system-wide unit fee - return this.getUnitFee(); + // Shouldn't happen, but set a sensible default just in case + return 100000; } public int getMaxRewardSharesAtTimestamp(long ourTimestamp) { diff --git a/src/main/java/org/qortal/controller/BlockMinter.java b/src/main/java/org/qortal/controller/BlockMinter.java index b1ed7e3c..35c89778 100644 --- a/src/main/java/org/qortal/controller/BlockMinter.java +++ b/src/main/java/org/qortal/controller/BlockMinter.java @@ -562,6 +562,9 @@ public class BlockMinter extends Thread { // Sign to create block's signature newBlock.sign(); + // Ensure online accounts are fully re-validated in this final check + newBlock.clearOnlineAccountsValidationCache(); + // Is newBlock still valid? ValidationResult validationResult = newBlock.isValid(); if (validationResult != ValidationResult.OK) diff --git a/src/main/java/org/qortal/controller/Synchronizer.java b/src/main/java/org/qortal/controller/Synchronizer.java index 2dad62e7..804bacbb 100644 --- a/src/main/java/org/qortal/controller/Synchronizer.java +++ b/src/main/java/org/qortal/controller/Synchronizer.java @@ -229,13 +229,6 @@ public class Synchronizer extends Thread { peers.removeIf(Controller.hasOldVersion); checkRecoveryModeForPeers(peers); - if (recoveryMode) { - // Needs a mutable copy of the unmodifiableList - peers = new ArrayList<>(Network.getInstance().getImmutableHandshakedPeers()); - peers.removeIf(Controller.hasOnlyGenesisBlock); - peers.removeIf(Controller.hasMisbehaved); - peers.removeIf(Controller.hasOldVersion); - } // Check we have enough peers to potentially synchronize if (peers.size() < Settings.getInstance().getMinBlockchainPeers()) @@ -262,10 +255,7 @@ public class Synchronizer extends Thread { peers.removeIf(Controller.hasInferiorChainTip); // Remove any peers that are no longer on a recent block since the last check - // Except for times when we're in recovery mode, in which case we need to keep them - if (!recoveryMode) { - peers.removeIf(Controller.hasNoRecentBlock); - } + peers.removeIf(Controller.hasNoRecentBlock); final int peersRemoved = peersBeforeComparison - peers.size(); if (peersRemoved > 0 && peers.size() > 0) @@ -1340,8 +1330,8 @@ public class Synchronizer extends Thread { return SynchronizationResult.INVALID_DATA; } - // Final check to make sure the peer isn't out of date (except for when we're in recovery mode) - if (!recoveryMode && peer.getChainTipData() != null) { + // Final check to make sure the peer isn't out of date + if (peer.getChainTipData() != null) { final Long minLatestBlockTimestamp = Controller.getMinimumLatestBlockTimestamp(); final Long peerLastBlockTimestamp = peer.getChainTipData().getTimestamp(); if (peerLastBlockTimestamp == null || peerLastBlockTimestamp < minLatestBlockTimestamp) { diff --git a/src/main/java/org/qortal/controller/TransactionImporter.java b/src/main/java/org/qortal/controller/TransactionImporter.java index 5c70f369..6c846f3b 100644 --- a/src/main/java/org/qortal/controller/TransactionImporter.java +++ b/src/main/java/org/qortal/controller/TransactionImporter.java @@ -47,6 +47,9 @@ public class TransactionImporter extends Thread { /** Map of recent invalid unconfirmed transactions. Key is base58 transaction signature, value is do-not-request expiry timestamp. */ private final Map invalidUnconfirmedTransactions = Collections.synchronizedMap(new HashMap<>()); + /** Cached list of unconfirmed transactions, used when counting per creator. This is replaced regularly */ + public static List unconfirmedTransactionsCache = null; + public static synchronized TransactionImporter getInstance() { if (instance == null) { @@ -215,12 +218,6 @@ public class TransactionImporter extends Thread { LOGGER.debug("Finished validating signatures in incoming transactions queue (valid this round: {}, total pending import: {})...", validatedCount, sigValidTransactions.size()); } - if (!newlyValidSignatures.isEmpty()) { - LOGGER.debug("Broadcasting {} newly valid signatures ahead of import", newlyValidSignatures.size()); - Message newTransactionSignatureMessage = new TransactionSignaturesMessage(newlyValidSignatures); - Network.getInstance().broadcast(broadcastPeer -> newTransactionSignatureMessage); - } - } catch (DataException e) { LOGGER.error("Repository issue while processing incoming transactions", e); } @@ -254,6 +251,15 @@ public class TransactionImporter extends Thread { int processedCount = 0; try (final Repository repository = RepositoryManager.getRepository()) { + // Use a single copy of the unconfirmed transactions list for each cycle, to speed up constant lookups + // when counting unconfirmed transactions by creator. + List unconfirmedTransactions = repository.getTransactionRepository().getUnconfirmedTransactions(); + unconfirmedTransactions.removeIf(t -> t.getType() == Transaction.TransactionType.CHAT); + unconfirmedTransactionsCache = unconfirmedTransactions; + + // A list of signatures were imported in this round + List newlyImportedSignatures = new ArrayList<>(); + // Import transactions with valid signatures try { for (int i = 0; i < sigValidTransactions.size(); ++i) { @@ -286,6 +292,15 @@ public class TransactionImporter extends Thread { case OK: { LOGGER.debug(() -> String.format("Imported %s transaction %s", transactionData.getType().name(), Base58.encode(transactionData.getSignature()))); + + // Add to the unconfirmed transactions cache + if (transactionData.getType() != Transaction.TransactionType.CHAT && unconfirmedTransactionsCache != null) { + unconfirmedTransactionsCache.add(transactionData); + } + + // Signature imported in this round + newlyImportedSignatures.add(transactionData.getSignature()); + break; } @@ -314,9 +329,18 @@ public class TransactionImporter extends Thread { // Transaction has been processed, even if only to reject it removeIncomingTransaction(transactionData.getSignature()); } + + if (!newlyImportedSignatures.isEmpty()) { + LOGGER.debug("Broadcasting {} newly imported signatures", newlyImportedSignatures.size()); + Message newTransactionSignatureMessage = new TransactionSignaturesMessage(newlyImportedSignatures); + Network.getInstance().broadcast(broadcastPeer -> newTransactionSignatureMessage); + } } finally { LOGGER.debug("Finished importing {} incoming transaction{}", processedCount, (processedCount == 1 ? "" : "s")); blockchainLock.unlock(); + + // Clear the unconfirmed transaction cache so new data can be populated in the next cycle + unconfirmedTransactionsCache = null; } } catch (DataException e) { LOGGER.error("Repository issue while importing incoming transactions", e); diff --git a/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataManager.java b/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataManager.java index 1e24ae70..af4e4fc5 100644 --- a/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataManager.java +++ b/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataManager.java @@ -276,7 +276,10 @@ public class ArbitraryDataManager extends Thread { int offset = 0; while (!isStopping) { - Thread.sleep(1000L); + final int minSeconds = 3; + final int maxSeconds = 10; + final int randomSleepTime = new Random().nextInt((maxSeconds - minSeconds + 1)) + minSeconds; + Thread.sleep(randomSleepTime * 1000L); // Any arbitrary transactions we want to fetch data for? try (final Repository repository = RepositoryManager.getRepository()) { diff --git a/src/main/java/org/qortal/controller/tradebot/BitcoinACCTv3TradeBot.java b/src/main/java/org/qortal/controller/tradebot/BitcoinACCTv3TradeBot.java index 9033e717..9ab97be9 100644 --- a/src/main/java/org/qortal/controller/tradebot/BitcoinACCTv3TradeBot.java +++ b/src/main/java/org/qortal/controller/tradebot/BitcoinACCTv3TradeBot.java @@ -19,6 +19,7 @@ import org.qortal.data.transaction.MessageTransactionData; import org.qortal.group.Group; import org.qortal.repository.DataException; import org.qortal.repository.Repository; +import org.qortal.repository.RepositoryManager; import org.qortal.transaction.DeployAtTransaction; import org.qortal.transaction.MessageTransaction; import org.qortal.transaction.Transaction.ValidationResult; @@ -317,20 +318,36 @@ public class BitcoinACCTv3TradeBot implements AcctTradeBot { boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData); if (!isMessageAlreadySent) { - PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey()); - MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false); + // Do this in a new thread so caller doesn't have to wait for computeNonce() + // In the unlikely event that the transaction doesn't validate then the buy won't happen and eventually Alice's AT will be refunded + new Thread(() -> { + try (final Repository threadsRepository = RepositoryManager.getRepository()) { + PrivateKeyAccount sender = new PrivateKeyAccount(threadsRepository, tradeBotData.getTradePrivateKey()); + MessageTransaction messageTransaction = MessageTransaction.build(threadsRepository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false); - messageTransaction.computeNonce(); - messageTransaction.sign(sender); + LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient); + messageTransaction.computeNonce(); + MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData(); + LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty()); + messageTransaction.sign(sender); - // reset repository state to prevent deadlock - repository.discardChanges(); - ValidationResult result = messageTransaction.importAsUnconfirmed(); + // reset repository state to prevent deadlock + threadsRepository.discardChanges(); - if (result != ValidationResult.OK) { - LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name())); - return ResponseResult.NETWORK_ISSUE; - } + if (messageTransaction.isSignatureValid()) { + ValidationResult result = messageTransaction.importAsUnconfirmed(); + + if (result != ValidationResult.OK) { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name())); + } + } + else { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: signature invalid", messageRecipient)); + } + } catch (DataException e) { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, e.getMessage())); + } + }, "TradeBot response").start(); } TradeBot.updateTradeBotState(repository, tradeBotData, () -> String.format("Funding P2SH-A %s. Messaged Bob. Waiting for AT-lock", p2shAddress)); @@ -548,15 +565,25 @@ public class BitcoinACCTv3TradeBot implements AcctTradeBot { PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey()); MessageTransaction outgoingMessageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, outgoingMessageData, false, false); + LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", outgoingMessageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient); outgoingMessageTransaction.computeNonce(); + MessageTransactionData newMessageTransactionData = (MessageTransactionData) outgoingMessageTransaction.getTransactionData(); + LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), outgoingMessageTransaction.getPoWDifficulty()); outgoingMessageTransaction.sign(sender); // reset repository state to prevent deadlock repository.discardChanges(); - ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed(); - if (result != ValidationResult.OK) { - LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name())); + if (outgoingMessageTransaction.isSignatureValid()) { + ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed(); + + if (result != ValidationResult.OK) { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name())); + return; + } + } + else { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient)); return; } } @@ -668,15 +695,25 @@ public class BitcoinACCTv3TradeBot implements AcctTradeBot { PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey()); MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false); + LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient); messageTransaction.computeNonce(); + MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData(); + LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty()); messageTransaction.sign(sender); // Reset repository state to prevent deadlock repository.discardChanges(); - ValidationResult result = messageTransaction.importAsUnconfirmed(); - if (result != ValidationResult.OK) { - LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name())); + if (messageTransaction.isSignatureValid()) { + ValidationResult result = messageTransaction.importAsUnconfirmed(); + + if (result != ValidationResult.OK) { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name())); + return; + } + } + else { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient)); return; } } diff --git a/src/main/java/org/qortal/controller/tradebot/DigibyteACCTv3TradeBot.java b/src/main/java/org/qortal/controller/tradebot/DigibyteACCTv3TradeBot.java index 171e818b..4b1ba7bb 100644 --- a/src/main/java/org/qortal/controller/tradebot/DigibyteACCTv3TradeBot.java +++ b/src/main/java/org/qortal/controller/tradebot/DigibyteACCTv3TradeBot.java @@ -19,6 +19,7 @@ import org.qortal.data.transaction.MessageTransactionData; import org.qortal.group.Group; import org.qortal.repository.DataException; import org.qortal.repository.Repository; +import org.qortal.repository.RepositoryManager; import org.qortal.transaction.DeployAtTransaction; import org.qortal.transaction.MessageTransaction; import org.qortal.transaction.Transaction.ValidationResult; @@ -317,20 +318,36 @@ public class DigibyteACCTv3TradeBot implements AcctTradeBot { boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData); if (!isMessageAlreadySent) { - PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey()); - MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false); + // Do this in a new thread so caller doesn't have to wait for computeNonce() + // In the unlikely event that the transaction doesn't validate then the buy won't happen and eventually Alice's AT will be refunded + new Thread(() -> { + try (final Repository threadsRepository = RepositoryManager.getRepository()) { + PrivateKeyAccount sender = new PrivateKeyAccount(threadsRepository, tradeBotData.getTradePrivateKey()); + MessageTransaction messageTransaction = MessageTransaction.build(threadsRepository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false); - messageTransaction.computeNonce(); - messageTransaction.sign(sender); + LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient); + messageTransaction.computeNonce(); + MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData(); + LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty()); + messageTransaction.sign(sender); - // reset repository state to prevent deadlock - repository.discardChanges(); - ValidationResult result = messageTransaction.importAsUnconfirmed(); + // reset repository state to prevent deadlock + threadsRepository.discardChanges(); - if (result != ValidationResult.OK) { - LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name())); - return ResponseResult.NETWORK_ISSUE; - } + if (messageTransaction.isSignatureValid()) { + ValidationResult result = messageTransaction.importAsUnconfirmed(); + + if (result != ValidationResult.OK) { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name())); + } + } + else { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: signature invalid", messageRecipient)); + } + } catch (DataException e) { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, e.getMessage())); + } + }, "TradeBot response").start(); } TradeBot.updateTradeBotState(repository, tradeBotData, () -> String.format("Funding P2SH-A %s. Messaged Bob. Waiting for AT-lock", p2shAddress)); @@ -548,15 +565,25 @@ public class DigibyteACCTv3TradeBot implements AcctTradeBot { PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey()); MessageTransaction outgoingMessageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, outgoingMessageData, false, false); + LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", outgoingMessageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient); outgoingMessageTransaction.computeNonce(); + MessageTransactionData newMessageTransactionData = (MessageTransactionData) outgoingMessageTransaction.getTransactionData(); + LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), outgoingMessageTransaction.getPoWDifficulty()); outgoingMessageTransaction.sign(sender); // reset repository state to prevent deadlock repository.discardChanges(); - ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed(); - if (result != ValidationResult.OK) { - LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name())); + if (outgoingMessageTransaction.isSignatureValid()) { + ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed(); + + if (result != ValidationResult.OK) { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name())); + return; + } + } + else { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient)); return; } } @@ -668,15 +695,25 @@ public class DigibyteACCTv3TradeBot implements AcctTradeBot { PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey()); MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false); + LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient); messageTransaction.computeNonce(); + MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData(); + LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty()); messageTransaction.sign(sender); // Reset repository state to prevent deadlock repository.discardChanges(); - ValidationResult result = messageTransaction.importAsUnconfirmed(); - if (result != ValidationResult.OK) { - LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name())); + if (messageTransaction.isSignatureValid()) { + ValidationResult result = messageTransaction.importAsUnconfirmed(); + + if (result != ValidationResult.OK) { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name())); + return; + } + } + else { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient)); return; } } diff --git a/src/main/java/org/qortal/controller/tradebot/DogecoinACCTv1TradeBot.java b/src/main/java/org/qortal/controller/tradebot/DogecoinACCTv1TradeBot.java index d37a6650..52e7bb24 100644 --- a/src/main/java/org/qortal/controller/tradebot/DogecoinACCTv1TradeBot.java +++ b/src/main/java/org/qortal/controller/tradebot/DogecoinACCTv1TradeBot.java @@ -19,6 +19,7 @@ import org.qortal.data.transaction.MessageTransactionData; import org.qortal.group.Group; import org.qortal.repository.DataException; import org.qortal.repository.Repository; +import org.qortal.repository.RepositoryManager; import org.qortal.transaction.DeployAtTransaction; import org.qortal.transaction.MessageTransaction; import org.qortal.transaction.Transaction.ValidationResult; @@ -317,20 +318,36 @@ public class DogecoinACCTv1TradeBot implements AcctTradeBot { boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData); if (!isMessageAlreadySent) { - PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey()); - MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false); + // Do this in a new thread so caller doesn't have to wait for computeNonce() + // In the unlikely event that the transaction doesn't validate then the buy won't happen and eventually Alice's AT will be refunded + new Thread(() -> { + try (final Repository threadsRepository = RepositoryManager.getRepository()) { + PrivateKeyAccount sender = new PrivateKeyAccount(threadsRepository, tradeBotData.getTradePrivateKey()); + MessageTransaction messageTransaction = MessageTransaction.build(threadsRepository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false); - messageTransaction.computeNonce(); - messageTransaction.sign(sender); + LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient); + messageTransaction.computeNonce(); + MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData(); + LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty()); + messageTransaction.sign(sender); - // reset repository state to prevent deadlock - repository.discardChanges(); - ValidationResult result = messageTransaction.importAsUnconfirmed(); + // reset repository state to prevent deadlock + threadsRepository.discardChanges(); - if (result != ValidationResult.OK) { - LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name())); - return ResponseResult.NETWORK_ISSUE; - } + if (messageTransaction.isSignatureValid()) { + ValidationResult result = messageTransaction.importAsUnconfirmed(); + + if (result != ValidationResult.OK) { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name())); + } + } + else { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: signature invalid", messageRecipient)); + } + } catch (DataException e) { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, e.getMessage())); + } + }, "TradeBot response").start(); } TradeBot.updateTradeBotState(repository, tradeBotData, () -> String.format("Funding P2SH-A %s. Messaged Bob. Waiting for AT-lock", p2shAddress)); @@ -548,15 +565,25 @@ public class DogecoinACCTv1TradeBot implements AcctTradeBot { PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey()); MessageTransaction outgoingMessageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, outgoingMessageData, false, false); + LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", outgoingMessageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient); outgoingMessageTransaction.computeNonce(); + MessageTransactionData newMessageTransactionData = (MessageTransactionData) outgoingMessageTransaction.getTransactionData(); + LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), outgoingMessageTransaction.getPoWDifficulty()); outgoingMessageTransaction.sign(sender); // reset repository state to prevent deadlock repository.discardChanges(); - ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed(); - if (result != ValidationResult.OK) { - LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name())); + if (outgoingMessageTransaction.isSignatureValid()) { + ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed(); + + if (result != ValidationResult.OK) { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name())); + return; + } + } + else { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient)); return; } } @@ -668,15 +695,25 @@ public class DogecoinACCTv1TradeBot implements AcctTradeBot { PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey()); MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false); + LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient); messageTransaction.computeNonce(); + MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData(); + LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty()); messageTransaction.sign(sender); // Reset repository state to prevent deadlock repository.discardChanges(); - ValidationResult result = messageTransaction.importAsUnconfirmed(); - if (result != ValidationResult.OK) { - LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name())); + if (messageTransaction.isSignatureValid()) { + ValidationResult result = messageTransaction.importAsUnconfirmed(); + + if (result != ValidationResult.OK) { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name())); + return; + } + } + else { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient)); return; } } diff --git a/src/main/java/org/qortal/controller/tradebot/DogecoinACCTv3TradeBot.java b/src/main/java/org/qortal/controller/tradebot/DogecoinACCTv3TradeBot.java index 996097f3..b57b9354 100644 --- a/src/main/java/org/qortal/controller/tradebot/DogecoinACCTv3TradeBot.java +++ b/src/main/java/org/qortal/controller/tradebot/DogecoinACCTv3TradeBot.java @@ -19,6 +19,7 @@ import org.qortal.data.transaction.MessageTransactionData; import org.qortal.group.Group; import org.qortal.repository.DataException; import org.qortal.repository.Repository; +import org.qortal.repository.RepositoryManager; import org.qortal.transaction.DeployAtTransaction; import org.qortal.transaction.MessageTransaction; import org.qortal.transaction.Transaction.ValidationResult; @@ -317,20 +318,36 @@ public class DogecoinACCTv3TradeBot implements AcctTradeBot { boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData); if (!isMessageAlreadySent) { - PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey()); - MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false); + // Do this in a new thread so caller doesn't have to wait for computeNonce() + // In the unlikely event that the transaction doesn't validate then the buy won't happen and eventually Alice's AT will be refunded + new Thread(() -> { + try (final Repository threadsRepository = RepositoryManager.getRepository()) { + PrivateKeyAccount sender = new PrivateKeyAccount(threadsRepository, tradeBotData.getTradePrivateKey()); + MessageTransaction messageTransaction = MessageTransaction.build(threadsRepository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false); - messageTransaction.computeNonce(); - messageTransaction.sign(sender); + LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient); + messageTransaction.computeNonce(); + MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData(); + LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty()); + messageTransaction.sign(sender); - // reset repository state to prevent deadlock - repository.discardChanges(); - ValidationResult result = messageTransaction.importAsUnconfirmed(); + // reset repository state to prevent deadlock + threadsRepository.discardChanges(); - if (result != ValidationResult.OK) { - LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name())); - return ResponseResult.NETWORK_ISSUE; - } + if (messageTransaction.isSignatureValid()) { + ValidationResult result = messageTransaction.importAsUnconfirmed(); + + if (result != ValidationResult.OK) { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name())); + } + } + else { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: signature invalid", messageRecipient)); + } + } catch (DataException e) { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, e.getMessage())); + } + }, "TradeBot response").start(); } TradeBot.updateTradeBotState(repository, tradeBotData, () -> String.format("Funding P2SH-A %s. Messaged Bob. Waiting for AT-lock", p2shAddress)); @@ -548,15 +565,25 @@ public class DogecoinACCTv3TradeBot implements AcctTradeBot { PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey()); MessageTransaction outgoingMessageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, outgoingMessageData, false, false); + LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", outgoingMessageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient); outgoingMessageTransaction.computeNonce(); + MessageTransactionData newMessageTransactionData = (MessageTransactionData) outgoingMessageTransaction.getTransactionData(); + LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), outgoingMessageTransaction.getPoWDifficulty()); outgoingMessageTransaction.sign(sender); // reset repository state to prevent deadlock repository.discardChanges(); - ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed(); - if (result != ValidationResult.OK) { - LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name())); + if (outgoingMessageTransaction.isSignatureValid()) { + ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed(); + + if (result != ValidationResult.OK) { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name())); + return; + } + } + else { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient)); return; } } @@ -668,15 +695,25 @@ public class DogecoinACCTv3TradeBot implements AcctTradeBot { PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey()); MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false); + LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient); messageTransaction.computeNonce(); + MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData(); + LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty()); messageTransaction.sign(sender); // Reset repository state to prevent deadlock repository.discardChanges(); - ValidationResult result = messageTransaction.importAsUnconfirmed(); - if (result != ValidationResult.OK) { - LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name())); + if (messageTransaction.isSignatureValid()) { + ValidationResult result = messageTransaction.importAsUnconfirmed(); + + if (result != ValidationResult.OK) { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name())); + return; + } + } + else { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient)); return; } } diff --git a/src/main/java/org/qortal/controller/tradebot/LitecoinACCTv3TradeBot.java b/src/main/java/org/qortal/controller/tradebot/LitecoinACCTv3TradeBot.java index a4ae921e..b5631f0b 100644 --- a/src/main/java/org/qortal/controller/tradebot/LitecoinACCTv3TradeBot.java +++ b/src/main/java/org/qortal/controller/tradebot/LitecoinACCTv3TradeBot.java @@ -325,15 +325,24 @@ public class LitecoinACCTv3TradeBot implements AcctTradeBot { PrivateKeyAccount sender = new PrivateKeyAccount(threadsRepository, tradeBotData.getTradePrivateKey()); MessageTransaction messageTransaction = MessageTransaction.build(threadsRepository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false); + LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient); messageTransaction.computeNonce(); + MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData(); + LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty()); messageTransaction.sign(sender); // reset repository state to prevent deadlock threadsRepository.discardChanges(); - ValidationResult result = messageTransaction.importAsUnconfirmed(); - if (result != ValidationResult.OK) { - LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name())); + if (messageTransaction.isSignatureValid()) { + ValidationResult result = messageTransaction.importAsUnconfirmed(); + + if (result != ValidationResult.OK) { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name())); + } + } + else { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: signature invalid", messageRecipient)); } } catch (DataException e) { LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, e.getMessage())); @@ -556,15 +565,25 @@ public class LitecoinACCTv3TradeBot implements AcctTradeBot { PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey()); MessageTransaction outgoingMessageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, outgoingMessageData, false, false); + LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", outgoingMessageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient); outgoingMessageTransaction.computeNonce(); + MessageTransactionData newMessageTransactionData = (MessageTransactionData) outgoingMessageTransaction.getTransactionData(); + LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), outgoingMessageTransaction.getPoWDifficulty()); outgoingMessageTransaction.sign(sender); // reset repository state to prevent deadlock repository.discardChanges(); - ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed(); - if (result != ValidationResult.OK) { - LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name())); + if (outgoingMessageTransaction.isSignatureValid()) { + ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed(); + + if (result != ValidationResult.OK) { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name())); + return; + } + } + else { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient)); return; } } @@ -676,15 +695,25 @@ public class LitecoinACCTv3TradeBot implements AcctTradeBot { PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey()); MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false); + LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient); messageTransaction.computeNonce(); + MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData(); + LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty()); messageTransaction.sign(sender); // Reset repository state to prevent deadlock repository.discardChanges(); - ValidationResult result = messageTransaction.importAsUnconfirmed(); - if (result != ValidationResult.OK) { - LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name())); + if (messageTransaction.isSignatureValid()) { + ValidationResult result = messageTransaction.importAsUnconfirmed(); + + if (result != ValidationResult.OK) { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name())); + return; + } + } + else { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient)); return; } } diff --git a/src/main/java/org/qortal/controller/tradebot/PirateChainACCTv3TradeBot.java b/src/main/java/org/qortal/controller/tradebot/PirateChainACCTv3TradeBot.java index 9834df20..4b5126d9 100644 --- a/src/main/java/org/qortal/controller/tradebot/PirateChainACCTv3TradeBot.java +++ b/src/main/java/org/qortal/controller/tradebot/PirateChainACCTv3TradeBot.java @@ -20,6 +20,7 @@ import org.qortal.data.transaction.MessageTransactionData; import org.qortal.group.Group; import org.qortal.repository.DataException; import org.qortal.repository.Repository; +import org.qortal.repository.RepositoryManager; import org.qortal.transaction.DeployAtTransaction; import org.qortal.transaction.MessageTransaction; import org.qortal.transaction.Transaction.ValidationResult; @@ -320,20 +321,36 @@ public class PirateChainACCTv3TradeBot implements AcctTradeBot { boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData); if (!isMessageAlreadySent) { - PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey()); - MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false); + // Do this in a new thread so caller doesn't have to wait for computeNonce() + // In the unlikely event that the transaction doesn't validate then the buy won't happen and eventually Alice's AT will be refunded + new Thread(() -> { + try (final Repository threadsRepository = RepositoryManager.getRepository()) { + PrivateKeyAccount sender = new PrivateKeyAccount(threadsRepository, tradeBotData.getTradePrivateKey()); + MessageTransaction messageTransaction = MessageTransaction.build(threadsRepository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false); - messageTransaction.computeNonce(); - messageTransaction.sign(sender); + LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient); + messageTransaction.computeNonce(); + MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData(); + LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty()); + messageTransaction.sign(sender); - // reset repository state to prevent deadlock - repository.discardChanges(); - ValidationResult result = messageTransaction.importAsUnconfirmed(); + // reset repository state to prevent deadlock + threadsRepository.discardChanges(); - if (result != ValidationResult.OK) { - LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name())); - return ResponseResult.NETWORK_ISSUE; - } + if (messageTransaction.isSignatureValid()) { + ValidationResult result = messageTransaction.importAsUnconfirmed(); + + if (result != ValidationResult.OK) { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name())); + } + } + else { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: signature invalid", messageRecipient)); + } + } catch (DataException e) { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, e.getMessage())); + } + }, "TradeBot response").start(); } TradeBot.updateTradeBotState(repository, tradeBotData, () -> String.format("Funding P2SH-A %s. Messaged Bob. Waiting for AT-lock", p2shAddressT3)); @@ -561,15 +578,25 @@ public class PirateChainACCTv3TradeBot implements AcctTradeBot { PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey()); MessageTransaction outgoingMessageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, outgoingMessageData, false, false); + LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", outgoingMessageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient); outgoingMessageTransaction.computeNonce(); + MessageTransactionData newMessageTransactionData = (MessageTransactionData) outgoingMessageTransaction.getTransactionData(); + LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), outgoingMessageTransaction.getPoWDifficulty()); outgoingMessageTransaction.sign(sender); // reset repository state to prevent deadlock repository.discardChanges(); - ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed(); - if (result != ValidationResult.OK) { - LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name())); + if (outgoingMessageTransaction.isSignatureValid()) { + ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed(); + + if (result != ValidationResult.OK) { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name())); + return; + } + } + else { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient)); return; } } @@ -681,15 +708,25 @@ public class PirateChainACCTv3TradeBot implements AcctTradeBot { PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey()); MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false); + LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient); messageTransaction.computeNonce(); + MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData(); + LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty()); messageTransaction.sign(sender); // Reset repository state to prevent deadlock repository.discardChanges(); - ValidationResult result = messageTransaction.importAsUnconfirmed(); - if (result != ValidationResult.OK) { - LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name())); + if (messageTransaction.isSignatureValid()) { + ValidationResult result = messageTransaction.importAsUnconfirmed(); + + if (result != ValidationResult.OK) { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name())); + return; + } + } + else { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient)); return; } } diff --git a/src/main/java/org/qortal/controller/tradebot/RavencoinACCTv3TradeBot.java b/src/main/java/org/qortal/controller/tradebot/RavencoinACCTv3TradeBot.java index 80fe7932..ed71d0e3 100644 --- a/src/main/java/org/qortal/controller/tradebot/RavencoinACCTv3TradeBot.java +++ b/src/main/java/org/qortal/controller/tradebot/RavencoinACCTv3TradeBot.java @@ -19,6 +19,7 @@ import org.qortal.data.transaction.MessageTransactionData; import org.qortal.group.Group; import org.qortal.repository.DataException; import org.qortal.repository.Repository; +import org.qortal.repository.RepositoryManager; import org.qortal.transaction.DeployAtTransaction; import org.qortal.transaction.MessageTransaction; import org.qortal.transaction.Transaction.ValidationResult; @@ -317,20 +318,36 @@ public class RavencoinACCTv3TradeBot implements AcctTradeBot { boolean isMessageAlreadySent = repository.getMessageRepository().exists(tradeBotData.getTradeNativePublicKey(), messageRecipient, messageData); if (!isMessageAlreadySent) { - PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey()); - MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false); + // Do this in a new thread so caller doesn't have to wait for computeNonce() + // In the unlikely event that the transaction doesn't validate then the buy won't happen and eventually Alice's AT will be refunded + new Thread(() -> { + try (final Repository threadsRepository = RepositoryManager.getRepository()) { + PrivateKeyAccount sender = new PrivateKeyAccount(threadsRepository, tradeBotData.getTradePrivateKey()); + MessageTransaction messageTransaction = MessageTransaction.build(threadsRepository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false); - messageTransaction.computeNonce(); - messageTransaction.sign(sender); + LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient); + messageTransaction.computeNonce(); + MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData(); + LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty()); + messageTransaction.sign(sender); - // reset repository state to prevent deadlock - repository.discardChanges(); - ValidationResult result = messageTransaction.importAsUnconfirmed(); + // reset repository state to prevent deadlock + threadsRepository.discardChanges(); - if (result != ValidationResult.OK) { - LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name())); - return ResponseResult.NETWORK_ISSUE; - } + if (messageTransaction.isSignatureValid()) { + ValidationResult result = messageTransaction.importAsUnconfirmed(); + + if (result != ValidationResult.OK) { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, result.name())); + } + } + else { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: signature invalid", messageRecipient)); + } + } catch (DataException e) { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to Bob's trade-bot %s: %s", messageRecipient, e.getMessage())); + } + }, "TradeBot response").start(); } TradeBot.updateTradeBotState(repository, tradeBotData, () -> String.format("Funding P2SH-A %s. Messaged Bob. Waiting for AT-lock", p2shAddress)); @@ -548,15 +565,25 @@ public class RavencoinACCTv3TradeBot implements AcctTradeBot { PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey()); MessageTransaction outgoingMessageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, outgoingMessageData, false, false); + LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", outgoingMessageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient); outgoingMessageTransaction.computeNonce(); + MessageTransactionData newMessageTransactionData = (MessageTransactionData) outgoingMessageTransaction.getTransactionData(); + LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), outgoingMessageTransaction.getPoWDifficulty()); outgoingMessageTransaction.sign(sender); // reset repository state to prevent deadlock repository.discardChanges(); - ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed(); - if (result != ValidationResult.OK) { - LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name())); + if (outgoingMessageTransaction.isSignatureValid()) { + ValidationResult result = outgoingMessageTransaction.importAsUnconfirmed(); + + if (result != ValidationResult.OK) { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name())); + return; + } + } + else { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient)); return; } } @@ -668,15 +695,25 @@ public class RavencoinACCTv3TradeBot implements AcctTradeBot { PrivateKeyAccount sender = new PrivateKeyAccount(repository, tradeBotData.getTradePrivateKey()); MessageTransaction messageTransaction = MessageTransaction.build(repository, sender, Group.NO_GROUP, messageRecipient, messageData, false, false); + LOGGER.info("Computing nonce at difficulty {} for AT {} and recipient {}", messageTransaction.getPoWDifficulty(), tradeBotData.getAtAddress(), messageRecipient); messageTransaction.computeNonce(); + MessageTransactionData newMessageTransactionData = (MessageTransactionData) messageTransaction.getTransactionData(); + LOGGER.info("Computed nonce {} at difficulty {}", newMessageTransactionData.getNonce(), messageTransaction.getPoWDifficulty()); messageTransaction.sign(sender); // Reset repository state to prevent deadlock repository.discardChanges(); - ValidationResult result = messageTransaction.importAsUnconfirmed(); - if (result != ValidationResult.OK) { - LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name())); + if (messageTransaction.isSignatureValid()) { + ValidationResult result = messageTransaction.importAsUnconfirmed(); + + if (result != ValidationResult.OK) { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: %s", messageRecipient, result.name())); + return; + } + } + else { + LOGGER.warn(() -> String.format("Unable to send MESSAGE to AT %s: signature invalid", messageRecipient)); return; } } diff --git a/src/main/java/org/qortal/controller/tradebot/TradeBot.java b/src/main/java/org/qortal/controller/tradebot/TradeBot.java index 96eeaf36..e17f642f 100644 --- a/src/main/java/org/qortal/controller/tradebot/TradeBot.java +++ b/src/main/java/org/qortal/controller/tradebot/TradeBot.java @@ -333,7 +333,7 @@ public class TradeBot implements Listener { SysTray.getInstance().showMessage("Trade-Bot", String.format("%s: %s", tradeBotData.getAtAddress(), newState), MessageType.INFO); if (logMessageSupplier != null) - LOGGER.info(logMessageSupplier); + LOGGER.info(logMessageSupplier.get()); LOGGER.debug(() -> String.format("new state for trade-bot entry based on AT %s: %s", tradeBotData.getAtAddress(), newState)); @@ -712,30 +712,16 @@ public class TradeBot implements Listener { } try { - List 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 transactions = repository.getTransactionRepository().getUnconfirmedTransactions(Arrays.asList(Transaction.TransactionType.MESSAGE), null, null, null, null); - List 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) { diff --git a/src/main/java/org/qortal/crosschain/Bitcoin.java b/src/main/java/org/qortal/crosschain/Bitcoin.java index b65bac8e..7925dd4e 100644 --- a/src/main/java/org/qortal/crosschain/Bitcoin.java +++ b/src/main/java/org/qortal/crosschain/Bitcoin.java @@ -44,89 +44,78 @@ public class Bitcoin extends Bitcoiny { @Override public Collection 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 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 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 diff --git a/src/main/java/org/qortal/crosschain/Digibyte.java b/src/main/java/org/qortal/crosschain/Digibyte.java index c5d96383..8e316173 100644 --- a/src/main/java/org/qortal/crosschain/Digibyte.java +++ b/src/main/java/org/qortal/crosschain/Digibyte.java @@ -43,14 +43,17 @@ public class Digibyte extends Bitcoiny { @Override public Collection 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 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 diff --git a/src/main/java/org/qortal/crosschain/Dogecoin.java b/src/main/java/org/qortal/crosschain/Dogecoin.java index 99f557a5..93941c41 100644 --- a/src/main/java/org/qortal/crosschain/Dogecoin.java +++ b/src/main/java/org/qortal/crosschain/Dogecoin.java @@ -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 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 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 diff --git a/src/main/java/org/qortal/crosschain/Litecoin.java b/src/main/java/org/qortal/crosschain/Litecoin.java index 1dd9037a..22825c74 100644 --- a/src/main/java/org/qortal/crosschain/Litecoin.java +++ b/src/main/java/org/qortal/crosschain/Litecoin.java @@ -43,22 +43,21 @@ public class Litecoin extends Bitcoiny { @Override public Collection 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 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 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 diff --git a/src/main/java/org/qortal/crosschain/PirateChain.java b/src/main/java/org/qortal/crosschain/PirateChain.java index a1d31a4e..4881c8bb 100644 --- a/src/main/java/org/qortal/crosschain/PirateChain.java +++ b/src/main/java/org/qortal/crosschain/PirateChain.java @@ -56,11 +56,14 @@ public class PirateChain extends Bitcoiny { @Override public Collection 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 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 diff --git a/src/main/java/org/qortal/crosschain/Ravencoin.java b/src/main/java/org/qortal/crosschain/Ravencoin.java index 6030fa50..51b65f68 100644 --- a/src/main/java/org/qortal/crosschain/Ravencoin.java +++ b/src/main/java/org/qortal/crosschain/Ravencoin.java @@ -43,19 +43,19 @@ public class Ravencoin extends Bitcoiny { @Override public Collection 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 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 diff --git a/src/main/java/org/qortal/data/transaction/TransactionData.java b/src/main/java/org/qortal/data/transaction/TransactionData.java index c4a115df..21628bb9 100644 --- a/src/main/java/org/qortal/data/transaction/TransactionData.java +++ b/src/main/java/org/qortal/data/transaction/TransactionData.java @@ -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); } - -} +} \ No newline at end of file diff --git a/src/main/java/org/qortal/network/Network.java b/src/main/java/org/qortal/network/Network.java index a3528a66..b42ab450 100644 --- a/src/main/java/org/qortal/network/Network.java +++ b/src/main/java/org/qortal/network/Network.java @@ -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 immutableOutboundHandshakedPeers = Collections.emptyList(); // always rebuilt from mutable, synced list above + /** + * Count threads per message type in order to enforce limits + */ + private final Map 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 selfPeers = new ArrayList<>(); private String bindAddress = null; @@ -240,6 +255,16 @@ public class Network { private static final Network INSTANCE = new Network(); } + public Map 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) { diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java index c73c80ac..2ef44371 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java @@ -1057,5 +1057,4 @@ public class HSQLDBDatabaseUpdates { LOGGER.info(() -> String.format("HSQLDB repository updated to version %d", databaseVersion + 1)); return true; } - -} +} \ No newline at end of file diff --git a/src/main/java/org/qortal/settings/Settings.java b/src/main/java/org/qortal/settings/Settings.java index ac9b8857..babef614 100644 --- a/src/main/java/org/qortal/settings/Settings.java +++ b/src/main/java/org/qortal/settings/Settings.java @@ -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; @@ -147,7 +147,7 @@ public class Settings { private int blockCacheSize = 10; /** Maximum number of transactions for the block minter to include in a block */ - private int maxTransactionsPerBlock = 25; + private int maxTransactionsPerBlock = 50; /** How long to keep old, full, AT state data (ms). */ private long atStatesMaxLifetime = 5 * 24 * 60 * 60 * 1000L; // milliseconds @@ -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 */ @@ -227,10 +223,10 @@ public class Settings { private int maxRetries = 2; /** The number of seconds of no activity before recovery mode begins */ - public long recoveryModeTimeout = 24 * 60 * 60 * 1000L; + public long recoveryModeTimeout = 9999999999999L; /** Minimum peer version number required in order to sync with them */ - private String minPeerVersion = "4.1.2"; + private String minPeerVersion = "4.3.1"; /** 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 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; + } } diff --git a/src/main/java/org/qortal/transaction/ArbitraryTransaction.java b/src/main/java/org/qortal/transaction/ArbitraryTransaction.java index de1dfaad..86a1f0e2 100644 --- a/src/main/java/org/qortal/transaction/ArbitraryTransaction.java +++ b/src/main/java/org/qortal/transaction/ArbitraryTransaction.java @@ -99,8 +99,14 @@ public class ArbitraryTransaction extends Transaction { if (this.transactionData.getFee() < 0) return ValidationResult.NEGATIVE_FEE; - // After the feature trigger, we require the fee to be sufficient if it's not 0. - // If the fee is zero, then the nonce is validated in isSignatureValid() as an alternative to a fee + // As of the mempow transaction updates timestamp, a nonce is no longer supported, so a valid fee must be included + if (this.transactionData.getTimestamp() >= BlockChain.getInstance().getMemPoWTransactionUpdatesTimestamp()) { + // Validate the fee + return super.isFeeValid(); + } + + // After the earlier "optional fee" feature trigger, we required the fee to be sufficient if it wasn't 0. + // If the fee was zero, then the nonce was validated in isSignatureValid() as an alternative to a fee if (this.arbitraryTransactionData.getTimestamp() >= BlockChain.getInstance().getArbitraryOptionalFeeTimestamp() && this.arbitraryTransactionData.getFee() != 0L) { return super.isFeeValid(); } @@ -225,7 +231,13 @@ public class ArbitraryTransaction extends Transaction { // Clear nonce from transactionBytes ArbitraryTransactionTransformer.clearNonce(transactionBytes); - // As of feature-trigger timestamp, we only require a nonce when the fee is zero + // As of the mempow transaction updates timestamp, a nonce is no longer supported, so a fee must be included + if (this.transactionData.getTimestamp() >= BlockChain.getInstance().getMemPoWTransactionUpdatesTimestamp()) { + // Require that the fee is a positive number. Fee checking itself is performed in isFeeValid() + return (this.arbitraryTransactionData.getFee() > 0L); + } + + // As of the earlier "optional fee" feature-trigger timestamp, we only required a nonce when the fee was zero boolean beforeFeatureTrigger = this.arbitraryTransactionData.getTimestamp() < BlockChain.getInstance().getArbitraryOptionalFeeTimestamp(); if (beforeFeatureTrigger || this.arbitraryTransactionData.getFee() == 0L) { // We only need to check nonce for recent transactions due to PoW verification overhead diff --git a/src/main/java/org/qortal/transaction/ChatTransaction.java b/src/main/java/org/qortal/transaction/ChatTransaction.java index f6e26802..3d968461 100644 --- a/src/main/java/org/qortal/transaction/ChatTransaction.java +++ b/src/main/java/org/qortal/transaction/ChatTransaction.java @@ -148,6 +148,12 @@ public class ChatTransaction extends Transaction { // Nothing to do } + @Override + public boolean isConfirmable() { + // CHAT transactions can't go into blocks + return false; + } + @Override public ValidationResult isValid() throws DataException { // Nonce checking is done via isSignatureValid() as that method is only called once per import diff --git a/src/main/java/org/qortal/transaction/MessageTransaction.java b/src/main/java/org/qortal/transaction/MessageTransaction.java index a9d3a01c..b61c3d11 100644 --- a/src/main/java/org/qortal/transaction/MessageTransaction.java +++ b/src/main/java/org/qortal/transaction/MessageTransaction.java @@ -33,7 +33,9 @@ public class MessageTransaction extends Transaction { public static final int MAX_DATA_SIZE = 4000; public static final int POW_BUFFER_SIZE = 8 * 1024 * 1024; // bytes - public static final int POW_DIFFICULTY = 14; // leading zero bits + public static final int POW_DIFFICULTY_V1 = 14; // leading zero bits + public static final int POW_DIFFICULTY_V2_CONFIRMABLE = 16; // leading zero bits + public static final int POW_DIFFICULTY_V2_UNCONFIRMABLE = 12; // leading zero bits // Properties @@ -109,7 +111,17 @@ public class MessageTransaction extends Transaction { MessageTransactionTransformer.clearNonce(transactionBytes); // Calculate nonce - this.messageTransactionData.setNonce(MemoryPoW.compute2(transactionBytes, POW_BUFFER_SIZE, POW_DIFFICULTY)); + this.messageTransactionData.setNonce(MemoryPoW.compute2(transactionBytes, POW_BUFFER_SIZE, getPoWDifficulty())); + } + + public int getPoWDifficulty() { + // The difficulty changes at the "mempow transactions updates" timestamp + if (this.transactionData.getTimestamp() >= BlockChain.getInstance().getMemPoWTransactionUpdatesTimestamp()) { + // If this message is confirmable then require a higher difficulty + return this.isConfirmable() ? POW_DIFFICULTY_V2_CONFIRMABLE : POW_DIFFICULTY_V2_UNCONFIRMABLE; + } + // Before feature trigger timestamp, so use existing difficulty value + return POW_DIFFICULTY_V1; } /** @@ -183,6 +195,18 @@ public class MessageTransaction extends Transaction { return super.hasValidReference(); } + @Override + public boolean isConfirmable() { + // After feature trigger timestamp, only messages to an AT address can confirm + if (this.transactionData.getTimestamp() >= BlockChain.getInstance().getMemPoWTransactionUpdatesTimestamp()) { + if (this.messageTransactionData.getRecipient() == null || !this.messageTransactionData.getRecipient().toUpperCase().startsWith("A")) { + // Message isn't to an AT address, so this transaction is unconfirmable + return false; + } + } + return true; + } + @Override public ValidationResult isValid() throws DataException { // Nonce checking is done via isSignatureValid() as that method is only called once per import @@ -235,7 +259,7 @@ public class MessageTransaction extends Transaction { MessageTransactionTransformer.clearNonce(transactionBytes); // Check nonce - return MemoryPoW.verify2(transactionBytes, POW_BUFFER_SIZE, POW_DIFFICULTY, nonce); + return MemoryPoW.verify2(transactionBytes, POW_BUFFER_SIZE, getPoWDifficulty(), nonce); } @Override @@ -256,6 +280,11 @@ public class MessageTransaction extends Transaction { @Override public void process() throws DataException { + // Only certain MESSAGE transactions are able to confirm + if (!this.isConfirmable()) { + throw new DataException("Unconfirmable MESSAGE transactions should never be processed"); + } + // If we have no amount then there's nothing to do if (this.messageTransactionData.getAmount() == 0L) return; @@ -280,6 +309,11 @@ public class MessageTransaction extends Transaction { @Override public void orphan() throws DataException { + // Only certain MESSAGE transactions are able to confirm + if (!this.isConfirmable()) { + throw new DataException("Unconfirmable MESSAGE transactions should never be orphaned"); + } + // If we have no amount then there's nothing to do if (this.messageTransactionData.getAmount() == 0L) return; diff --git a/src/main/java/org/qortal/transaction/PresenceTransaction.java b/src/main/java/org/qortal/transaction/PresenceTransaction.java index 8076997c..56a9f633 100644 --- a/src/main/java/org/qortal/transaction/PresenceTransaction.java +++ b/src/main/java/org/qortal/transaction/PresenceTransaction.java @@ -155,6 +155,12 @@ public class PresenceTransaction extends Transaction { // Nothing to do } + @Override + public boolean isConfirmable() { + // PRESENCE transactions can't go into blocks + return false; + } + @Override public ValidationResult isValid() throws DataException { // Nonce checking is done via isSignatureValid() as that method is only called once per import diff --git a/src/main/java/org/qortal/transaction/PublicizeTransaction.java b/src/main/java/org/qortal/transaction/PublicizeTransaction.java index 76fef00b..44f93e6e 100644 --- a/src/main/java/org/qortal/transaction/PublicizeTransaction.java +++ b/src/main/java/org/qortal/transaction/PublicizeTransaction.java @@ -7,6 +7,7 @@ import org.qortal.account.Account; import org.qortal.account.PublicKeyAccount; import org.qortal.api.resource.TransactionsResource.ConfirmationStatus; import org.qortal.asset.Asset; +import org.qortal.block.BlockChain; import org.qortal.crypto.MemoryPoW; import org.qortal.data.transaction.PublicizeTransactionData; import org.qortal.data.transaction.TransactionData; @@ -89,6 +90,12 @@ public class PublicizeTransaction extends Transaction { @Override public ValidationResult isValid() throws DataException { + // Disable completely after feature-trigger timestamp, at the same time that mempow difficulties are being increased. + // It could be enabled again in the future, but preferably with an enforced minimum fee instead of allowing a mempow nonce. + if (this.transactionData.getTimestamp() >= BlockChain.getInstance().getMemPoWTransactionUpdatesTimestamp()) { + return ValidationResult.NOT_SUPPORTED; + } + // There can be only one List signatures = this.repository.getTransactionRepository().getSignaturesMatchingCriteria( TransactionType.PUBLICIZE, diff --git a/src/main/java/org/qortal/transaction/Transaction.java b/src/main/java/org/qortal/transaction/Transaction.java index bd91f25a..e0ed1f82 100644 --- a/src/main/java/org/qortal/transaction/Transaction.java +++ b/src/main/java/org/qortal/transaction/Transaction.java @@ -13,6 +13,7 @@ import org.qortal.account.PublicKeyAccount; import org.qortal.asset.Asset; import org.qortal.block.BlockChain; import org.qortal.controller.Controller; +import org.qortal.controller.TransactionImporter; import org.qortal.crypto.Crypto; import org.qortal.data.block.BlockData; import org.qortal.data.group.GroupApprovalData; @@ -247,7 +248,8 @@ public abstract class Transaction { GROUP_APPROVAL_REQUIRED(98), ACCOUNT_NOT_TRANSFERABLE(99), INVALID_BUT_OK(999), - NOT_YET_RELEASED(1000); + NOT_YET_RELEASED(1000), + NOT_SUPPORTED(1001); public final int value; @@ -377,7 +379,7 @@ public abstract class Transaction { * @return */ public long getUnitFee(Long timestamp) { - return BlockChain.getInstance().getUnitFee(); + return BlockChain.getInstance().getUnitFeeAtTimestamp(timestamp); } /** @@ -617,7 +619,10 @@ public abstract class Transaction { } private int countUnconfirmedByCreator(PublicKeyAccount creator) throws DataException { - List unconfirmedTransactions = repository.getTransactionRepository().getUnconfirmedTransactions(); + List unconfirmedTransactions = TransactionImporter.getInstance().unconfirmedTransactionsCache; + if (unconfirmedTransactions == null) { + unconfirmedTransactions = repository.getTransactionRepository().getUnconfirmedTransactions(); + } // We exclude CHAT transactions as they never get included into blocks and // have spam/DoS prevention by requiring proof of work @@ -632,7 +637,7 @@ public abstract class Transaction { } /** - * Returns sorted, unconfirmed transactions, excluding invalid. + * Returns sorted, unconfirmed transactions, excluding invalid and unconfirmable. * * @return sorted, unconfirmed transactions * @throws DataException @@ -650,7 +655,8 @@ public abstract class Transaction { TransactionData transactionData = unconfirmedTransactionsIterator.next(); Transaction transaction = Transaction.fromData(repository, transactionData); - if (transaction.isStillValidUnconfirmed(latestBlockData.getTimestamp()) != ValidationResult.OK) + // Must be confirmable and valid + if (!transaction.isConfirmable() || transaction.isStillValidUnconfirmed(latestBlockData.getTimestamp()) != ValidationResult.OK) unconfirmedTransactionsIterator.remove(); } @@ -888,6 +894,17 @@ public abstract class Transaction { /* To be optionally overridden */ } + /** + * Returns whether transaction is 'confirmable' - i.e. is of a type that + * can be included in a block. Some transactions are 'unconfirmable' + * and therefore must remain in the mempool until they expire. + * @return + */ + public boolean isConfirmable() { + /* To be optionally overridden */ + return true; + } + /** * Returns whether transaction can be added to the blockchain. *

diff --git a/src/main/resources/blockchain.json b/src/main/resources/blockchain.json index c6151204..760da97c 100644 --- a/src/main/resources/blockchain.json +++ b/src/main/resources/blockchain.json @@ -3,8 +3,12 @@ "transactionExpiryPeriod": 86400000, "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, - "unitFee": "0.001", + "unitFees": [ + { "timestamp": 0, "fee": "0.001" }, + { "timestamp": 1692118800000, "fee": "0.01" } + ], "nameRegistrationUnitFees": [ + { "timestamp": 0, "fee": "0.001" }, { "timestamp": 1645372800000, "fee": "5" }, { "timestamp": 1651420800000, "fee": "1.25" } ], @@ -18,13 +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": 1693558800000, "rewardsByHeight": [ { "height": 1, "reward": 5.00 }, { "height": 259201, "reward": 4.75 }, diff --git a/src/main/resources/i18n/ApiError_de.properties b/src/main/resources/i18n/ApiError_de.properties index 8f5bffeb..f00c2b45 100644 --- a/src/main/resources/i18n/ApiError_de.properties +++ b/src/main/resources/i18n/ApiError_de.properties @@ -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 diff --git a/src/main/resources/i18n/ApiError_nl.properties b/src/main/resources/i18n/ApiError_nl.properties index 0501fe9a..8c8a3b09 100644 --- a/src/main/resources/i18n/ApiError_nl.properties +++ b/src/main/resources/i18n/ApiError_nl.properties @@ -4,36 +4,36 @@ # "localeLang": "nl", ### Common ### -JSON = lezen van JSON bericht gefaald +JSON = lezen van JSON bericht is mislukt -INSUFFICIENT_BALANCE = insufficient balance +INSUFFICIENT_BALANCE = onvoldoende saldo UNAUTHORIZED = ongeautoriseerde API call -REPOSITORY_ISSUE = repository fout +REPOSITORY_ISSUE = fout in repository NON_PRODUCTION = deze API call is niet toegestaan voor productiesystemen -BLOCKCHAIN_NEEDS_SYNC = blockchain dient eerst gesynchronizeerd te worden +BLOCKCHAIN_NEEDS_SYNC = blockchain dient eerst te synchronizeren -NO_TIME_SYNC = klok nog niet gesynchronizeerd +NO_TIME_SYNC = klok is nog niet gesynchronizeerd ### Validation ### -INVALID_SIGNATURE = ongeldige handtekening +INVALID_SIGNATURE = ongeldige signature INVALID_ADDRESS = ongeldig adres INVALID_PUBLIC_KEY = ongeldige public key -INVALID_DATA = ongeldige gegevens +INVALID_DATA = ongeldige data INVALID_NETWORK_ADDRESS = ongeldig netwerkadres -ADDRESS_UNKNOWN = account adres onbekend +ADDRESS_UNKNOWN = account-adres onbekend INVALID_CRITERIA = ongeldige zoekcriteria -INVALID_REFERENCE = ongeldige verwijzing +INVALID_REFERENCE = ongeldige referentie TRANSFORMATION_ERROR = JSON kon niet omgezet worden in transactie @@ -44,10 +44,10 @@ INVALID_HEIGHT = ongeldige blokhoogte CANNOT_MINT = account kan niet minten ### Blocks ### -BLOCK_UNKNOWN = blok onbekend +BLOCK_UNKNOWN = blok niet gekend ### Transactions ### -TRANSACTION_UNKNOWN = onbekende transactie +TRANSACTION_UNKNOWN = transactie niet gekend PUBLIC_KEY_NOT_FOUND = public key niet gevonden @@ -55,29 +55,29 @@ PUBLIC_KEY_NOT_FOUND = public key niet gevonden TRANSACTION_INVALID = ongeldige transactie: %s (%s) ### Naming ### -NAME_UNKNOWN = onbekende naam +NAME_UNKNOWN = naam niet gekend ### Asset ### INVALID_ASSET_ID = ongeldige asset ID INVALID_ORDER_ID = ongeldige asset order ID -ORDER_UNKNOWN = onbekende asset order ID +ORDER_UNKNOWN = niet gekende asset order ID ### Groups ### -GROUP_UNKNOWN = onbekende groep +GROUP_UNKNOWN = groep niet gekend ### Foreign Blockchain ### -FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = blockchain of ElectrumX network probleem +FOREIGN_BLOCKCHAIN_NETWORK_ISSUE = vreemde blockchain of ElectrumX networkprobleem -FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = onvoldoende saldo blockchain +FOREIGN_BLOCKCHAIN_BALANCE_ISSUE = onvoldoende saldo bij vreemde blockchain -FOREIGN_BLOCKCHAIN_TOO_SOON = nog niet gereed om de blockchain transactie uittevoeren (LockTime/median block time) +FOREIGN_BLOCKCHAIN_TOO_SOON = te vroeg om de blockchain transactie uit te sturen (LockTime/median block time) ### Trade Portal ### -ORDER_SIZE_TOO_SMALL = order bedrag te laag +ORDER_SIZE_TOO_SMALL = order-bedrag te laag ### Data ### -FILE_NOT_FOUND = file niet gevonden +FILE_NOT_FOUND = bestand niet gevonden -NO_REPLY = peer reageerd niet met data +NO_REPLY = peer reageerde niet binnen toegelaten tijd diff --git a/src/main/resources/i18n/SysTray_de.properties b/src/main/resources/i18n/SysTray_de.properties index 7fa041b3..c92130f1 100644 --- a/src/main/resources/i18n/SysTray_de.properties +++ b/src/main/resources/i18n/SysTray_de.properties @@ -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 diff --git a/src/main/resources/i18n/SysTray_nl.properties b/src/main/resources/i18n/SysTray_nl.properties index c2acb7ce..3d7de024 100644 --- a/src/main/resources/i18n/SysTray_nl.properties +++ b/src/main/resources/i18n/SysTray_nl.properties @@ -1,17 +1,17 @@ #Generated by ResourceBundle Editor (http://essiembre.github.io/eclipse-rbe/) # SysTray pop-up menu -APPLYING_UPDATE_AND_RESTARTING = Automatische update en herstart worden uitgevoerd... +APPLYING_UPDATE_AND_RESTARTING = Bezig met automatische update en herstart... AUTO_UPDATE = Automatische Update -BLOCK_HEIGHT = Block hoogte +BLOCK_HEIGHT = blok-hoogte -BLOCKS_REMAINING = blocks remaining +BLOCKS_REMAINING = overblijvende blokken -BUILD_VERSION = Versie nummer +BUILD_VERSION = Versienummer -CHECK_TIME_ACCURACY = Controleer accuraatheid van de tijd +CHECK_TIME_ACCURACY = Controleer of de tijd correct is CONNECTING = Verbinden @@ -33,16 +33,16 @@ LITE_NODE = Lite node MINTING_DISABLED = Minten is uitgeschakeld -MINTING_ENABLED = \u2714 Minten is ingeschakeld +MINTING_ENABLED = \u2714 Minten is actief OPEN_UI = Open UI -PERFORMING_DB_CHECKPOINT = De database wordt gecontroleerd... +PERFORMING_DB_CHECKPOINT = De database wordt bijgewerkt... -PERFORMING_DB_MAINTENANCE = Uitvoeren van gepland onderhoud... +PERFORMING_DB_MAINTENANCE = Bezig met gepland onderhoud... SYNCHRONIZE_CLOCK = Synchronizeer klok -SYNCHRONIZING_BLOCKCHAIN = Aan het synchronizeren +SYNCHRONIZING_BLOCKCHAIN = Bezig met synchronizeren SYNCHRONIZING_CLOCK = Klok wordt gesynchronizeerd diff --git a/src/main/resources/i18n/TransactionValidity_de.properties b/src/main/resources/i18n/TransactionValidity_de.properties index 1827482b..eab7fb9e 100644 --- a/src/main/resources/i18n/TransactionValidity_de.properties +++ b/src/main/resources/i18n/TransactionValidity_de.properties @@ -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 diff --git a/src/main/resources/i18n/TransactionValidity_nl.properties b/src/main/resources/i18n/TransactionValidity_nl.properties index 36b0fec9..f92adf72 100644 --- a/src/main/resources/i18n/TransactionValidity_nl.properties +++ b/src/main/resources/i18n/TransactionValidity_nl.properties @@ -1,28 +1,28 @@ # -ACCOUNT_ALREADY_EXISTS = account bestaat al +ACCOUNT_ALREADY_EXISTS = account bestaat reeds ACCOUNT_CANNOT_REWARD_SHARE = account kan geen beloningen delen -ADDRESS_ABOVE_RATE_LIMIT = adres heeft een waarde limiet bereikt +ADDRESS_ABOVE_RATE_LIMIT = adres heeft opgegeven limietwaarde bereikt -ADDRESS_BLOCKED = adres is geblokkeerd +ADDRESS_BLOCKED = dit adres is geblokkeerd -ALREADY_GROUP_ADMIN = groeps administrator bestaat al +ALREADY_GROUP_ADMIN = reeds gekend als groepsadministrator -ALREADY_GROUP_MEMBER = groeps lid bestaat al +ALREADY_GROUP_MEMBER = reeds gekend als groepslid ALREADY_VOTED_FOR_THAT_OPTION = reeds gestemd voor die optie -ASSET_ALREADY_EXISTS = asset bestaat al +ASSET_ALREADY_EXISTS = asset bestaat reeds ASSET_DOES_NOT_EXIST = asset bestaat niet ASSET_DOES_NOT_MATCH_AT = asset komt niet overeen met de asset van de AT -ASSET_NOT_SPENDABLE = asset is niet toerijkend +ASSET_NOT_SPENDABLE = asset kan niet uitbetaald worden -AT_ALREADY_EXISTS = AT bestaat al +AT_ALREADY_EXISTS = AT bestaat reeds AT_IS_FINISHED = AT is afgelopen @@ -38,25 +38,25 @@ BUYER_ALREADY_OWNER = koper is al de eigenaar CLOCK_NOT_SYNCED = klok is niet gesynchronizeerd -DUPLICATE_MESSAGE = dubbel adres bericht +DUPLICATE_MESSAGE = adres heeft dubbel bericht verzonden DUPLICATE_OPTION = dubbele optie GROUP_ALREADY_EXISTS = groep bestaat reeds -GROUP_APPROVAL_DECIDED = groeps goedkeuring afgewezen +GROUP_APPROVAL_DECIDED = groeps-goedkeuring afgewezen -GROUP_APPROVAL_NOT_REQUIRED = groeps goedkeuring niet vereist +GROUP_APPROVAL_NOT_REQUIRED = groeps-goedkeuring niet vereist GROUP_DOES_NOT_EXIST = groep bestaat niet GROUP_ID_MISMATCH = groeps ID komt niet overeen -GROUP_OWNER_CANNOT_LEAVE = groep eigenaar kan de groep niet verlaten +GROUP_OWNER_CANNOT_LEAVE = groep-eigenaar kan groep niet verlaten -HAVE_EQUALS_WANT = asset is gelijk aan Want-asset +HAVE_EQUALS_WANT = asset is gelijk aan Wens-asset -INCORRECT_NONCE = incorrecte PoW nonce +INCORRECT_NONCE = foutieve PoW nonce INSUFFICIENT_FEE = vergoeding te laag @@ -72,19 +72,19 @@ INVALID_AT_TYPE_LENGTH = ongeldige lengte voor AT type INVALID_BUT_OK = ongeldig maar is in orde -INVALID_CREATION_BYTES = ongeldige gecreerde bytes +INVALID_CREATION_BYTES = ongeldige creatie-bytes -INVALID_DATA_LENGTH = ongeldige data lengte +INVALID_DATA_LENGTH = ongeldige data-lengte -INVALID_DESCRIPTION_LENGTH = ongeldige lengte voor de beschrijving +INVALID_DESCRIPTION_LENGTH = ongeldige lengte voor beschrijving INVALID_GROUP_APPROVAL_THRESHOLD = ongeldige drempelwaarde voor groepsgoedkeuring -INVALID_GROUP_BLOCK_DELAY = ongeldige groep blok vertraging +INVALID_GROUP_BLOCK_DELAY = ongeldige blok-vertraging bij groepsgoedkeuring INVALID_GROUP_ID = ongeldige groep-ID -INVALID_GROUP_OWNER = ongeldige groep eigenaar +INVALID_GROUP_OWNER = ongeldige groep-eigenaar INVALID_LIFETIME = ongeldige levensduur @@ -94,27 +94,27 @@ INVALID_NAME_OWNER = ongeldige naam voor eigenaar INVALID_OPTION_LENGTH = ongeldige lengte voor opties -INVALID_OPTIONS_COUNT = ongeldige hoeveelheid opties +INVALID_OPTIONS_COUNT = ongeldig aantal opties -INVALID_ORDER_CREATOR = ongeldige gebruiker voor deze order +INVALID_ORDER_CREATOR = ongeldige order-creatie-gebruiker -INVALID_PAYMENTS_COUNT = ongeldige betalings waarde +INVALID_PAYMENTS_COUNT = ongeldig aantal betalingen INVALID_PUBLIC_KEY = ongeldige public key INVALID_QUANTITY = ongeldige hoeveelheid -INVALID_REFERENCE = ongeldige verwijzing +INVALID_REFERENCE = ongeldige referentie -INVALID_RETURN = ongeldige return +INVALID_RETURN = ongeldig resultaat -INVALID_REWARD_SHARE_PERCENT = ongeldig belonings percentage +INVALID_REWARD_SHARE_PERCENT = ongeldig belonings-deelpercentage INVALID_SELLER = ongeldige verkoper INVALID_TAGS_LENGTH = ongeldige lengte voor 'tags' -INVALID_TIMESTAMP_SIGNATURE = ongeldig tijd aanduiding +INVALID_TIMESTAMP_SIGNATURE = ongeldig tijd-aanduiding INVALID_TX_GROUP_ID = ongeldige transactiegroep-ID @@ -124,15 +124,15 @@ INVITE_UNKNOWN = onbekende groepsuitnodiging JOIN_REQUEST_EXISTS = aanvraag om lid van groep te worden bestaat al -MAXIMUM_REWARD_SHARES = limiet aan belonen voor dit account bereikt +MAXIMUM_REWARD_SHARES = maximum bereikt voor beloning-delen voor dit account -MISSING_CREATOR = ontbrekende aanmaker +MISSING_CREATOR = creator niet gekend MULTIPLE_NAMES_FORBIDDEN = het registreren van meerdere namen op een account is niet toegestaan -NAME_ALREADY_FOR_SALE = naam reeds te koop +NAME_ALREADY_FOR_SALE = naam is reeds te koop -NAME_ALREADY_REGISTERED = naam reeds geregistreerd +NAME_ALREADY_REGISTERED = naam is reeds geregistreerd NAME_BLOCKED = deze naam is geblokkeerd @@ -140,7 +140,7 @@ NAME_DOES_NOT_EXIST = naam bestaat niet NAME_NOT_FOR_SALE = naam is niet te koop -NAME_NOT_NORMALIZED = naam voldoet niet aan Unicode-vorm +NAME_NOT_NORMALIZED = naam niet in Unicode-'nomaal'-vorm NEGATIVE_AMOUNT = negatieve hoeveelheid @@ -148,9 +148,9 @@ NEGATIVE_FEE = negatieve vergoeding NEGATIVE_PRICE = negatieve prijs -NO_BALANCE = onvoldoende balans +NO_BALANCE = onvoldoende saldo -NO_BLOCKCHAIN_LOCK = geen blockchain slot +NO_BLOCKCHAIN_LOCK = blockchain op node is momenteel bezet NO_FLAG_PERMISSION = account heeft hier geen toestemming voor @@ -176,9 +176,9 @@ POLL_OPTION_DOES_NOT_EXIST = peilingsoptie bestaat niet PUBLIC_KEY_UNKNOWN = public key onbekend -REWARD_SHARE_UNKNOWN = beloning vergoeding onbekend +REWARD_SHARE_UNKNOWN = beloningsdeel is onbekend -SELF_SHARE_EXISTS = zelf vergoeding bestaat reeds +SELF_SHARE_EXISTS = zelf-beloning (belonings-delen) bestaat reeds TIMESTAMP_TOO_NEW = tijdstempel te nieuw @@ -186,10 +186,10 @@ TIMESTAMP_TOO_OLD = tijdstempel te oud TOO_MANY_UNCONFIRMED = account heeft te veel onbevestigde transacties in afwachting -TRANSACTION_ALREADY_CONFIRMED = transactie is reeds bevestigd +TRANSACTION_ALREADY_CONFIRMED = transactie werd reeds bevestigd -TRANSACTION_ALREADY_EXISTS = transactie bestaat al +TRANSACTION_ALREADY_EXISTS = transactie bestaat reeds TRANSACTION_UNKNOWN = transactie onbekend -TX_GROUP_ID_MISMATCH = groep ID komt niet overeen +TX_GROUP_ID_MISMATCH = groep-ID komt niet overeen diff --git a/src/test/java/org/qortal/test/MemoryPoWTests.java b/src/test/java/org/qortal/test/MemoryPoWTests.java index 662fab19..3b0045e5 100644 --- a/src/test/java/org/qortal/test/MemoryPoWTests.java +++ b/src/test/java/org/qortal/test/MemoryPoWTests.java @@ -3,6 +3,8 @@ package org.qortal.test; import org.junit.Ignore; import org.junit.Test; import org.qortal.crypto.MemoryPoW; +import org.qortal.repository.DataException; +import org.qortal.test.common.Common; import static org.junit.Assert.*; @@ -39,13 +41,14 @@ public class MemoryPoWTests { } @Test - public void testMultipleComputes() { + public void testMultipleComputes() throws DataException { + Common.useDefaultSettings(); Random random = new Random(); - final int sampleSize = 20; + final int sampleSize = 10; final long stddevDivisor = sampleSize * (sampleSize - 1); - for (int difficulty = 8; difficulty < 16; difficulty += 2) { + for (int difficulty = 8; difficulty <= 16; difficulty++) { byte[] data = new byte[256]; long[] times = new long[sampleSize]; diff --git a/src/test/java/org/qortal/test/MessageTests.java b/src/test/java/org/qortal/test/MessageTests.java index f08c7b2f..c76c715e 100644 --- a/src/test/java/org/qortal/test/MessageTests.java +++ b/src/test/java/org/qortal/test/MessageTests.java @@ -1,5 +1,6 @@ package org.qortal.test; +import org.apache.commons.lang3.reflect.FieldUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -14,12 +15,9 @@ import org.qortal.group.Group.ApprovalThreshold; import org.qortal.repository.DataException; import org.qortal.repository.Repository; import org.qortal.repository.RepositoryManager; -import org.qortal.test.common.BlockUtils; -import org.qortal.test.common.Common; -import org.qortal.test.common.GroupUtils; -import org.qortal.test.common.TestAccount; -import org.qortal.test.common.TransactionUtils; +import org.qortal.test.common.*; import org.qortal.test.common.transaction.TestTransaction; +import org.qortal.transaction.DeployAtTransaction; import org.qortal.transaction.MessageTransaction; import org.qortal.transaction.Transaction; import org.qortal.transaction.Transaction.TransactionType; @@ -31,6 +29,7 @@ import org.qortal.utils.NTP; import static org.junit.Assert.*; +import java.util.List; import java.util.Random; public class MessageTests extends Common { @@ -85,7 +84,7 @@ public class MessageTests extends Common { byte[] randomReference = new byte[64]; random.nextBytes(randomReference); - long minimumFee = BlockChain.getInstance().getUnitFee(); + long minimumFee = BlockChain.getInstance().getUnitFeeAtTimestamp(System.currentTimeMillis()); try (final Repository repository = RepositoryManager.getRepository()) { PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); @@ -139,7 +138,7 @@ public class MessageTests extends Common { } @Test - public void withRecipentWithAmount() throws DataException { + public void withRecipientWithAmount() throws DataException { testMessage(Group.NO_GROUP, recipient, 123L, Asset.QORT); } @@ -153,6 +152,140 @@ public class MessageTests extends Common { testMessage(1, null, 0L, null); } + @Test + public void atRecipientNoFeeWithNonce() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); + String atRecipient = deployAt(); + MessageTransaction transaction = testFeeNonce(repository, false, true, atRecipient, true); + + // Transaction should be confirmable because it's to an AT, and therefore should be present in a block + assertTrue(transaction.isConfirmable()); + TransactionUtils.signAndMint(repository, transaction.getTransactionData(), alice); + assertTrue(isTransactionConfirmed(repository, transaction)); + assertEquals(16, transaction.getPoWDifficulty()); + + BlockUtils.orphanLastBlock(repository); + } + } + + @Test + public void regularRecipientNoFeeWithNonce() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); + + // Transaction should not be present in db yet + List messageTransactionsData = repository.getMessageRepository().getMessagesByParticipants(null, recipient, null, null, null); + assertTrue(messageTransactionsData.isEmpty()); + + MessageTransaction transaction = testFeeNonce(repository, false, true, recipient, true); + + // Transaction shouldn't be confirmable because it's not to an AT, and therefore shouldn't be present in a block + assertFalse(transaction.isConfirmable()); + TransactionUtils.signAndMint(repository, transaction.getTransactionData(), alice); + assertFalse(isTransactionConfirmed(repository, transaction)); + assertEquals(12, transaction.getPoWDifficulty()); + + // Transaction should be found when trade bot searches for it + messageTransactionsData = repository.getMessageRepository().getMessagesByParticipants(null, recipient, null, null, null); + assertEquals(1, messageTransactionsData.size()); + + BlockUtils.orphanLastBlock(repository); + } + } + + @Test + public void noRecipientNoFeeWithNonce() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); + + MessageTransaction transaction = testFeeNonce(repository, false, true, null, true); + + // Transaction shouldn't be confirmable because it's not to an AT, and therefore shouldn't be present in a block + assertFalse(transaction.isConfirmable()); + TransactionUtils.signAndMint(repository, transaction.getTransactionData(), alice); + assertFalse(isTransactionConfirmed(repository, transaction)); + assertEquals(12, transaction.getPoWDifficulty()); + + BlockUtils.orphanLastBlock(repository); + } + } + + @Test + public void atRecipientWithFeeNoNonce() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); + String atRecipient = deployAt(); + MessageTransaction transaction = testFeeNonce(repository, true, false, atRecipient, true); + + // Transaction should be confirmable because it's to an AT, and therefore should be present in a block + assertTrue(transaction.isConfirmable()); + TransactionUtils.signAndMint(repository, transaction.getTransactionData(), alice); + assertTrue(isTransactionConfirmed(repository, transaction)); + assertEquals(16, transaction.getPoWDifficulty()); + + BlockUtils.orphanLastBlock(repository); + } + } + + @Test + public void regularRecipientWithFeeNoNonce() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); + + MessageTransaction transaction = testFeeNonce(repository, true, false, recipient, true); + + // Transaction shouldn't be confirmable because it's not to an AT, and therefore shouldn't be present in a block + assertFalse(transaction.isConfirmable()); + TransactionUtils.signAndMint(repository, transaction.getTransactionData(), alice); + assertFalse(isTransactionConfirmed(repository, transaction)); + assertEquals(12, transaction.getPoWDifficulty()); + + BlockUtils.orphanLastBlock(repository); + } + } + + @Test + public void atRecipientNoFeeWithNonceLegacyDifficulty() throws DataException, IllegalAccessException { + try (final Repository repository = RepositoryManager.getRepository()) { + + // Set mempowTransactionUpdatesTimestamp to a high value, so that it hasn't activated key + FieldUtils.writeField(BlockChain.getInstance(), "mempowTransactionUpdatesTimestamp", Long.MAX_VALUE, true); + + PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); + String atRecipient = deployAt(); + MessageTransaction transaction = testFeeNonce(repository, false, true, atRecipient, true); + + // Transaction should be confirmable because all MESSAGE transactions confirmed prior to the feature trigger + assertTrue(transaction.isConfirmable()); + TransactionUtils.signAndMint(repository, transaction.getTransactionData(), alice); + assertTrue(isTransactionConfirmed(repository, transaction)); + assertEquals(14, transaction.getPoWDifficulty()); // Legacy difficulty was 14 in all cases + + BlockUtils.orphanLastBlock(repository); + } + } + + @Test + public void regularRecipientNoFeeWithNonceLegacyDifficulty() throws DataException, IllegalAccessException { + try (final Repository repository = RepositoryManager.getRepository()) { + + // Set mempowTransactionUpdatesTimestamp to a high value, so that it hasn't activated key + FieldUtils.writeField(BlockChain.getInstance(), "mempowTransactionUpdatesTimestamp", Long.MAX_VALUE, true); + + PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); + MessageTransaction transaction = testFeeNonce(repository, false, true, recipient, true); + + // Transaction should be confirmable because all MESSAGE transactions confirmed prior to the feature trigger + assertTrue(transaction.isConfirmable()); + TransactionUtils.signAndMint(repository, transaction.getTransactionData(), alice); + assertTrue(isTransactionConfirmed(repository, transaction)); // All MESSAGE transactions would confirm before feature trigger + assertEquals(14, transaction.getPoWDifficulty()); // Legacy difficulty was 14 in all cases + + BlockUtils.orphanLastBlock(repository); + } + } + @Test public void serializationTests() throws DataException, TransformationException { // with recipient, with amount @@ -165,6 +298,24 @@ public class MessageTests extends Common { testSerialization(null, 0L, null); } + private String deployAt() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + PrivateKeyAccount deployer = Common.getTestAccount(repository, "alice"); + byte[] creationBytes = AtUtils.buildSimpleAT(); + long fundingAmount = 1_00000000L; + DeployAtTransaction deployAtTransaction = AtUtils.doDeployAT(repository, deployer, creationBytes, fundingAmount); + + String address = deployAtTransaction.getATAccount().getAddress(); + assertNotNull(address); + return address; + } + } + + private boolean isTransactionConfirmed(Repository repository, MessageTransaction transaction) throws DataException { + TransactionData queriedTransactionData = repository.getTransactionRepository().fromSignature(transaction.getTransactionData().getSignature()); + return queriedTransactionData.getBlockHeight() != null && queriedTransactionData.getBlockHeight() > 0; + } + private boolean isValid(int txGroupId, String recipient, long amount, Long assetId) throws DataException { try (final Repository repository = RepositoryManager.getRepository()) { TestAccount alice = Common.getTestAccount(repository, "alice"); @@ -195,41 +346,48 @@ public class MessageTests extends Common { return messageTransaction.hasValidReference(); } - private void testFeeNonce(boolean withFee, boolean withNonce, boolean isValid) throws DataException { + + private MessageTransaction testFeeNonce(boolean withFee, boolean withNonce, boolean isValid) throws DataException { try (final Repository repository = RepositoryManager.getRepository()) { - TestAccount alice = Common.getTestAccount(repository, "alice"); - - int txGroupId = 0; - int nonce = 0; - long amount = 0; - long assetId = Asset.QORT; - byte[] data = new byte[1]; - boolean isText = false; - boolean isEncrypted = false; - - MessageTransactionData transactionData = new MessageTransactionData(TestTransaction.generateBase(alice, txGroupId), - version, nonce, recipient, amount, assetId, data, isText, isEncrypted); - - MessageTransaction transaction = new MessageTransaction(repository, transactionData); - - if (withFee) - transactionData.setFee(transaction.calcRecommendedFee()); - else - transactionData.setFee(0L); - - if (withNonce) { - transaction.computeNonce(); - } else { - transactionData.setNonce(-1); - } - - transaction.sign(alice); - - assertEquals(isValid, transaction.isSignatureValid()); + return testFeeNonce(repository, withFee, withNonce, recipient, isValid); } } - private void testMessage(int txGroupId, String recipient, long amount, Long assetId) throws DataException { + private MessageTransaction testFeeNonce(Repository repository, boolean withFee, boolean withNonce, String recipient, boolean isValid) throws DataException { + TestAccount alice = Common.getTestAccount(repository, "alice"); + + int txGroupId = 0; + int nonce = 0; + long amount = 0; + long assetId = Asset.QORT; + byte[] data = new byte[1]; + boolean isText = false; + boolean isEncrypted = false; + + MessageTransactionData transactionData = new MessageTransactionData(TestTransaction.generateBase(alice, txGroupId), + version, nonce, recipient, amount, assetId, data, isText, isEncrypted); + + MessageTransaction transaction = new MessageTransaction(repository, transactionData); + + if (withFee) + transactionData.setFee(transaction.calcRecommendedFee()); + else + transactionData.setFee(0L); + + if (withNonce) { + transaction.computeNonce(); + } else { + transactionData.setNonce(-1); + } + + transaction.sign(alice); + + assertEquals(isValid, transaction.isSignatureValid()); + + return transaction; + } + + private MessageTransaction testMessage(int txGroupId, String recipient, long amount, Long assetId) throws DataException { try (final Repository repository = RepositoryManager.getRepository()) { TestAccount alice = Common.getTestAccount(repository, "alice"); @@ -244,6 +402,8 @@ public class MessageTests extends Common { TransactionUtils.signAndMint(repository, transactionData, alice); BlockUtils.orphanLastBlock(repository); + + return new MessageTransaction(repository, transactionData); } } diff --git a/src/test/java/org/qortal/test/arbitrary/ArbitraryTransactionMetadataTests.java b/src/test/java/org/qortal/test/arbitrary/ArbitraryTransactionMetadataTests.java index 47c68b25..9ac73166 100644 --- a/src/test/java/org/qortal/test/arbitrary/ArbitraryTransactionMetadataTests.java +++ b/src/test/java/org/qortal/test/arbitrary/ArbitraryTransactionMetadataTests.java @@ -11,6 +11,7 @@ import org.qortal.arbitrary.ArbitraryDataReader; import org.qortal.arbitrary.exception.MissingDataException; import org.qortal.arbitrary.misc.Category; import org.qortal.arbitrary.misc.Service; +import org.qortal.block.BlockChain; import org.qortal.controller.arbitrary.ArbitraryDataManager; import org.qortal.data.arbitrary.ArbitraryResourceMetadata; import org.qortal.data.transaction.ArbitraryTransactionData; @@ -24,6 +25,7 @@ import org.qortal.test.common.TransactionUtils; import org.qortal.test.common.transaction.TestTransaction; import org.qortal.transaction.RegisterNameTransaction; import org.qortal.utils.Base58; +import org.qortal.utils.NTP; import java.io.IOException; import java.nio.file.Files; @@ -106,8 +108,9 @@ public class ArbitraryTransactionMetadataTests extends Common { // Create PUT transaction Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength); + long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime()); ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name, - identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, 0L, true, + identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, fee, false, title, description, tags, category); // Check the chunk count is correct @@ -157,8 +160,9 @@ public class ArbitraryTransactionMetadataTests extends Common { // Create PUT transaction Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength); + long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime()); ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name, - identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, 0L, true, + identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, fee, false, title, description, tags, category); // Check the chunk count is correct @@ -220,8 +224,9 @@ public class ArbitraryTransactionMetadataTests extends Common { // Create PUT transaction Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength); + long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime()); ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name, - identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, 0L, true, + identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, fee, false, title, description, tags, category); // Check the chunk count is correct @@ -272,8 +277,9 @@ public class ArbitraryTransactionMetadataTests extends Common { // Create PUT transaction Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength); + long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime()); ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name, - identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, 0L, true, + identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, fee, false, title, description, tags, category); // Check the chunk count is correct @@ -316,8 +322,9 @@ public class ArbitraryTransactionMetadataTests extends Common { // Create PUT transaction Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength); + long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime()); ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name, - identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, 0L, true, + identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, fee, false, title, description, tags, category); // Check the metadata is correct diff --git a/src/test/java/org/qortal/test/arbitrary/ArbitraryTransactionTests.java b/src/test/java/org/qortal/test/arbitrary/ArbitraryTransactionTests.java index 855aeafd..089f0475 100644 --- a/src/test/java/org/qortal/test/arbitrary/ArbitraryTransactionTests.java +++ b/src/test/java/org/qortal/test/arbitrary/ArbitraryTransactionTests.java @@ -10,6 +10,7 @@ import org.qortal.arbitrary.ArbitraryDataTransactionBuilder; import org.qortal.arbitrary.exception.MissingDataException; import org.qortal.arbitrary.misc.Category; import org.qortal.arbitrary.misc.Service; +import org.qortal.block.BlockChain; import org.qortal.controller.arbitrary.ArbitraryDataManager; import org.qortal.crypto.Crypto; import org.qortal.data.PaymentData; @@ -50,51 +51,6 @@ public class ArbitraryTransactionTests extends Common { Common.useDefaultSettings(); } - @Test - public void testDifficultyTooLow() throws IllegalAccessException, DataException, IOException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); - String publicKey58 = Base58.encode(alice.getPublicKey()); - String name = "TEST"; // Can be anything for this test - String identifier = null; // Not used for this test - Service service = Service.ARBITRARY_DATA; - int chunkSize = 100; - int dataLength = 900; // Actual data length will be longer due to encryption - - // Register the name to Alice - RegisterNameTransactionData registerNameTransactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, ""); - registerNameTransactionData.setFee(new RegisterNameTransaction(null, null).getUnitFee(registerNameTransactionData.getTimestamp())); - TransactionUtils.signAndMint(repository, registerNameTransactionData, alice); - - // Set difficulty to 1 - FieldUtils.writeField(ArbitraryDataManager.getInstance(), "powDifficulty", 1, true); - - // Create PUT transaction - Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength); - ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name, identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize); - - // Check that nonce validation succeeds - byte[] signature = arbitraryDataFile.getSignature(); - TransactionData transactionData = repository.getTransactionRepository().fromSignature(signature); - ArbitraryTransaction transaction = new ArbitraryTransaction(repository, transactionData); - assertTrue(transaction.isSignatureValid()); - - // Increase difficulty to 15 - FieldUtils.writeField(ArbitraryDataManager.getInstance(), "powDifficulty", 15, true); - - // Make sure the nonce validation fails - // Note: there is a very tiny chance this could succeed due to being extremely lucky - // and finding a high difficulty nonce in the first couple of cycles. It will be rare - // enough that we shouldn't need to account for it. - assertFalse(transaction.isSignatureValid()); - - // Reduce difficulty back to 1, to double check - FieldUtils.writeField(ArbitraryDataManager.getInstance(), "powDifficulty", 1, true); - assertTrue(transaction.isSignatureValid()); - - } - } - @Test public void testNonceAndFee() throws IllegalAccessException, DataException, IOException { try (final Repository repository = RepositoryManager.getRepository()) { @@ -497,8 +453,9 @@ public class ArbitraryTransactionTests extends Common { // Create PUT transaction Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength, true); + long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime()); ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name, - identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, 0L, true, + identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, fee, false, null, null, null, null); byte[] signature = arbitraryDataFile.getSignature(); @@ -556,8 +513,9 @@ public class ArbitraryTransactionTests extends Common { // Create PUT transaction Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength, true); + long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime()); ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name, - identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, 0L, true, + identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, fee, false, title, description, tags, category); byte[] signature = arbitraryDataFile.getSignature(); @@ -614,8 +572,9 @@ public class ArbitraryTransactionTests extends Common { // Create PUT transaction Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength, true); + long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime()); ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name, - identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, 0L, true, + identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, fee, false, null, null, null, null); byte[] signature = arbitraryDataFile.getSignature(); diff --git a/src/test/java/org/qortal/test/common/ArbitraryUtils.java b/src/test/java/org/qortal/test/common/ArbitraryUtils.java index 1741d22c..e08eb0ac 100644 --- a/src/test/java/org/qortal/test/common/ArbitraryUtils.java +++ b/src/test/java/org/qortal/test/common/ArbitraryUtils.java @@ -5,10 +5,12 @@ import org.qortal.arbitrary.ArbitraryDataFile; import org.qortal.arbitrary.ArbitraryDataTransactionBuilder; import org.qortal.arbitrary.misc.Category; import org.qortal.arbitrary.misc.Service; +import org.qortal.block.BlockChain; import org.qortal.data.transaction.ArbitraryTransactionData; import org.qortal.repository.DataException; import org.qortal.repository.Repository; import org.qortal.transaction.Transaction; +import org.qortal.utils.NTP; import java.io.BufferedWriter; import java.io.File; @@ -20,16 +22,15 @@ import java.nio.file.Paths; import java.util.List; import java.util.Random; -import static org.junit.Assert.assertEquals; - public class ArbitraryUtils { public static ArbitraryDataFile createAndMintTxn(Repository repository, String publicKey58, Path path, String name, String identifier, ArbitraryTransactionData.Method method, Service service, PrivateKeyAccount account, int chunkSize) throws DataException { + long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime()); return ArbitraryUtils.createAndMintTxn(repository, publicKey58, path, name, identifier, method, service, - account, chunkSize, 0L, true, null, null, null, null); + account, chunkSize, fee, false, null, null, null, null); } public static ArbitraryDataFile createAndMintTxn(Repository repository, String publicKey58, Path path, String name, String identifier, @@ -47,7 +48,9 @@ public class ArbitraryUtils { } ArbitraryTransactionData transactionData = txnBuilder.getArbitraryTransactionData(); Transaction.ValidationResult result = TransactionUtils.signAndImport(repository, transactionData, account); - assertEquals(Transaction.ValidationResult.OK, result); + if (result != Transaction.ValidationResult.OK) { + throw new DataException(String.format("Arbitrary transaction invalid: %s", result.toString())); + } BlockUtils.mintBlock(repository); // We need a new ArbitraryDataFile instance because the files will have been moved to the signature's folder diff --git a/src/test/java/org/qortal/test/common/transaction/TestTransaction.java b/src/test/java/org/qortal/test/common/transaction/TestTransaction.java index 11fdf58e..b580ecd3 100644 --- a/src/test/java/org/qortal/test/common/transaction/TestTransaction.java +++ b/src/test/java/org/qortal/test/common/transaction/TestTransaction.java @@ -7,13 +7,15 @@ import org.qortal.block.BlockChain; import org.qortal.data.transaction.BaseTransactionData; import org.qortal.group.Group; import org.qortal.repository.DataException; +import org.qortal.utils.NTP; public abstract class TestTransaction { protected static final Random random = new Random(); public static BaseTransactionData generateBase(PrivateKeyAccount account, int txGroupId) throws DataException { - return new BaseTransactionData(System.currentTimeMillis(), txGroupId, account.getLastReference(), account.getPublicKey(), BlockChain.getInstance().getUnitFee(), null); + long timestamp = System.currentTimeMillis(); + return new BaseTransactionData(timestamp, txGroupId, account.getLastReference(), account.getPublicKey(), BlockChain.getInstance().getUnitFeeAtTimestamp(timestamp), null); } public static BaseTransactionData generateBase(PrivateKeyAccount account) throws DataException { diff --git a/src/test/java/org/qortal/test/crosschain/ACCTTests.java b/src/test/java/org/qortal/test/crosschain/ACCTTests.java new file mode 100644 index 00000000..6af27a96 --- /dev/null +++ b/src/test/java/org/qortal/test/crosschain/ACCTTests.java @@ -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 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 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 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"); + } +} \ No newline at end of file diff --git a/src/test/java/org/qortal/test/crosschain/BitcoinTests.java b/src/test/java/org/qortal/test/crosschain/BitcoinTests.java index 1096d7ad..684f1cd6 100644 --- a/src/test/java/org/qortal/test/crosschain/BitcoinTests.java +++ b/src/test/java/org/qortal/test/crosschain/BitcoinTests.java @@ -1,121 +1,59 @@ package org.qortal.test.crosschain; -import static org.junit.Assert.*; - -import java.util.Arrays; - -import org.bitcoinj.core.Transaction; -import org.bitcoinj.store.BlockStoreException; -import org.junit.After; -import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.qortal.crosschain.Bitcoin; -import org.qortal.crosschain.ForeignBlockchainException; -import org.qortal.crosschain.BitcoinyHTLC; -import org.qortal.repository.DataException; -import org.qortal.test.common.Common; +import org.qortal.crosschain.Bitcoiny; -public class BitcoinTests extends Common { +public class BitcoinTests extends BitcoinyTests { - private Bitcoin bitcoin; - - @Before - public void beforeTest() throws DataException { - Common.useDefaultSettings(); // TestNet3 - bitcoin = Bitcoin.getInstance(); + @Override + protected String getCoinName() { + return "Bitcoin"; } - @After - public void afterTest() { + @Override + protected String getCoinSymbol() { + return "BTC"; + } + + @Override + protected Bitcoiny getCoin() { + return Bitcoin.getInstance(); + } + + @Override + protected void resetCoinForTesting() { Bitcoin.resetForTesting(); - bitcoin = null; + } + + @Override + protected String getDeterministicKey58() { + return "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ"; + } + + @Override + protected String getRecipient() { + return "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE"; } @Test @Ignore("Often fails due to unreliable BTC testnet ElectrumX servers") - public void testGetMedianBlockTime() throws BlockStoreException, ForeignBlockchainException { - System.out.println(String.format("Starting BTC instance...")); - System.out.println(String.format("BTC instance started")); - - long before = System.currentTimeMillis(); - System.out.println(String.format("Bitcoin median blocktime: %d", bitcoin.getMedianBlockTime())); - long afterFirst = System.currentTimeMillis(); - - System.out.println(String.format("Bitcoin median blocktime: %d", bitcoin.getMedianBlockTime())); - long afterSecond = System.currentTimeMillis(); - - long firstPeriod = afterFirst - before; - long secondPeriod = afterSecond - afterFirst; - - System.out.println(String.format("1st call: %d ms, 2nd call: %d ms", firstPeriod, secondPeriod)); - - assertTrue("2nd call should be quicker than 1st", secondPeriod < firstPeriod); - assertTrue("2nd call should take less than 5 seconds", secondPeriod < 5000L); - } + public void testGetMedianBlockTime() {} @Test @Ignore("Often fails due to unreliable BTC testnet ElectrumX servers") - public void testFindHtlcSecret() throws ForeignBlockchainException { - // This actually exists on TEST3 but can take a while to fetch - String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE"; - - byte[] expectedSecret = "This string is exactly 32 bytes!".getBytes(); - byte[] secret = BitcoinyHTLC.findHtlcSecret(bitcoin, p2shAddress); - - assertNotNull(secret); - assertTrue("secret incorrect", Arrays.equals(expectedSecret, secret)); - } + public void testFindHtlcSecret() {} @Test @Ignore("Often fails due to unreliable BTC testnet ElectrumX servers") - public void testBuildSpend() { - String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ"; - - String recipient = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE"; - long amount = 1000L; - - Transaction transaction = bitcoin.buildSpend(xprv58, recipient, amount); - assertNotNull(transaction); - - // Check spent key caching doesn't affect outcome - - transaction = bitcoin.buildSpend(xprv58, recipient, amount); - assertNotNull(transaction); - } + public void testBuildSpend() {} @Test @Ignore("Often fails due to unreliable BTC testnet ElectrumX servers") - public void testGetWalletBalance() throws ForeignBlockchainException { - String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ"; - - Long balance = bitcoin.getWalletBalance(xprv58); - - assertNotNull(balance); - - System.out.println(bitcoin.format(balance)); - - // Check spent key caching doesn't affect outcome - - Long repeatBalance = bitcoin.getWalletBalance(xprv58); - - assertNotNull(repeatBalance); - - System.out.println(bitcoin.format(repeatBalance)); - - assertEquals(balance, repeatBalance); - } + public void testGetWalletBalance() {} @Test @Ignore("Often fails due to unreliable BTC testnet ElectrumX servers") - public void testGetUnusedReceiveAddress() throws ForeignBlockchainException { - String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ"; - - String address = bitcoin.getUnusedReceiveAddress(xprv58); - - assertNotNull(address); - - System.out.println(address); - } - + public void testGetUnusedReceiveAddress() {} } diff --git a/src/test/java/org/qortal/test/crosschain/BitcoinyTests.java b/src/test/java/org/qortal/test/crosschain/BitcoinyTests.java new file mode 100644 index 00000000..b29fffd4 --- /dev/null +++ b/src/test/java/org/qortal/test/crosschain/BitcoinyTests.java @@ -0,0 +1,130 @@ +package org.qortal.test.crosschain; + +import org.bitcoinj.core.Transaction; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.qortal.crosschain.Bitcoiny; +import org.qortal.crosschain.BitcoinyHTLC; +import org.qortal.crosschain.ForeignBlockchainException; +import org.qortal.repository.DataException; +import org.qortal.test.common.Common; + +import java.util.Arrays; + +import static org.junit.Assert.*; + +public abstract class BitcoinyTests extends Common { + + protected Bitcoiny bitcoiny; + + protected abstract String getCoinName(); + + protected abstract String getCoinSymbol(); + + protected abstract Bitcoiny getCoin(); + + protected abstract void resetCoinForTesting(); + + protected abstract String getDeterministicKey58(); + + protected abstract String getRecipient(); + + @Before + public void beforeTest() throws DataException { + Common.useDefaultSettings(); // TestNet3 + bitcoiny = getCoin(); + } + + @After + public void afterTest() { + resetCoinForTesting(); + bitcoiny = null; + } + + @Test + public void testGetMedianBlockTime() throws ForeignBlockchainException { + System.out.println(String.format("Starting " + getCoinSymbol() + " instance...")); + System.out.println(String.format(getCoinSymbol() + " instance started")); + + long before = System.currentTimeMillis(); + System.out.println(String.format(getCoinName() + " median blocktime: %d", bitcoiny.getMedianBlockTime())); + long afterFirst = System.currentTimeMillis(); + + System.out.println(String.format(getCoinName() + " median blocktime: %d", bitcoiny.getMedianBlockTime())); + long afterSecond = System.currentTimeMillis(); + + long firstPeriod = afterFirst - before; + long secondPeriod = afterSecond - afterFirst; + + System.out.println(String.format("1st call: %d ms, 2nd call: %d ms", firstPeriod, secondPeriod)); + + makeGetMedianBlockTimeAssertions(firstPeriod, secondPeriod); + } + + public void makeGetMedianBlockTimeAssertions(long firstPeriod, long secondPeriod) { + assertTrue("2nd call should be quicker than 1st", secondPeriod < firstPeriod); + assertTrue("2nd call should take less than 5 seconds", secondPeriod < 5000L); + } + + @Test + public void testFindHtlcSecret() throws ForeignBlockchainException { + // This actually exists on TEST3 but can take a while to fetch + String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE"; + + byte[] expectedSecret = "This string is exactly 32 bytes!".getBytes(); + byte[] secret = BitcoinyHTLC.findHtlcSecret(bitcoiny, p2shAddress); + + assertNotNull(secret); + assertTrue("secret incorrect", Arrays.equals(expectedSecret, secret)); + } + + @Test + public void testBuildSpend() { + String xprv58 = getDeterministicKey58(); + + String recipient = getRecipient(); + long amount = 1000L; + + Transaction transaction = bitcoiny.buildSpend(xprv58, recipient, amount); + assertNotNull(transaction); + + // Check spent key caching doesn't affect outcome + + transaction = bitcoiny.buildSpend(xprv58, recipient, amount); + assertNotNull(transaction); + } + + @Test + public void testGetWalletBalance() throws ForeignBlockchainException { + String xprv58 = getDeterministicKey58(); + + Long balance = bitcoiny.getWalletBalance(xprv58); + + assertNotNull(balance); + + System.out.println(bitcoiny.format(balance)); + + // Check spent key caching doesn't affect outcome + + Long repeatBalance = bitcoiny.getWalletBalance(xprv58); + + assertNotNull(repeatBalance); + + System.out.println(bitcoiny.format(repeatBalance)); + + assertEquals(balance, repeatBalance); + } + + @Test + public void testGetUnusedReceiveAddress() throws ForeignBlockchainException { + String xprv58 = getDeterministicKey58(); + + String address = bitcoiny.getUnusedReceiveAddress(xprv58); + + assertNotNull(address); + + System.out.println(address); + } + +} diff --git a/src/test/java/org/qortal/test/crosschain/DigibyteTests.java b/src/test/java/org/qortal/test/crosschain/DigibyteTests.java index dbe81c82..d95f1bd5 100644 --- a/src/test/java/org/qortal/test/crosschain/DigibyteTests.java +++ b/src/test/java/org/qortal/test/crosschain/DigibyteTests.java @@ -1,115 +1,48 @@ package org.qortal.test.crosschain; -import static org.junit.Assert.*; - -import java.util.Arrays; - -import org.bitcoinj.core.Transaction; -import org.bitcoinj.store.BlockStoreException; -import org.junit.After; -import org.junit.Before; import org.junit.Ignore; import org.junit.Test; -import org.qortal.crosschain.ForeignBlockchainException; +import org.qortal.crosschain.Bitcoiny; import org.qortal.crosschain.Digibyte; -import org.qortal.crosschain.BitcoinyHTLC; -import org.qortal.repository.DataException; -import org.qortal.test.common.Common; -public class DigibyteTests extends Common { +public class DigibyteTests extends BitcoinyTests { - private Digibyte digibyte; - - @Before - public void beforeTest() throws DataException { - Common.useDefaultSettings(); // TestNet3 - digibyte = Digibyte.getInstance(); + @Override + protected String getCoinName() { + return "Digibyte"; } - @After - public void afterTest() { + @Override + protected String getCoinSymbol() { + return "DGB"; + } + + @Override + protected Bitcoiny getCoin() { + return Digibyte.getInstance(); + } + + @Override + protected void resetCoinForTesting() { Digibyte.resetForTesting(); - digibyte = null; } - @Test - public void testGetMedianBlockTime() throws BlockStoreException, ForeignBlockchainException { - long before = System.currentTimeMillis(); - System.out.println(String.format("Digibyte median blocktime: %d", digibyte.getMedianBlockTime())); - long afterFirst = System.currentTimeMillis(); + @Override + protected String getDeterministicKey58() { + return "xpub661MyMwAqRbcEnabTLX5uebYcsE3uG5y7ve9jn1VK8iY1MaU3YLoLJEe8sTu2YVav5Zka5qf2dmMssfxmXJTqZnazZL2kL7M2tNKwEoC34R"; + } - System.out.println(String.format("Digibyte median blocktime: %d", digibyte.getMedianBlockTime())); - long afterSecond = System.currentTimeMillis(); - - long firstPeriod = afterFirst - before; - long secondPeriod = afterSecond - afterFirst; - - System.out.println(String.format("1st call: %d ms, 2nd call: %d ms", firstPeriod, secondPeriod)); - - assertTrue("2nd call should be quicker than 1st", secondPeriod < firstPeriod); - assertTrue("2nd call should take less than 5 seconds", secondPeriod < 5000L); + @Override + protected String getRecipient() { + return "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE"; } @Test @Ignore(value = "Doesn't work, to be fixed later") - public void testFindHtlcSecret() throws ForeignBlockchainException { - // This actually exists on TEST3 but can take a while to fetch - String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE"; - - byte[] expectedSecret = "This string is exactly 32 bytes!".getBytes(); - byte[] secret = BitcoinyHTLC.findHtlcSecret(digibyte, p2shAddress); - - assertNotNull("secret not found", secret); - assertTrue("secret incorrect", Arrays.equals(expectedSecret, secret)); - } + public void testFindHtlcSecret() {} @Test @Ignore(value = "No testnet nodes available, so we can't regularly test buildSpend yet") - public void testBuildSpend() { - String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ"; + public void testBuildSpend() {} - String recipient = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE"; - long amount = 1000L; - - Transaction transaction = digibyte.buildSpend(xprv58, recipient, amount); - assertNotNull("insufficient funds", transaction); - - // Check spent key caching doesn't affect outcome - - transaction = digibyte.buildSpend(xprv58, recipient, amount); - assertNotNull("insufficient funds", transaction); } - - @Test - public void testGetWalletBalance() throws ForeignBlockchainException { - String xprv58 = "xpub661MyMwAqRbcEnabTLX5uebYcsE3uG5y7ve9jn1VK8iY1MaU3YLoLJEe8sTu2YVav5Zka5qf2dmMssfxmXJTqZnazZL2kL7M2tNKwEoC34R"; - - Long balance = digibyte.getWalletBalance(xprv58); - - assertNotNull(balance); - - System.out.println(digibyte.format(balance)); - - // Check spent key caching doesn't affect outcome - - Long repeatBalance = digibyte.getWalletBalance(xprv58); - - assertNotNull(repeatBalance); - - System.out.println(digibyte.format(repeatBalance)); - - assertEquals(balance, repeatBalance); - } - - @Test - public void testGetUnusedReceiveAddress() throws ForeignBlockchainException { - String xprv58 = "xpub661MyMwAqRbcEnabTLX5uebYcsE3uG5y7ve9jn1VK8iY1MaU3YLoLJEe8sTu2YVav5Zka5qf2dmMssfxmXJTqZnazZL2kL7M2tNKwEoC34R"; - - String address = digibyte.getUnusedReceiveAddress(xprv58); - - assertNotNull(address); - - System.out.println(address); - } - -} diff --git a/src/test/java/org/qortal/test/crosschain/DogecoinTests.java b/src/test/java/org/qortal/test/crosschain/DogecoinTests.java index 6c070d09..62982437 100644 --- a/src/test/java/org/qortal/test/crosschain/DogecoinTests.java +++ b/src/test/java/org/qortal/test/crosschain/DogecoinTests.java @@ -1,115 +1,47 @@ package org.qortal.test.crosschain; -import org.bitcoinj.core.Transaction; -import org.bitcoinj.store.BlockStoreException; -import org.junit.After; -import org.junit.Before; import org.junit.Ignore; import org.junit.Test; -import org.qortal.crosschain.BitcoinyHTLC; -import org.qortal.crosschain.ForeignBlockchainException; +import org.qortal.crosschain.Bitcoiny; import org.qortal.crosschain.Dogecoin; -import org.qortal.repository.DataException; -import org.qortal.test.common.Common; -import java.util.Arrays; +public class DogecoinTests extends BitcoinyTests { -import static org.junit.Assert.*; - -public class DogecoinTests extends Common { - - private Dogecoin dogecoin; - - @Before - public void beforeTest() throws DataException { - Common.useDefaultSettings(); // TestNet3 - dogecoin = Dogecoin.getInstance(); + @Override + protected String getCoinName() { + return "Dogecoin"; } - @After - public void afterTest() { + @Override + protected String getCoinSymbol() { + return "DOGE"; + } + + @Override + protected Bitcoiny getCoin() { + return Dogecoin.getInstance(); + } + + @Override + protected void resetCoinForTesting() { Dogecoin.resetForTesting(); - dogecoin = null; } - @Test - public void testGetMedianBlockTime() throws BlockStoreException, ForeignBlockchainException { - long before = System.currentTimeMillis(); - System.out.println(String.format("Dogecoin median blocktime: %d", dogecoin.getMedianBlockTime())); - long afterFirst = System.currentTimeMillis(); + @Override + protected String getDeterministicKey58() { + return "dgpv51eADS3spNJh9drNeW1Tc1P9z2LyaQRXPBortsq6yice1k47C2u2Prvgxycr2ihNBWzKZ2LthcBBGiYkWZ69KUTVkcLVbnjq7pD8mnApEru"; + } - System.out.println(String.format("Dogecoin median blocktime: %d", dogecoin.getMedianBlockTime())); - long afterSecond = System.currentTimeMillis(); - - long firstPeriod = afterFirst - before; - long secondPeriod = afterSecond - afterFirst; - - System.out.println(String.format("1st call: %d ms, 2nd call: %d ms", firstPeriod, secondPeriod)); - - assertTrue("2nd call should be quicker than 1st", secondPeriod < firstPeriod); - assertTrue("2nd call should take less than 5 seconds", secondPeriod < 5000L); + @Override + protected String getRecipient() { + return null; } @Test @Ignore(value = "Doesn't work, to be fixed later") - public void testFindHtlcSecret() throws ForeignBlockchainException { - // This actually exists on TEST3 but can take a while to fetch - String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE"; - - byte[] expectedSecret = "This string is exactly 32 bytes!".getBytes(); - byte[] secret = BitcoinyHTLC.findHtlcSecret(dogecoin, p2shAddress); - - assertNotNull("secret not found", secret); - assertTrue("secret incorrect", Arrays.equals(expectedSecret, secret)); - } + public void testFindHtlcSecret() {} @Test @Ignore(value = "No testnet nodes available, so we can't regularly test buildSpend yet") - public void testBuildSpend() { - String xprv58 = "dgpv51eADS3spNJh9drNeW1Tc1P9z2LyaQRXPBortsq6yice1k47C2u2Prvgxycr2ihNBWzKZ2LthcBBGiYkWZ69KUTVkcLVbnjq7pD8mnApEru"; - - String recipient = "DP1iFao33xdEPa5vaArpj7sykfzKNeiJeX"; - long amount = 1000L; - - Transaction transaction = dogecoin.buildSpend(xprv58, recipient, amount); - assertNotNull("insufficient funds", transaction); - - // Check spent key caching doesn't affect outcome - - transaction = dogecoin.buildSpend(xprv58, recipient, amount); - assertNotNull("insufficient funds", transaction); - } - - @Test - public void testGetWalletBalance() throws ForeignBlockchainException { - String xprv58 = "dgpv51eADS3spNJh9drNeW1Tc1P9z2LyaQRXPBortsq6yice1k47C2u2Prvgxycr2ihNBWzKZ2LthcBBGiYkWZ69KUTVkcLVbnjq7pD8mnApEru"; - - Long balance = dogecoin.getWalletBalance(xprv58); - - assertNotNull(balance); - - System.out.println(dogecoin.format(balance)); - - // Check spent key caching doesn't affect outcome - - Long repeatBalance = dogecoin.getWalletBalance(xprv58); - - assertNotNull(repeatBalance); - - System.out.println(dogecoin.format(repeatBalance)); - - assertEquals(balance, repeatBalance); - } - - @Test - public void testGetUnusedReceiveAddress() throws ForeignBlockchainException { - String xprv58 = "dgpv51eADS3spNJh9drNeW1Tc1P9z2LyaQRXPBortsq6yice1k47C2u2Prvgxycr2ihNBWzKZ2LthcBBGiYkWZ69KUTVkcLVbnjq7pD8mnApEru"; - - String address = dogecoin.getUnusedReceiveAddress(xprv58); - - assertNotNull(address); - - System.out.println(address); - } - + public void testBuildSpend() {} } diff --git a/src/test/java/org/qortal/test/crosschain/LitecoinTests.java b/src/test/java/org/qortal/test/crosschain/LitecoinTests.java index 5ea7bc95..66c631e5 100644 --- a/src/test/java/org/qortal/test/crosschain/LitecoinTests.java +++ b/src/test/java/org/qortal/test/crosschain/LitecoinTests.java @@ -1,113 +1,43 @@ package org.qortal.test.crosschain; -import static org.junit.Assert.*; - -import java.util.Arrays; - -import org.bitcoinj.core.Transaction; -import org.junit.After; -import org.junit.Before; import org.junit.Ignore; import org.junit.Test; -import org.qortal.crosschain.ForeignBlockchainException; +import org.qortal.crosschain.Bitcoiny; import org.qortal.crosschain.Litecoin; -import org.qortal.crosschain.BitcoinyHTLC; -import org.qortal.repository.DataException; -import org.qortal.test.common.Common; -public class LitecoinTests extends Common { +public class LitecoinTests extends BitcoinyTests { - private Litecoin litecoin; - - @Before - public void beforeTest() throws DataException { - Common.useDefaultSettings(); // TestNet3 - litecoin = Litecoin.getInstance(); + @Override + protected String getCoinName() { + return "Litecoin"; } - @After - public void afterTest() { + @Override + protected String getCoinSymbol() { + return "LTC"; + } + + @Override + protected Bitcoiny getCoin() { + return Litecoin.getInstance(); + } + + @Override + protected void resetCoinForTesting() { Litecoin.resetForTesting(); - litecoin = null; } - @Test - public void testGetMedianBlockTime() throws ForeignBlockchainException { - long before = System.currentTimeMillis(); - System.out.println(String.format("Litecoin median blocktime: %d", litecoin.getMedianBlockTime())); - long afterFirst = System.currentTimeMillis(); + @Override + protected String getDeterministicKey58() { + return "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ"; + } - System.out.println(String.format("Litecoin median blocktime: %d", litecoin.getMedianBlockTime())); - long afterSecond = System.currentTimeMillis(); - - long firstPeriod = afterFirst - before; - long secondPeriod = afterSecond - afterFirst; - - System.out.println(String.format("1st call: %d ms, 2nd call: %d ms", firstPeriod, secondPeriod)); - - assertTrue("2nd call should be quicker than 1st", secondPeriod < firstPeriod); - assertTrue("2nd call should take less than 5 seconds", secondPeriod < 5000L); + @Override + protected String getRecipient() { + return "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE"; } @Test @Ignore(value = "Doesn't work, to be fixed later") - public void testFindHtlcSecret() throws ForeignBlockchainException { - // This actually exists on TEST3 but can take a while to fetch - String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE"; - - byte[] expectedSecret = "This string is exactly 32 bytes!".getBytes(); - byte[] secret = BitcoinyHTLC.findHtlcSecret(litecoin, p2shAddress); - - assertNotNull("secret not found", secret); - assertTrue("secret incorrect", Arrays.equals(expectedSecret, secret)); - } - - @Test - public void testBuildSpend() { - String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ"; - - String recipient = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE"; - long amount = 1000L; - - Transaction transaction = litecoin.buildSpend(xprv58, recipient, amount); - assertNotNull("insufficient funds", transaction); - - // Check spent key caching doesn't affect outcome - - transaction = litecoin.buildSpend(xprv58, recipient, amount); - assertNotNull("insufficient funds", transaction); - } - - @Test - public void testGetWalletBalance() throws ForeignBlockchainException { - String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ"; - - Long balance = litecoin.getWalletBalance(xprv58); - - assertNotNull(balance); - - System.out.println(litecoin.format(balance)); - - // Check spent key caching doesn't affect outcome - - Long repeatBalance = litecoin.getWalletBalance(xprv58); - - assertNotNull(repeatBalance); - - System.out.println(litecoin.format(repeatBalance)); - - assertEquals(balance, repeatBalance); - } - - @Test - public void testGetUnusedReceiveAddress() throws ForeignBlockchainException { - String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ"; - - String address = litecoin.getUnusedReceiveAddress(xprv58); - - assertNotNull(address); - - System.out.println(address); - } - + public void testFindHtlcSecret() {} } diff --git a/src/test/java/org/qortal/test/crosschain/PirateChainTests.java b/src/test/java/org/qortal/test/crosschain/PirateChainTests.java index 9502e45a..b212aea1 100644 --- a/src/test/java/org/qortal/test/crosschain/PirateChainTests.java +++ b/src/test/java/org/qortal/test/crosschain/PirateChainTests.java @@ -3,57 +3,53 @@ package org.qortal.test.crosschain; import cash.z.wallet.sdk.rpc.CompactFormats.*; import com.google.common.hash.HashCode; import com.google.common.primitives.Bytes; -import org.bitcoinj.core.Transaction; -import org.bitcoinj.store.BlockStoreException; -import org.junit.After; -import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.qortal.controller.tradebot.TradeBot; import org.qortal.crosschain.*; import org.qortal.crypto.Crypto; -import org.qortal.repository.DataException; -import org.qortal.test.common.Common; import org.qortal.transform.TransformationException; -import java.util.Arrays; import java.util.List; import static org.junit.Assert.*; import static org.qortal.crosschain.BitcoinyHTLC.Status.*; -public class PirateChainTests extends Common { +public class PirateChainTests extends BitcoinyTests { - private PirateChain pirateChain; - - @Before - public void beforeTest() throws DataException { - Common.useDefaultSettings(); - pirateChain = PirateChain.getInstance(); + @Override + protected String getCoinName() { + return "PirateChain"; } - @After - public void afterTest() { + @Override + protected String getCoinSymbol() { + return "ARRR"; + } + + @Override + protected Bitcoiny getCoin() { + return PirateChain.getInstance(); + } + + @Override + protected void resetCoinForTesting() { Litecoin.resetForTesting(); - pirateChain = null; } - @Test - public void testGetMedianBlockTime() throws BlockStoreException, ForeignBlockchainException { - long before = System.currentTimeMillis(); - System.out.println(String.format("Pirate Chain median blocktime: %d", pirateChain.getMedianBlockTime())); - long afterFirst = System.currentTimeMillis(); + @Override + protected String getDeterministicKey58() { + return null; + } - System.out.println(String.format("Pirate Chain median blocktime: %d", pirateChain.getMedianBlockTime())); - long afterSecond = System.currentTimeMillis(); + @Override + protected String getRecipient() { + return null; + } - long firstPeriod = afterFirst - before; - long secondPeriod = afterSecond - afterFirst; - - System.out.println(String.format("1st call: %d ms, 2nd call: %d ms", firstPeriod, secondPeriod)); - - assertTrue("1st call should take less than 5 seconds", firstPeriod < 5000L); - assertTrue("2nd call should take less than 5 seconds", secondPeriod < 5000L); + public void makeGetMedianBlockTimeAssertions(long firstPeriod, long secondPeriod) { + assertTrue("1st call should take less than 5 seconds", firstPeriod < 5000L); + assertTrue("2nd call should take less than 5 seconds", secondPeriod < 5000L); } @Test @@ -62,7 +58,7 @@ public class PirateChainTests extends Common { int count = 20; long before = System.currentTimeMillis(); - List compactBlocks = pirateChain.getCompactBlocks(startHeight, count); + List compactBlocks = ((PirateChain) bitcoiny).getCompactBlocks(startHeight, count); long after = System.currentTimeMillis(); System.out.println(String.format("Retrieval took: %d ms", after-before)); @@ -82,7 +78,7 @@ public class PirateChainTests extends Common { Bytes.reverse(txBytes); String txHashBE = HashCode.fromBytes(txBytes).toString(); - byte[] rawTransaction = pirateChain.getBlockchainProvider().getRawTransaction(txHashBE); + byte[] rawTransaction = bitcoiny.getBlockchainProvider().getRawTransaction(txHashBE); assertNotNull(rawTransaction); } @@ -121,7 +117,7 @@ public class PirateChainTests extends Common { String p2shAddress = "ba6Q5HWrWtmfU2WZqQbrFdRYsafA45cUAt"; long p2shFee = 10000; final long minimumAmount = 10000 + p2shFee; - BitcoinyHTLC.Status htlcStatus = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmount); + BitcoinyHTLC.Status htlcStatus = PirateChainHTLC.determineHtlcStatus(bitcoiny.getBlockchainProvider(), p2shAddress, minimumAmount); assertEquals(FUNDED, htlcStatus); } @@ -130,7 +126,7 @@ public class PirateChainTests extends Common { String p2shAddress = "bYZrzSSgGp8aEGvihukoMGU8sXYrx19Wka"; long p2shFee = 10000; final long minimumAmount = 10000 + p2shFee; - BitcoinyHTLC.Status htlcStatus = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmount); + BitcoinyHTLC.Status htlcStatus = PirateChainHTLC.determineHtlcStatus(bitcoiny.getBlockchainProvider(), p2shAddress, minimumAmount); assertEquals(REDEEMED, htlcStatus); } @@ -139,14 +135,14 @@ public class PirateChainTests extends Common { String p2shAddress = "bE49izfVxz8odhu8c2BcUaVFUnt7NLFRgv"; long p2shFee = 10000; final long minimumAmount = 10000 + p2shFee; - BitcoinyHTLC.Status htlcStatus = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmount); + BitcoinyHTLC.Status htlcStatus = PirateChainHTLC.determineHtlcStatus(bitcoiny.getBlockchainProvider(), p2shAddress, minimumAmount); assertEquals(REFUNDED, htlcStatus); } @Test public void testGetTxidForUnspentAddress() throws ForeignBlockchainException { String p2shAddress = "ba6Q5HWrWtmfU2WZqQbrFdRYsafA45cUAt"; - String txid = PirateChainHTLC.getFundingTxid(pirateChain.getBlockchainProvider(), p2shAddress); + String txid = PirateChainHTLC.getFundingTxid(bitcoiny.getBlockchainProvider(), p2shAddress); // Reverse the byte order of the txid used by block explorers, to get to big-endian form byte[] expectedTxidLE = HashCode.fromString("fea4b0c1abcf8f0f3ddc2fa2f9438501ee102aad62a9ff18a5ce7d08774755c0").asBytes(); @@ -161,7 +157,7 @@ public class PirateChainTests extends Common { String p2shAddress = "ba6Q5HWrWtmfU2WZqQbrFdRYsafA45cUAt"; long p2shFee = 10000; final long minimumAmount = 10000 + p2shFee; - String txid = PirateChainHTLC.getUnspentFundingTxid(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmount); + String txid = PirateChainHTLC.getUnspentFundingTxid(bitcoiny.getBlockchainProvider(), p2shAddress, minimumAmount); // Reverse the byte order of the txid used by block explorers, to get to big-endian form byte[] expectedTxidLE = HashCode.fromString("fea4b0c1abcf8f0f3ddc2fa2f9438501ee102aad62a9ff18a5ce7d08774755c0").asBytes(); @@ -174,7 +170,7 @@ public class PirateChainTests extends Common { @Test public void testGetTxidForSpentAddress() throws ForeignBlockchainException { String p2shAddress = "bE49izfVxz8odhu8c2BcUaVFUnt7NLFRgv"; //"t3KtVxeEb8srJofo6atMEpMpEP6TjEi8VqA"; - String txid = PirateChainHTLC.getFundingTxid(pirateChain.getBlockchainProvider(), p2shAddress); + String txid = PirateChainHTLC.getFundingTxid(bitcoiny.getBlockchainProvider(), p2shAddress); // Reverse the byte order of the txid used by block explorers, to get to big-endian form byte[] expectedTxidLE = HashCode.fromString("fb386fc8eea0fbf3ea37047726b92c39441652b32d8d62a274331687f7a1eca8").asBytes(); @@ -187,7 +183,7 @@ public class PirateChainTests extends Common { @Test public void testGetTransactionsForAddress() throws ForeignBlockchainException { String p2shAddress = "bE49izfVxz8odhu8c2BcUaVFUnt7NLFRgv"; //"t3KtVxeEb8srJofo6atMEpMpEP6TjEi8VqA"; - List transactions = pirateChain.getBlockchainProvider() + List transactions = bitcoiny.getBlockchainProvider() .getAddressBitcoinyTransactions(p2shAddress, false); assertEquals(2, transactions.size()); @@ -232,66 +228,17 @@ public class PirateChainTests extends Common { @Test @Ignore(value = "Doesn't work, to be fixed later") - public void testFindHtlcSecret() throws ForeignBlockchainException { - // This actually exists on TEST3 but can take a while to fetch - String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE"; - - byte[] expectedSecret = "This string is exactly 32 bytes!".getBytes(); - byte[] secret = BitcoinyHTLC.findHtlcSecret(pirateChain, p2shAddress); - - assertNotNull("secret not found", secret); - assertTrue("secret incorrect", Arrays.equals(expectedSecret, secret)); - } + public void testFindHtlcSecret() {} @Test @Ignore(value = "Needs adapting for Pirate Chain") - public void testBuildSpend() { - String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ"; - - String recipient = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE"; - long amount = 1000L; - - Transaction transaction = pirateChain.buildSpend(xprv58, recipient, amount); - assertNotNull("insufficient funds", transaction); - - // Check spent key caching doesn't affect outcome - - transaction = pirateChain.buildSpend(xprv58, recipient, amount); - assertNotNull("insufficient funds", transaction); - } + public void testBuildSpend() {} @Test @Ignore(value = "Needs adapting for Pirate Chain") - public void testGetWalletBalance() throws ForeignBlockchainException { - String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ"; - - Long balance = pirateChain.getWalletBalance(xprv58); - - assertNotNull(balance); - - System.out.println(pirateChain.format(balance)); - - // Check spent key caching doesn't affect outcome - - Long repeatBalance = pirateChain.getWalletBalance(xprv58); - - assertNotNull(repeatBalance); - - System.out.println(pirateChain.format(repeatBalance)); - - assertEquals(balance, repeatBalance); - } + public void testGetWalletBalance() {} @Test @Ignore(value = "Needs adapting for Pirate Chain") - public void testGetUnusedReceiveAddress() throws ForeignBlockchainException { - String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ"; - - String address = pirateChain.getUnusedReceiveAddress(xprv58); - - assertNotNull(address); - - System.out.println(address); - } - -} + public void testGetUnusedReceiveAddress() {} +} \ No newline at end of file diff --git a/src/test/java/org/qortal/test/crosschain/RavencoinTests.java b/src/test/java/org/qortal/test/crosschain/RavencoinTests.java index 866c41a2..d7581f74 100644 --- a/src/test/java/org/qortal/test/crosschain/RavencoinTests.java +++ b/src/test/java/org/qortal/test/crosschain/RavencoinTests.java @@ -1,115 +1,47 @@ package org.qortal.test.crosschain; -import static org.junit.Assert.*; - -import java.util.Arrays; - -import org.bitcoinj.core.Transaction; -import org.bitcoinj.store.BlockStoreException; -import org.junit.After; -import org.junit.Before; import org.junit.Ignore; import org.junit.Test; -import org.qortal.crosschain.ForeignBlockchainException; +import org.qortal.crosschain.Bitcoiny; import org.qortal.crosschain.Ravencoin; -import org.qortal.crosschain.BitcoinyHTLC; -import org.qortal.repository.DataException; -import org.qortal.test.common.Common; -public class RavencoinTests extends Common { +public class RavencoinTests extends BitcoinyTests { - private Ravencoin ravencoin; - - @Before - public void beforeTest() throws DataException { - Common.useDefaultSettings(); // TestNet3 - ravencoin = Ravencoin.getInstance(); + @Override + protected String getCoinName() { + return "Ravencoin"; } - @After - public void afterTest() { + @Override + protected String getCoinSymbol() { + return "RVN"; + } + + @Override + protected Bitcoiny getCoin() { + return Ravencoin.getInstance(); + } + + @Override + protected void resetCoinForTesting() { Ravencoin.resetForTesting(); - ravencoin = null; } - @Test - public void testGetMedianBlockTime() throws BlockStoreException, ForeignBlockchainException { - long before = System.currentTimeMillis(); - System.out.println(String.format("Ravencoin median blocktime: %d", ravencoin.getMedianBlockTime())); - long afterFirst = System.currentTimeMillis(); + @Override + protected String getDeterministicKey58() { + return "xpub661MyMwAqRbcEt3Ge1wNmkagyb1J7yTQu4Kquvy77Ycg2iPoh7Urg8s9Jdwp7YmrqGkDKJpUVjsZXSSsQgmAVUC17ZVQQeoWMzm7vDTt1y7"; + } - System.out.println(String.format("Ravencoin median blocktime: %d", ravencoin.getMedianBlockTime())); - long afterSecond = System.currentTimeMillis(); - - long firstPeriod = afterFirst - before; - long secondPeriod = afterSecond - afterFirst; - - System.out.println(String.format("1st call: %d ms, 2nd call: %d ms", firstPeriod, secondPeriod)); - - assertTrue("2nd call should be quicker than 1st", secondPeriod < firstPeriod); - assertTrue("2nd call should take less than 5 seconds", secondPeriod < 5000L); + @Override + protected String getRecipient() { + return null; } @Test @Ignore(value = "Doesn't work, to be fixed later") - public void testFindHtlcSecret() throws ForeignBlockchainException { - // This actually exists on TEST3 but can take a while to fetch - String p2shAddress = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE"; - - byte[] expectedSecret = "This string is exactly 32 bytes!".getBytes(); - byte[] secret = BitcoinyHTLC.findHtlcSecret(ravencoin, p2shAddress); - - assertNotNull("secret not found", secret); - assertTrue("secret incorrect", Arrays.equals(expectedSecret, secret)); - } + public void testFindHtlcSecret() {} @Test @Ignore(value = "No testnet nodes available, so we can't regularly test buildSpend yet") - public void testBuildSpend() { - String xprv58 = "tprv8ZgxMBicQKsPdahhFSrCdvC1bsWyzHHZfTneTVqUXN6s1wEtZLwAkZXzFP6TYLg2aQMecZLXLre5bTVGajEB55L1HYJcawpdFG66STVAWPJ"; - - String recipient = "2N8WCg52ULCtDSMjkgVTm5mtPdCsUptkHWE"; - long amount = 1000L; - - Transaction transaction = ravencoin.buildSpend(xprv58, recipient, amount); - assertNotNull("insufficient funds", transaction); - - // Check spent key caching doesn't affect outcome - - transaction = ravencoin.buildSpend(xprv58, recipient, amount); - assertNotNull("insufficient funds", transaction); - } - - @Test - public void testGetWalletBalance() throws ForeignBlockchainException { - String xprv58 = "xpub661MyMwAqRbcEt3Ge1wNmkagyb1J7yTQu4Kquvy77Ycg2iPoh7Urg8s9Jdwp7YmrqGkDKJpUVjsZXSSsQgmAVUC17ZVQQeoWMzm7vDTt1y7"; - - Long balance = ravencoin.getWalletBalance(xprv58); - - assertNotNull(balance); - - System.out.println(ravencoin.format(balance)); - - // Check spent key caching doesn't affect outcome - - Long repeatBalance = ravencoin.getWalletBalance(xprv58); - - assertNotNull(repeatBalance); - - System.out.println(ravencoin.format(repeatBalance)); - - assertEquals(balance, repeatBalance); - } - - @Test - public void testGetUnusedReceiveAddress() throws ForeignBlockchainException { - String xprv58 = "xpub661MyMwAqRbcEt3Ge1wNmkagyb1J7yTQu4Kquvy77Ycg2iPoh7Urg8s9Jdwp7YmrqGkDKJpUVjsZXSSsQgmAVUC17ZVQQeoWMzm7vDTt1y7"; - - String address = ravencoin.getUnusedReceiveAddress(xprv58); - - assertNotNull(address); - - System.out.println(address); - } - + public void testBuildSpend() {} } diff --git a/src/test/java/org/qortal/test/crosschain/bitcoinv1/BitcoinACCTv1Tests.java b/src/test/java/org/qortal/test/crosschain/bitcoinv1/BitcoinACCTv1Tests.java index 4487e874..cc33eb43 100644 --- a/src/test/java/org/qortal/test/crosschain/bitcoinv1/BitcoinACCTv1Tests.java +++ b/src/test/java/org/qortal/test/crosschain/bitcoinv1/BitcoinACCTv1Tests.java @@ -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 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 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 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"); - } - } diff --git a/src/test/java/org/qortal/test/crosschain/bitcoinv3/BitcoinACCTv3Tests.java b/src/test/java/org/qortal/test/crosschain/bitcoinv3/BitcoinACCTv3Tests.java index 01345727..5e0048bf 100644 --- a/src/test/java/org/qortal/test/crosschain/bitcoinv3/BitcoinACCTv3Tests.java +++ b/src/test/java/org/qortal/test/crosschain/bitcoinv3/BitcoinACCTv3Tests.java @@ -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 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 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"); - } - } diff --git a/src/test/java/org/qortal/test/crosschain/digibytev3/DigibyteACCTv3Tests.java b/src/test/java/org/qortal/test/crosschain/digibytev3/DigibyteACCTv3Tests.java index d13aba4c..01ead678 100644 --- a/src/test/java/org/qortal/test/crosschain/digibytev3/DigibyteACCTv3Tests.java +++ b/src/test/java/org/qortal/test/crosschain/digibytev3/DigibyteACCTv3Tests.java @@ -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 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 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"); - } - } diff --git a/src/test/java/org/qortal/test/crosschain/dogecoinv3/DogecoinACCTv3Tests.java b/src/test/java/org/qortal/test/crosschain/dogecoinv3/DogecoinACCTv3Tests.java index 7056e433..551173f7 100644 --- a/src/test/java/org/qortal/test/crosschain/dogecoinv3/DogecoinACCTv3Tests.java +++ b/src/test/java/org/qortal/test/crosschain/dogecoinv3/DogecoinACCTv3Tests.java @@ -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 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 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"); - } - } diff --git a/src/test/java/org/qortal/test/crosschain/litecoinv1/LitecoinACCTv1Tests.java b/src/test/java/org/qortal/test/crosschain/litecoinv1/LitecoinACCTv1Tests.java index 609ff5f3..91a450d0 100644 --- a/src/test/java/org/qortal/test/crosschain/litecoinv1/LitecoinACCTv1Tests.java +++ b/src/test/java/org/qortal/test/crosschain/litecoinv1/LitecoinACCTv1Tests.java @@ -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 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 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"); - } - } diff --git a/src/test/java/org/qortal/test/crosschain/litecoinv3/LitecoinACCTv3Tests.java b/src/test/java/org/qortal/test/crosschain/litecoinv3/LitecoinACCTv3Tests.java index 009af5ea..a1a0bfcc 100644 --- a/src/test/java/org/qortal/test/crosschain/litecoinv3/LitecoinACCTv3Tests.java +++ b/src/test/java/org/qortal/test/crosschain/litecoinv3/LitecoinACCTv3Tests.java @@ -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 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 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"); - } - } diff --git a/src/test/java/org/qortal/test/crosschain/piratechainv3/PirateChainACCTv3Tests.java b/src/test/java/org/qortal/test/crosschain/piratechainv3/PirateChainACCTv3Tests.java index f9ac9de1..18099872 100644 --- a/src/test/java/org/qortal/test/crosschain/piratechainv3/PirateChainACCTv3Tests.java +++ b/src/test/java/org/qortal/test/crosschain/piratechainv3/PirateChainACCTv3Tests.java @@ -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 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 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"); - } - } diff --git a/src/test/java/org/qortal/test/crosschain/ravencoinv3/RavencoinACCTv3Tests.java b/src/test/java/org/qortal/test/crosschain/ravencoinv3/RavencoinACCTv3Tests.java index 012d5f5d..1af0f7d6 100644 --- a/src/test/java/org/qortal/test/crosschain/ravencoinv3/RavencoinACCTv3Tests.java +++ b/src/test/java/org/qortal/test/crosschain/ravencoinv3/RavencoinACCTv3Tests.java @@ -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 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 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"); - } - } diff --git a/src/test/java/org/qortal/test/naming/BuySellTests.java b/src/test/java/org/qortal/test/naming/BuySellTests.java index 4530820e..f0e97b94 100644 --- a/src/test/java/org/qortal/test/naming/BuySellTests.java +++ b/src/test/java/org/qortal/test/naming/BuySellTests.java @@ -44,7 +44,7 @@ public class BuySellTests extends Common { bob = Common.getTestAccount(repository, "bob"); name = "test name" + " " + random.nextInt(1_000_000); - price = random.nextInt(1000) * Amounts.MULTIPLIER; + price = (random.nextInt(1000) + 1) * Amounts.MULTIPLIER; } @After diff --git a/src/test/java/org/qortal/test/naming/MiscTests.java b/src/test/java/org/qortal/test/naming/MiscTests.java index 2bcd098d..401b03b9 100644 --- a/src/test/java/org/qortal/test/naming/MiscTests.java +++ b/src/test/java/org/qortal/test/naming/MiscTests.java @@ -20,6 +20,7 @@ import org.qortal.repository.Repository; import org.qortal.repository.RepositoryManager; import org.qortal.test.common.*; import org.qortal.test.common.transaction.TestTransaction; +import org.qortal.transaction.PaymentTransaction; import org.qortal.transaction.RegisterNameTransaction; import org.qortal.transaction.Transaction; import org.qortal.transaction.Transaction.ValidationResult; @@ -329,15 +330,19 @@ public class MiscTests extends Common { public void testRegisterNameFeeIncrease() throws Exception { try (final Repository repository = RepositoryManager.getRepository()) { - // Set nameRegistrationUnitFeeTimestamp to a time far in the future + // Add original fee to nameRegistrationUnitFees + UnitFeesByTimestamp originalFee = new UnitFeesByTimestamp(); + originalFee.timestamp = 0L; + originalFee.fee = new AmountTypeAdapter().unmarshal("0.1"); + + // Add a time far in the future to nameRegistrationUnitFees UnitFeesByTimestamp futureFeeIncrease = new UnitFeesByTimestamp(); futureFeeIncrease.timestamp = 9999999999999L; // 20 Nov 2286 futureFeeIncrease.fee = new AmountTypeAdapter().unmarshal("5"); - FieldUtils.writeField(BlockChain.getInstance(), "nameRegistrationUnitFees", Arrays.asList(futureFeeIncrease), true); + FieldUtils.writeField(BlockChain.getInstance(), "nameRegistrationUnitFees", Arrays.asList(originalFee, futureFeeIncrease), true); assertEquals(futureFeeIncrease.fee, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(futureFeeIncrease.timestamp)); // Validate unit fees pre and post timestamp - assertEquals(10000000, BlockChain.getInstance().getUnitFee()); // 0.1 QORT assertEquals(10000000, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(futureFeeIncrease.timestamp - 1)); // 0.1 QORT assertEquals(500000000, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(futureFeeIncrease.timestamp)); // 5 QORT @@ -362,7 +367,7 @@ public class MiscTests extends Common { futureFeeIncrease.timestamp = now + (60 * 60 * 1000L); // 1 hour in the future futureFeeIncrease.fee = new AmountTypeAdapter().unmarshal("10"); - FieldUtils.writeField(BlockChain.getInstance(), "nameRegistrationUnitFees", Arrays.asList(pastFeeIncrease, futureFeeIncrease), true); + FieldUtils.writeField(BlockChain.getInstance(), "nameRegistrationUnitFees", Arrays.asList(originalFee, pastFeeIncrease, futureFeeIncrease), true); assertEquals(pastFeeIncrease.fee, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(pastFeeIncrease.timestamp)); assertEquals(futureFeeIncrease.fee, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(futureFeeIncrease.timestamp)); @@ -387,4 +392,124 @@ public class MiscTests extends Common { } } + // test reading the name registration fee schedule from blockchain.json / test-chain-v2.json + @Test + public void testRegisterNameFeeScheduleInTestchainData() throws Exception { + try (final Repository repository = RepositoryManager.getRepository()) { + + final long expectedFutureFeeIncreaseTimestamp = 9999999999999L; // 20 Nov 2286, as per test-chain-v2.json + final long expectedFutureFeeIncreaseValue = new AmountTypeAdapter().unmarshal("5"); + + assertEquals(expectedFutureFeeIncreaseValue, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(expectedFutureFeeIncreaseTimestamp)); + + // Validate unit fees pre and post timestamp + assertEquals(10000000, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(expectedFutureFeeIncreaseTimestamp - 1)); // 0.1 QORT + assertEquals(500000000, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(expectedFutureFeeIncreaseTimestamp)); // 5 QORT + + // Register-name + PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); + String name = "test-name"; + String data = "{\"age\":30}"; + + RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, data); + transactionData.setFee(new RegisterNameTransaction(null, null).getUnitFee(transactionData.getTimestamp())); + assertEquals(10000000L, transactionData.getFee().longValue()); + TransactionUtils.signAndMint(repository, transactionData, alice); + } + } + + + + // test general unit fee increase + @Test + public void testUnitFeeIncrease() throws Exception { + try (final Repository repository = RepositoryManager.getRepository()) { + + // Add original fee to unitFees + UnitFeesByTimestamp originalFee = new UnitFeesByTimestamp(); + originalFee.timestamp = 0L; + originalFee.fee = new AmountTypeAdapter().unmarshal("0.1"); + + // Add a time far in the future to unitFees + UnitFeesByTimestamp futureFeeIncrease = new UnitFeesByTimestamp(); + futureFeeIncrease.timestamp = 9999999999999L; // 20 Nov 2286 + futureFeeIncrease.fee = new AmountTypeAdapter().unmarshal("1"); + FieldUtils.writeField(BlockChain.getInstance(), "unitFees", Arrays.asList(originalFee, futureFeeIncrease), true); + assertEquals(futureFeeIncrease.fee, BlockChain.getInstance().getUnitFeeAtTimestamp(futureFeeIncrease.timestamp)); + + // Validate unit fees pre and post timestamp + assertEquals(10000000, BlockChain.getInstance().getUnitFeeAtTimestamp(futureFeeIncrease.timestamp - 1)); // 0.1 QORT + assertEquals(100000000, BlockChain.getInstance().getUnitFeeAtTimestamp(futureFeeIncrease.timestamp)); // 1 QORT + + // Payment + PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); + PrivateKeyAccount bob = Common.getTestAccount(repository, "bob"); + PrivateKeyAccount chloe = Common.getTestAccount(repository, "chloe"); + + PaymentTransactionData transactionData = new PaymentTransactionData(TestTransaction.generateBase(alice), bob.getAddress(), 100000); + transactionData.setFee(new PaymentTransaction(null, null).getUnitFee(transactionData.getTimestamp())); + assertEquals(10000000L, transactionData.getFee().longValue()); + TransactionUtils.signAndMint(repository, transactionData, alice); + + // Set fee increase to a time in the past + Long now = NTP.getTime(); + UnitFeesByTimestamp pastFeeIncrease = new UnitFeesByTimestamp(); + pastFeeIncrease.timestamp = now - 1000L; // 1 second ago + pastFeeIncrease.fee = new AmountTypeAdapter().unmarshal("3"); + + // Set another increase in the future + futureFeeIncrease = new UnitFeesByTimestamp(); + futureFeeIncrease.timestamp = now + (60 * 60 * 1000L); // 1 hour in the future + futureFeeIncrease.fee = new AmountTypeAdapter().unmarshal("10"); + + FieldUtils.writeField(BlockChain.getInstance(), "unitFees", Arrays.asList(originalFee, pastFeeIncrease, futureFeeIncrease), true); + assertEquals(originalFee.fee, BlockChain.getInstance().getUnitFeeAtTimestamp(originalFee.timestamp)); + assertEquals(pastFeeIncrease.fee, BlockChain.getInstance().getUnitFeeAtTimestamp(pastFeeIncrease.timestamp)); + assertEquals(futureFeeIncrease.fee, BlockChain.getInstance().getUnitFeeAtTimestamp(futureFeeIncrease.timestamp)); + + // Send another payment transaction + // Fee should be determined automatically + transactionData = new PaymentTransactionData(TestTransaction.generateBase(alice), bob.getAddress(), 50000); + assertEquals(300000000L, transactionData.getFee().longValue()); + Transaction transaction = Transaction.fromData(repository, transactionData); + transaction.sign(alice); + ValidationResult result = transaction.importAsUnconfirmed(); + assertEquals("Transaction should be valid", ValidationResult.OK, result); + + // Now try fetching and setting fee manually + transactionData = new PaymentTransactionData(TestTransaction.generateBase(alice), chloe.getAddress(), 50000); + transactionData.setFee(new PaymentTransaction(null, null).getUnitFee(transactionData.getTimestamp())); + assertEquals(300000000L, transactionData.getFee().longValue()); + transaction = Transaction.fromData(repository, transactionData); + transaction.sign(alice); + result = transaction.importAsUnconfirmed(); + assertEquals("Transaction should be valid", ValidationResult.OK, result); + } + } + + // test reading the fee schedule from blockchain.json / test-chain-v2.json + @Test + public void testFeeScheduleInTestchainData() throws Exception { + try (final Repository repository = RepositoryManager.getRepository()) { + + final long expectedFutureFeeIncreaseTimestamp = 9999999999999L; // 20 Nov 2286, as per test-chain-v2.json + final long expectedFutureFeeIncreaseValue = new AmountTypeAdapter().unmarshal("1"); + + assertEquals(expectedFutureFeeIncreaseValue, BlockChain.getInstance().getUnitFeeAtTimestamp(expectedFutureFeeIncreaseTimestamp)); + + // Validate unit fees pre and post timestamp + assertEquals(10000000, BlockChain.getInstance().getUnitFeeAtTimestamp(expectedFutureFeeIncreaseTimestamp - 1)); // 0.1 QORT + assertEquals(100000000, BlockChain.getInstance().getUnitFeeAtTimestamp(expectedFutureFeeIncreaseTimestamp)); // 1 QORT + + // Payment + PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); + PrivateKeyAccount bob = Common.getTestAccount(repository, "bob"); + + PaymentTransactionData transactionData = new PaymentTransactionData(TestTransaction.generateBase(alice), bob.getAddress(), 100000); + transactionData.setFee(new PaymentTransaction(null, null).getUnitFee(transactionData.getTimestamp())); + assertEquals(10000000L, transactionData.getFee().longValue()); + TransactionUtils.signAndMint(repository, transactionData, alice); + } + } + } diff --git a/src/test/resources/test-chain-v2-block-timestamps.json b/src/test/resources/test-chain-v2-block-timestamps.json index 3b4de702..7059e035 100644 --- a/src/test/resources/test-chain-v2-block-timestamps.json +++ b/src/test/resources/test-chain-v2-block-timestamps.json @@ -4,8 +4,11 @@ "transactionExpiryPeriod": 86400000, "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, - "unitFee": "0.1", + "unitFees": [ + { "timestamp": 0, "fee": "0.1" } + ], "nameRegistrationUnitFees": [ + { "timestamp": 0, "fee": "0.1" }, { "timestamp": 1645372800000, "fee": "5" } ], "requireGroupForApproval": false, @@ -15,6 +18,7 @@ "onlineAccountSignaturesMinLifetime": 3600000, "onlineAccountSignaturesMaxLifetime": 86400000, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, + "mempowTransactionUpdatesTimestamp": 0, "rewardsByHeight": [ { "height": 1, "reward": 100 }, { "height": 11, "reward": 10 }, diff --git a/src/test/resources/test-chain-v2-disable-reference.json b/src/test/resources/test-chain-v2-disable-reference.json index c93fbb78..1016bc17 100644 --- a/src/test/resources/test-chain-v2-disable-reference.json +++ b/src/test/resources/test-chain-v2-disable-reference.json @@ -4,8 +4,11 @@ "transactionExpiryPeriod": 86400000, "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, - "unitFee": "0.1", + "unitFees": [ + { "timestamp": 0, "fee": "0.1" } + ], "nameRegistrationUnitFees": [ + { "timestamp": 0, "fee": "0.1" }, { "timestamp": 1645372800000, "fee": "5" } ], "requireGroupForApproval": false, @@ -19,6 +22,7 @@ "onlineAccountSignaturesMinLifetime": 3600000, "onlineAccountSignaturesMaxLifetime": 86400000, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, + "mempowTransactionUpdatesTimestamp": 0, "rewardsByHeight": [ { "height": 1, "reward": 100 }, { "height": 11, "reward": 10 }, diff --git a/src/test/resources/test-chain-v2-founder-rewards.json b/src/test/resources/test-chain-v2-founder-rewards.json index 1b068932..5f29bc97 100644 --- a/src/test/resources/test-chain-v2-founder-rewards.json +++ b/src/test/resources/test-chain-v2-founder-rewards.json @@ -4,8 +4,11 @@ "transactionExpiryPeriod": 86400000, "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, - "unitFee": "0.1", + "unitFees": [ + { "timestamp": 0, "fee": "0.1" } + ], "nameRegistrationUnitFees": [ + { "timestamp": 0, "fee": "0.1" }, { "timestamp": 1645372800000, "fee": "5" } ], "requireGroupForApproval": false, @@ -20,6 +23,7 @@ "onlineAccountSignaturesMaxLifetime": 86400000, "onlineAccountsModulusV2Timestamp": 9999999999999, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, + "mempowTransactionUpdatesTimestamp": 0, "rewardsByHeight": [ { "height": 1, "reward": 100 }, { "height": 11, "reward": 10 }, diff --git a/src/test/resources/test-chain-v2-leftover-reward.json b/src/test/resources/test-chain-v2-leftover-reward.json index aef76cc2..86f2def1 100644 --- a/src/test/resources/test-chain-v2-leftover-reward.json +++ b/src/test/resources/test-chain-v2-leftover-reward.json @@ -4,8 +4,11 @@ "transactionExpiryPeriod": 86400000, "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, - "unitFee": "0.1", + "unitFees": [ + { "timestamp": 0, "fee": "0.1" } + ], "nameRegistrationUnitFees": [ + { "timestamp": 0, "fee": "0.1" }, { "timestamp": 1645372800000, "fee": "5" } ], "requireGroupForApproval": false, @@ -20,6 +23,7 @@ "onlineAccountSignaturesMaxLifetime": 86400000, "onlineAccountsModulusV2Timestamp": 9999999999999, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, + "mempowTransactionUpdatesTimestamp": 0, "rewardsByHeight": [ { "height": 1, "reward": 100 }, { "height": 11, "reward": 10 }, diff --git a/src/test/resources/test-chain-v2-minting.json b/src/test/resources/test-chain-v2-minting.json index db6d8a0b..b2da6489 100644 --- a/src/test/resources/test-chain-v2-minting.json +++ b/src/test/resources/test-chain-v2-minting.json @@ -4,8 +4,11 @@ "transactionExpiryPeriod": 86400000, "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, - "unitFee": "0.1", + "unitFees": [ + { "timestamp": 0, "fee": "0.1" } + ], "nameRegistrationUnitFees": [ + { "timestamp": 0, "fee": "0.1" }, { "timestamp": 1645372800000, "fee": "5" } ], "requireGroupForApproval": false, @@ -20,6 +23,7 @@ "onlineAccountSignaturesMaxLifetime": 86400000, "onlineAccountsModulusV2Timestamp": 9999999999999, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, + "mempowTransactionUpdatesTimestamp": 9999999999999, "rewardsByHeight": [ { "height": 1, "reward": 100 }, { "height": 11, "reward": 10 }, diff --git a/src/test/resources/test-chain-v2-qora-holder-extremes.json b/src/test/resources/test-chain-v2-qora-holder-extremes.json index 2452d4d2..2933a63d 100644 --- a/src/test/resources/test-chain-v2-qora-holder-extremes.json +++ b/src/test/resources/test-chain-v2-qora-holder-extremes.json @@ -4,8 +4,11 @@ "transactionExpiryPeriod": 86400000, "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, - "unitFee": "0.1", + "unitFees": [ + { "timestamp": 0, "fee": "0.1" } + ], "nameRegistrationUnitFees": [ + { "timestamp": 0, "fee": "0.1" }, { "timestamp": 1645372800000, "fee": "5" } ], "requireGroupForApproval": false, @@ -20,6 +23,7 @@ "onlineAccountSignaturesMaxLifetime": 86400000, "onlineAccountsModulusV2Timestamp": 9999999999999, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, + "mempowTransactionUpdatesTimestamp": 0, "rewardsByHeight": [ { "height": 1, "reward": 100 }, { "height": 11, "reward": 10 }, diff --git a/src/test/resources/test-chain-v2-qora-holder-reduction.json b/src/test/resources/test-chain-v2-qora-holder-reduction.json index 23193729..40e40673 100644 --- a/src/test/resources/test-chain-v2-qora-holder-reduction.json +++ b/src/test/resources/test-chain-v2-qora-holder-reduction.json @@ -4,8 +4,11 @@ "transactionExpiryPeriod": 86400000, "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, - "unitFee": "0.1", + "unitFees": [ + { "timestamp": 0, "fee": "0.1" } + ], "nameRegistrationUnitFees": [ + { "timestamp": 0, "fee": "0.1" }, { "timestamp": 1645372800000, "fee": "5" } ], "requireGroupForApproval": false, @@ -20,6 +23,7 @@ "onlineAccountSignaturesMaxLifetime": 86400000, "onlineAccountsModulusV2Timestamp": 9999999999999, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, + "mempowTransactionUpdatesTimestamp": 0, "rewardsByHeight": [ { "height": 1, "reward": 100 }, { "height": 11, "reward": 10 }, diff --git a/src/test/resources/test-chain-v2-qora-holder.json b/src/test/resources/test-chain-v2-qora-holder.json index 9d81632b..8ceafe63 100644 --- a/src/test/resources/test-chain-v2-qora-holder.json +++ b/src/test/resources/test-chain-v2-qora-holder.json @@ -4,8 +4,11 @@ "transactionExpiryPeriod": 86400000, "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, - "unitFee": "0.1", + "unitFees": [ + { "timestamp": 0, "fee": "0.1" } + ], "nameRegistrationUnitFees": [ + { "timestamp": 0, "fee": "0.1" }, { "timestamp": 1645372800000, "fee": "5" } ], "requireGroupForApproval": false, @@ -20,6 +23,7 @@ "onlineAccountSignaturesMaxLifetime": 86400000, "onlineAccountsModulusV2Timestamp": 9999999999999, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, + "mempowTransactionUpdatesTimestamp": 0, "rewardsByHeight": [ { "height": 1, "reward": 100 }, { "height": 11, "reward": 10 }, diff --git a/src/test/resources/test-chain-v2-reward-levels.json b/src/test/resources/test-chain-v2-reward-levels.json index 81609595..68a79ed4 100644 --- a/src/test/resources/test-chain-v2-reward-levels.json +++ b/src/test/resources/test-chain-v2-reward-levels.json @@ -4,8 +4,11 @@ "transactionExpiryPeriod": 86400000, "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, - "unitFee": "0.1", + "unitFees": [ + { "timestamp": 0, "fee": "0.1" } + ], "nameRegistrationUnitFees": [ + { "timestamp": 0, "fee": "0.1" }, { "timestamp": 1645372800000, "fee": "5" } ], "requireGroupForApproval": false, @@ -20,6 +23,7 @@ "onlineAccountSignaturesMaxLifetime": 86400000, "onlineAccountsModulusV2Timestamp": 9999999999999, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, + "mempowTransactionUpdatesTimestamp": 0, "rewardsByHeight": [ { "height": 1, "reward": 100 }, { "height": 11, "reward": 10 }, diff --git a/src/test/resources/test-chain-v2-reward-scaling.json b/src/test/resources/test-chain-v2-reward-scaling.json index 21a5b7a7..cc02a73e 100644 --- a/src/test/resources/test-chain-v2-reward-scaling.json +++ b/src/test/resources/test-chain-v2-reward-scaling.json @@ -4,8 +4,11 @@ "transactionExpiryPeriod": 86400000, "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, - "unitFee": "0.1", + "unitFees": [ + { "timestamp": 0, "fee": "0.1" } + ], "nameRegistrationUnitFees": [ + { "timestamp": 0, "fee": "0.1" }, { "timestamp": 1645372800000, "fee": "5" } ], "requireGroupForApproval": false, @@ -20,6 +23,7 @@ "onlineAccountSignaturesMaxLifetime": 86400000, "onlineAccountsModulusV2Timestamp": 9999999999999, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, + "mempowTransactionUpdatesTimestamp": 0, "rewardsByHeight": [ { "height": 1, "reward": 100 }, { "height": 11, "reward": 10 }, diff --git a/src/test/resources/test-chain-v2-reward-shares.json b/src/test/resources/test-chain-v2-reward-shares.json index 6119ac48..5c508188 100644 --- a/src/test/resources/test-chain-v2-reward-shares.json +++ b/src/test/resources/test-chain-v2-reward-shares.json @@ -4,8 +4,11 @@ "transactionExpiryPeriod": 86400000, "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, - "unitFee": "0.1", + "unitFees": [ + { "timestamp": 0, "fee": "0.1" } + ], "nameRegistrationUnitFees": [ + { "timestamp": 0, "fee": "0.1" }, { "timestamp": 1645372800000, "fee": "5" } ], "requireGroupForApproval": false, @@ -19,6 +22,7 @@ "onlineAccountSignaturesMinLifetime": 3600000, "onlineAccountSignaturesMaxLifetime": 86400000, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, + "mempowTransactionUpdatesTimestamp": 0, "rewardsByHeight": [ { "height": 1, "reward": 100 }, { "height": 11, "reward": 10 }, diff --git a/src/test/resources/test-chain-v2-self-sponsorship-algo.json b/src/test/resources/test-chain-v2-self-sponsorship-algo.json index dc5f3961..244d2491 100644 --- a/src/test/resources/test-chain-v2-self-sponsorship-algo.json +++ b/src/test/resources/test-chain-v2-self-sponsorship-algo.json @@ -4,8 +4,11 @@ "transactionExpiryPeriod": 86400000, "maxBlockSize": 2097152, "maxBytesPerUnitFee": 0, - "unitFee": "0.00000001", + "unitFees": [ + { "timestamp": 0, "fee": "0.00000001" } + ], "nameRegistrationUnitFees": [ + { "timestamp": 0, "fee": "0.00000001" }, { "timestamp": 1645372800000, "fee": "5" } ], "requireGroupForApproval": false, @@ -20,6 +23,7 @@ "onlineAccountSignaturesMaxLifetime": 86400000, "onlineAccountsModulusV2Timestamp": 9999999999999, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, + "mempowTransactionUpdatesTimestamp": 0, "rewardsByHeight": [ { "height": 1, "reward": 100 }, { "height": 11, "reward": 10 }, diff --git a/src/test/resources/test-chain-v2.json b/src/test/resources/test-chain-v2.json index d0c460df..9168a0de 100644 --- a/src/test/resources/test-chain-v2.json +++ b/src/test/resources/test-chain-v2.json @@ -4,9 +4,13 @@ "transactionExpiryPeriod": 86400000, "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, - "unitFee": "0.1", + "unitFees": [ + { "timestamp": 0, "fee": "0.1" }, + { "timestamp": 9999999999999, "fee": "1" } + ], "nameRegistrationUnitFees": [ - { "timestamp": 1645372800000, "fee": "5" } + { "timestamp": 0, "fee": "0.1" }, + { "timestamp": 9999999999999, "fee": "5" } ], "requireGroupForApproval": false, "minAccountLevelToRewardShare": 5, @@ -20,6 +24,7 @@ "onlineAccountSignaturesMaxLifetime": 86400000, "onlineAccountsModulusV2Timestamp": 9999999999999, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, + "mempowTransactionUpdatesTimestamp": 0, "rewardsByHeight": [ { "height": 1, "reward": 100 }, { "height": 11, "reward": 10 }, diff --git a/testnet/testchain.json b/testnet/testchain.json index aef9ed9a..089bd693 100644 --- a/testnet/testchain.json +++ b/testnet/testchain.json @@ -4,7 +4,9 @@ "transactionExpiryPeriod": 86400000, "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, - "unitFee": "0.001", + "unitFees": [ + { "timestamp": 0, "fee": "0.001" } + ], "nameRegistrationUnitFees": [ { "timestamp": 0, "fee": "1.25" } ], @@ -24,6 +26,7 @@ "onlineAccountSignaturesMaxLifetime": 86400000, "onlineAccountsModulusV2Timestamp": 0, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, + "mempowTransactionUpdatesTimestamp": 1692554400000, "rewardsByHeight": [ { "height": 1, "reward": 5.00 }, { "height": 259201, "reward": 4.75 }, diff --git a/tools/approve-auto-update.sh b/tools/approve-auto-update.sh index cbfa280d..c34b6de7 100755 --- a/tools/approve-auto-update.sh +++ b/tools/approve-auto-update.sh @@ -50,7 +50,7 @@ tx_json=$( cat < 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 () { - if (m/(\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"; diff --git a/tools/publish-auto-update.pl b/tools/publish-auto-update.pl index ad43b2f4..9e6b885b 100755 --- a/tools/publish-auto-update.pl +++ b/tools/publish-auto-update.pl @@ -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 () { } 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 diff --git a/tools/qdn b/tools/qdn index ea52e3c9..b8f58141 100755 --- a/tools/qdn +++ b/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] " + echo "qdn POST [service] [name] PATH [dirpath] <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 + diff --git a/tools/tx.pl b/tools/tx.pl index 1cb3dd5b..7cdf444b 100755 --- a/tools/tx.pl +++ b/tools/tx.pl @@ -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 => {