From e9d8b3e6e341c96e367f34d5e3feacdb8843d58e Mon Sep 17 00:00:00 2001 From: catbref Date: Thu, 4 Oct 2018 14:38:59 +0100 Subject: [PATCH] Unit test fixes + initial CIYAM AT integration NB: we're still using HSQLDB svn r5836 Updated README.md Added log4j2.properties file for logging! Imported CIYAM-AT jar into project-local Maven repo CIYAM-AT related: ATData ATStateData ATTransactionData DeployATTransactionData AT DeployATTransaction ATRepository HSQLDBATRepository HSQLDBDeployATTransactionRepository ATTests DeployATTransactionTransformer Fixed Block so correct block hash and timestamps are generated, especially when previous/next block versions differ. Added extra call in BlockTransformer to aid this. Fixed GenesisTransaction.isValid's incorrect amount test. Fixed comments in TransferAssetTransaction and incorrect use of BlockChain.getVotingReleaseTimestamp() instead of BlockChain.getAssetsReleaseTimestamp(). Added new TYPEs to HSQLDBDatabaseUpdates, and set LOB granularity to 1KB for AT use. Added AT_address column to DeployATTransactions in HSQLDB. Added ATs, ATStates and ATTransactions tables. (You will need to discard existing database and rebuild). Fixed incorrect byte array output in IssueAssetTransactionTransformer, where Asset "references" were not processed correctly. Added support for BigDecimal serialization to a byte-array size other than the standard 8. --- README.md | 26 ++- lib/org/ciyam/at/1.0/at-1.0.jar | Bin 0 -> 131739 bytes lib/org/ciyam/at/1.0/at-1.0.jar.md5 | 1 + lib/org/ciyam/at/1.0/at-1.0.jar.sha1 | 1 + lib/org/ciyam/at/1.0/at-1.0.pom | 8 + lib/org/ciyam/at/1.0/at-1.0.pom.md5 | 1 + lib/org/ciyam/at/1.0/at-1.0.pom.sha1 | 1 + lib/org/ciyam/at/maven-metadata.xml | 12 + lib/org/ciyam/at/maven-metadata.xml.md5 | 1 + lib/org/ciyam/at/maven-metadata.xml.sha1 | 1 + log4j2.properties | 41 ++++ pom.xml | 11 + src/data/at/ATData.java | 110 +++++++++ src/data/at/ATStateData.java | 32 +++ src/data/transaction/ATTransactionData.java | 49 ++++ .../transaction/ArbitraryTransactionData.java | 3 +- .../transaction/DeployATTransactionData.java | 77 +++++++ src/qora/at/AT.java | 52 +++++ src/qora/block/Block.java | 68 ++++-- src/qora/block/BlockChain.java | 8 + src/qora/transaction/DeployATTransaction.java | 217 ++++++++++++++++++ src/qora/transaction/GenesisTransaction.java | 2 +- src/qora/transaction/Transaction.java | 72 +++++- .../transaction/TransferAssetTransaction.java | 4 +- src/repository/ATRepository.java | 24 ++ src/repository/Repository.java | 2 + src/repository/hsqldb/HSQLDBATRepository.java | 117 ++++++++++ .../hsqldb/HSQLDBDatabaseUpdates.java | 24 +- src/repository/hsqldb/HSQLDBRepository.java | 14 +- .../HSQLDBDeployATTransactionRepository.java | 63 +++++ .../HSQLDBTransactionRepository.java | 9 + src/test/ATTests.java | 96 ++++++++ src/test/RepositoryTests.java | 6 + src/test/SaveTests.java | 2 + src/test/TransactionTests.java | 29 ++- src/transform/block/BlockTransformer.java | 18 ++ .../DeployATTransactionTransformer.java | 188 +++++++++++++++ .../IssueAssetTransactionTransformer.java | 11 +- .../transaction/TransactionTransformer.java | 15 ++ src/utils/Serialization.java | 31 ++- 40 files changed, 1393 insertions(+), 54 deletions(-) create mode 100644 lib/org/ciyam/at/1.0/at-1.0.jar create mode 100644 lib/org/ciyam/at/1.0/at-1.0.jar.md5 create mode 100644 lib/org/ciyam/at/1.0/at-1.0.jar.sha1 create mode 100644 lib/org/ciyam/at/1.0/at-1.0.pom create mode 100644 lib/org/ciyam/at/1.0/at-1.0.pom.md5 create mode 100644 lib/org/ciyam/at/1.0/at-1.0.pom.sha1 create mode 100644 lib/org/ciyam/at/maven-metadata.xml create mode 100644 lib/org/ciyam/at/maven-metadata.xml.md5 create mode 100644 lib/org/ciyam/at/maven-metadata.xml.sha1 create mode 100644 log4j2.properties create mode 100644 src/data/at/ATData.java create mode 100644 src/data/at/ATStateData.java create mode 100644 src/data/transaction/ATTransactionData.java create mode 100644 src/data/transaction/DeployATTransactionData.java create mode 100644 src/qora/at/AT.java create mode 100644 src/qora/transaction/DeployATTransaction.java create mode 100644 src/repository/ATRepository.java create mode 100644 src/repository/hsqldb/HSQLDBATRepository.java create mode 100644 src/repository/hsqldb/transaction/HSQLDBDeployATTransactionRepository.java create mode 100644 src/test/ATTests.java create mode 100644 src/transform/transaction/DeployATTransactionTransformer.java diff --git a/README.md b/README.md index 7837af25..27c53351 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,13 @@ To use: - Use maven to fetch dependencies. - Build project. +- Build v1feeder.jar as a fatjar using src/v1feeder.java as the main class - Fire up an old-gen Qora node. -- Run ```src/migrate.java``` as a Java application to migrate old Qora blocks to DB. +- Use ```v1feeder.jar``` to migrate old Qora blocks to DB: -You should now be able to run ```src/test/load.java``` and ```src/test/save.java``` -as JUnit tests demonstrating loading/saving Transactions from/to database. +```java -jar v1feeder.jar qora-v1-node-ip``` + +You should now be able to run all the JUnit tests. You can also examine the migrated database using [HSQLDB's "sqltool"](http://www.hsqldb.org/doc/2.0/util-guide/sqltool-chapt.html). @@ -21,17 +23,23 @@ Typical command line for sqltool would be: rlwrap java -cp ${HSQLDB_JAR}:${SQLTOOL_JAR} org.hsqldb.cmdline.SqlTool --rcFile=${SQLTOOL_RC} qora ``` -```${HSQLDB_JAR}``` contains pathname to ```hsqldb-2.4.0.jar```, -typically ```${HOME}/.m2/repository/org/hsqldb/hsqldb/2.4.0/hsqldb-2.4.0.jar``` +```${HSQLDB_JAR}``` contains pathname to where Maven downloaded hsqldb, +typically ```${HOME}/.m2/repository/org/hsqldb/hsqldb/2.4.0/hsqldb-2.4.0.jar```, +but for now ```lib/org/hsqldb/hsqldb/r5836/hsqldb-r5836.jar``` -```${SQLTOOL_JAR}``` contains pathname to where you -[downloaded sqltool-2.2.6.jar](http://search.maven.org/remotecontent?filepath=org/hsqldb/sqltool/2.2.6/sqltool-2.2.6.jar) +```${SQLTOOL_JAR}``` contains pathname to where Maven downloaded sqltool, +typically ```${HOME}/.m2/repository/org/hsqldb/sqltool/2.4.1/sqltool-2.4.1.jar``` ```${SQLTOOL_RC}``` contains pathname to a text file describing Qora2 database, e.g. ```${HOME}/.sqltool.rc```, with contents like: ``` urlid qora +url jdbc:hsqldb:file:db/qora +username SA +password + +urlid qora-test url jdbc:hsqldb:file:db/test username SA password @@ -41,8 +49,10 @@ You could change the line ```url jdbc:hsqldb:file:db/test``` to use a full pathn Another idea is to assign a shell alias in your ```.bashrc``` like: ``` +export HSQLDB_JAR=${HOME}/.m2/repository/org/hsqldb/hsqldb/2.4.0/hsqldb-2.4.0.jar +export SQLTOOL_JAR=${HOME}/.m2/repository/org/hsqldb/sqltool/2.4.1/sqltool-2.4.1.jar alias sqltool='rlwrap java -cp ${HSQLDB_JAR}:${SQLTOOL_JAR} org.hsqldb.cmdline.SqlTool --rcFile=${SQLTOOL_RC}' ``` -So you can simply type: ```sqltool qora``` +So you can simply type: ```sqltool qora-test``` Don't forget to use ```SHUTDOWN;``` before exiting sqltool so that database files are closed cleanly. \ No newline at end of file diff --git a/lib/org/ciyam/at/1.0/at-1.0.jar b/lib/org/ciyam/at/1.0/at-1.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..1b554f3425f2dbc909f0d2ae720d7164fd7ff5f3 GIT binary patch literal 131739 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

4_vx$NZh!Yq80UY!#ogGMv1wl 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 SYEOWZ1p3>_Oaiyi;QkLqMPPOS literal 0 HcmV?d00001 diff --git a/lib/org/ciyam/at/1.0/at-1.0.jar.md5 b/lib/org/ciyam/at/1.0/at-1.0.jar.md5 new file mode 100644 index 00000000..c06cf86a --- /dev/null +++ b/lib/org/ciyam/at/1.0/at-1.0.jar.md5 @@ -0,0 +1 @@ +1d6f5d634a2c4e570a5a8af260a51653 \ No newline at end of file diff --git a/lib/org/ciyam/at/1.0/at-1.0.jar.sha1 b/lib/org/ciyam/at/1.0/at-1.0.jar.sha1 new file mode 100644 index 00000000..2f748fd6 --- /dev/null +++ b/lib/org/ciyam/at/1.0/at-1.0.jar.sha1 @@ -0,0 +1 @@ +c6387380bc5db1f0a98ecbb480b17bd89b564401 \ No newline at end of file diff --git a/lib/org/ciyam/at/1.0/at-1.0.pom b/lib/org/ciyam/at/1.0/at-1.0.pom new file mode 100644 index 00000000..05532457 --- /dev/null +++ b/lib/org/ciyam/at/1.0/at-1.0.pom @@ -0,0 +1,8 @@ + + + 4.0.0 + org.ciyam + at + 1.0 + diff --git a/lib/org/ciyam/at/1.0/at-1.0.pom.md5 b/lib/org/ciyam/at/1.0/at-1.0.pom.md5 new file mode 100644 index 00000000..811dfee8 --- /dev/null +++ b/lib/org/ciyam/at/1.0/at-1.0.pom.md5 @@ -0,0 +1 @@ +42f6e3eb3c6e510f65c963ce97583f05 \ No newline at end of file diff --git a/lib/org/ciyam/at/1.0/at-1.0.pom.sha1 b/lib/org/ciyam/at/1.0/at-1.0.pom.sha1 new file mode 100644 index 00000000..6b6618c9 --- /dev/null +++ b/lib/org/ciyam/at/1.0/at-1.0.pom.sha1 @@ -0,0 +1 @@ +490287647d3c69c05bd50ab565ffff86192ff423 \ No newline at end of file diff --git a/lib/org/ciyam/at/maven-metadata.xml b/lib/org/ciyam/at/maven-metadata.xml new file mode 100644 index 00000000..9b556545 --- /dev/null +++ b/lib/org/ciyam/at/maven-metadata.xml @@ -0,0 +1,12 @@ + + + org.ciyam + at + + 1.0 + + 1.0 + + 20181003154752 + + diff --git a/lib/org/ciyam/at/maven-metadata.xml.md5 b/lib/org/ciyam/at/maven-metadata.xml.md5 new file mode 100644 index 00000000..de530860 --- /dev/null +++ b/lib/org/ciyam/at/maven-metadata.xml.md5 @@ -0,0 +1 @@ +bc81bc1f9b74a4eececd5dd8b29e47d8 \ No newline at end of file diff --git a/lib/org/ciyam/at/maven-metadata.xml.sha1 b/lib/org/ciyam/at/maven-metadata.xml.sha1 new file mode 100644 index 00000000..1746aa6b --- /dev/null +++ b/lib/org/ciyam/at/maven-metadata.xml.sha1 @@ -0,0 +1 @@ +feefde4343bda4d6e13159e5c01f8b4f8963a1bc \ No newline at end of file diff --git a/log4j2.properties b/log4j2.properties new file mode 100644 index 00000000..187ef94a --- /dev/null +++ b/log4j2.properties @@ -0,0 +1,41 @@ +rootLogger.level = info +# On Windows, this might be rewritten as: +# property.filename = ${sys:user.home}\\AppData\\Roaming\\Qora\\log.txt +property.filename = log.txt + +rootLogger.appenderRef.console.ref = stdout +rootLogger.appenderRef.rolling.ref = FILE + +# Override HSQLDB logging level to "warn" as too much is logged at "info" +logger.hsqldb.name = hsqldb.db +logger.hsqldb.level = warn +logger.hsqldb.appenderRef.rolling.ref = FILE + +# Override logging level for this class +logger.voting.name = qora.transaction.VoteOnPollTransaction +logger.voting.level = trace +logger.voting.appenderRef.rolling.ref = FILE + +# Override logging level for this class +logger.assets.name = qora.assets.Order +logger.assets.level = trace +logger.assets.appenderRef.rolling.ref = FILE + +appender.console.type = Console +appender.console.name = stdout +appender.console.layout.type = PatternLayout +appender.console.layout.pattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n +appender.console.filter.threshold.type = ThresholdFilter +appender.console.filter.threshold.level = error + +appender.rolling.type = RollingFile +appender.rolling.name = FILE +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 +# Set the immediate flush to true (default) +# appender.rolling.immediateFlush = true +# Set the append to true (default), should not overwrite +# appender.rolling.append=true diff --git a/pom.xml b/pom.xml index dfd08a3d..72da220b 100644 --- a/pom.xml +++ b/pom.xml @@ -107,5 +107,16 @@ jersey-media-moxy 2.27 + + org.ciyam + at + 1.0 + + + org.hsqldb + sqltool + 2.4.1 + test + \ No newline at end of file diff --git a/src/data/at/ATData.java b/src/data/at/ATData.java new file mode 100644 index 00000000..bf8272e6 --- /dev/null +++ b/src/data/at/ATData.java @@ -0,0 +1,110 @@ +package data.at; + +import java.math.BigDecimal; + +public class ATData { + + // Properties + private String ATAddress; + private int version; + private byte[] codeBytes; + private boolean isSleeping; + private Integer sleepUntilHeight; + private boolean isFinished; + private boolean hadFatalError; + private boolean isFrozen; + private BigDecimal frozenBalance; + private byte[] deploySignature; + + // Constructors + + public ATData(String ATAddress, int version, byte[] codeBytes, boolean isSleeping, Integer sleepUntilHeight, boolean isFinished, boolean hadFatalError, + boolean isFrozen, BigDecimal frozenBalance, byte[] deploySignature) { + this.ATAddress = ATAddress; + this.version = version; + this.codeBytes = codeBytes; + this.isSleeping = isSleeping; + this.sleepUntilHeight = sleepUntilHeight; + this.isFinished = isFinished; + this.hadFatalError = hadFatalError; + this.isFrozen = isFrozen; + this.frozenBalance = frozenBalance; + this.deploySignature = deploySignature; + } + + public ATData(String ATAddress, int version, byte[] codeBytes, boolean isSleeping, Integer sleepUntilHeight, boolean isFinished, boolean hadFatalError, + boolean isFrozen, Long frozenBalance, byte[] deploySignature) { + this(ATAddress, version, codeBytes, isSleeping, sleepUntilHeight, isFinished, hadFatalError, isFrozen, (BigDecimal) null, deploySignature); + + // Convert Long frozenBalance to BigDecimal + if (frozenBalance != null) + this.frozenBalance = BigDecimal.valueOf(frozenBalance).setScale(8).divide(BigDecimal.valueOf(1e8)); + } + + // Getters / setters + + public String getATAddress() { + return this.ATAddress; + } + + public int getVersion() { + return this.version; + } + + public byte[] getCodeBytes() { + return this.codeBytes; + } + + public boolean getIsSleeping() { + return this.isSleeping; + } + + public void setIsSleeping(boolean isSleeping) { + this.isSleeping = isSleeping; + } + + public Integer getSleepUntilHeight() { + return this.sleepUntilHeight; + } + + public void setSleepUntilHeight(Integer sleepUntilHeight) { + this.sleepUntilHeight = sleepUntilHeight; + } + + public boolean getIsFinished() { + return this.isFinished; + } + + public void setIsFinished(boolean isFinished) { + this.isFinished = isFinished; + } + + public boolean getHadFatalError() { + return this.hadFatalError; + } + + public void setHadFatalError(boolean hadFatalError) { + this.hadFatalError = hadFatalError; + } + + public boolean getIsFrozen() { + return this.isFrozen; + } + + public void setIsFrozen(boolean isFrozen) { + this.isFrozen = isFrozen; + } + + public BigDecimal getFrozenBalance() { + return this.frozenBalance; + } + + public void setFrozenBalance(BigDecimal frozenBalance) { + this.frozenBalance = frozenBalance; + } + + public byte[] getDeploySignature() { + return this.deploySignature; + } + +} diff --git a/src/data/at/ATStateData.java b/src/data/at/ATStateData.java new file mode 100644 index 00000000..192c8095 --- /dev/null +++ b/src/data/at/ATStateData.java @@ -0,0 +1,32 @@ +package data.at; + +public class ATStateData { + + // Properties + private String ATAddress; + private int height; + private byte[] stateData; + + // Constructors + + public ATStateData(String ATAddress, int height, byte[] stateData) { + this.ATAddress = ATAddress; + this.height = height; + this.stateData = stateData; + } + + // Getters / setters + + public String getATAddress() { + return this.ATAddress; + } + + public int getHeight() { + return this.height; + } + + public byte[] getStateData() { + return this.stateData; + } + +} diff --git a/src/data/transaction/ATTransactionData.java b/src/data/transaction/ATTransactionData.java new file mode 100644 index 00000000..43053561 --- /dev/null +++ b/src/data/transaction/ATTransactionData.java @@ -0,0 +1,49 @@ +package data.transaction; + +import java.math.BigDecimal; + +import qora.transaction.Transaction.TransactionType; + +public class ATTransactionData extends TransactionData { + + // Properties + private byte[] senderPublicKey; + private String recipient; + private BigDecimal amount; + private byte[] message; + + // Constructors + + public ATTransactionData(byte[] senderPublicKey, String recipient, BigDecimal amount, byte[] message, BigDecimal fee, long timestamp, byte[] reference, + byte[] signature) { + super(TransactionType.AT, fee, senderPublicKey, timestamp, reference, signature); + + this.senderPublicKey = senderPublicKey; + this.recipient = recipient; + this.amount = amount; + this.message = message; + } + + public ATTransactionData(byte[] senderPublicKey, String recipient, BigDecimal amount, byte[] message, BigDecimal fee, long timestamp, byte[] reference) { + this(senderPublicKey, recipient, amount, message, fee, timestamp, reference, null); + } + + // Getters/Setters + + public byte[] getSenderPublicKey() { + return this.senderPublicKey; + } + + public String getRecipient() { + return this.recipient; + } + + public BigDecimal getAmount() { + return this.amount; + } + + public byte[] getMessage() { + return this.message; + } + +} diff --git a/src/data/transaction/ArbitraryTransactionData.java b/src/data/transaction/ArbitraryTransactionData.java index a5de2238..7392e1ff 100644 --- a/src/data/transaction/ArbitraryTransactionData.java +++ b/src/data/transaction/ArbitraryTransactionData.java @@ -10,7 +10,8 @@ public class ArbitraryTransactionData extends TransactionData { // "data" field types public enum DataType { - RAW_DATA, DATA_HASH; + RAW_DATA, + DATA_HASH; } // Properties diff --git a/src/data/transaction/DeployATTransactionData.java b/src/data/transaction/DeployATTransactionData.java new file mode 100644 index 00000000..101e2094 --- /dev/null +++ b/src/data/transaction/DeployATTransactionData.java @@ -0,0 +1,77 @@ +package data.transaction; + +import java.math.BigDecimal; + +import qora.transaction.Transaction.TransactionType; + +public class DeployATTransactionData extends TransactionData { + + // Properties + private String name; + private String description; + private String ATType; + private String tags; + private byte[] creationBytes; + private BigDecimal amount; + private String ATAddress; + + // Constructors + + public DeployATTransactionData(String ATAddress, byte[] creatorPublicKey, String name, String description, String ATType, String tags, byte[] creationBytes, + BigDecimal amount, BigDecimal fee, long timestamp, byte[] reference, byte[] signature) { + super(TransactionType.DEPLOY_AT, fee, creatorPublicKey, timestamp, reference, signature); + + this.name = name; + this.description = description; + this.ATType = ATType; + this.tags = tags; + this.amount = amount; + this.creationBytes = creationBytes; + this.ATAddress = ATAddress; + } + + public DeployATTransactionData(byte[] creatorPublicKey, String name, String description, String ATType, String tags, byte[] creationBytes, + BigDecimal amount, BigDecimal fee, long timestamp, byte[] reference, byte[] signature) { + this(null, creatorPublicKey, name, description, ATType, tags, creationBytes, amount, fee, timestamp, reference, signature); + } + + public DeployATTransactionData(byte[] creatorPublicKey, String name, String description, String ATType, String tags, byte[] creationBytes, + BigDecimal amount, BigDecimal fee, long timestamp, byte[] reference) { + this(null, creatorPublicKey, name, description, ATType, tags, creationBytes, amount, fee, timestamp, reference, null); + } + + // Getters/Setters + + public String getName() { + return this.name; + } + + public String getDescription() { + return this.description; + } + + public String getATType() { + return this.ATType; + } + + public String getTags() { + return this.tags; + } + + public byte[] getCreationBytes() { + return this.creationBytes; + } + + public BigDecimal getAmount() { + return this.amount; + } + + public String getATAddress() { + return this.ATAddress; + } + + public void setATAddress(String ATAddress) { + this.ATAddress = ATAddress; + } + +} diff --git a/src/qora/at/AT.java b/src/qora/at/AT.java new file mode 100644 index 00000000..8c1a9b99 --- /dev/null +++ b/src/qora/at/AT.java @@ -0,0 +1,52 @@ +package qora.at; + +import org.ciyam.at.MachineState; + +import data.at.ATData; +import data.at.ATStateData; +import data.transaction.DeployATTransactionData; +import repository.DataException; +import repository.Repository; + +public class AT { + + // Properties + private Repository repository; + private ATData atData; + private ATStateData atStateData; + + // Constructors + + public AT(Repository repository, ATData atData, ATStateData atStateData) { + this.repository = repository; + this.atData = atData; + this.atStateData = atStateData; + } + + public AT(Repository repository, DeployATTransactionData deployATTransactionData) throws DataException { + this.repository = repository; + + MachineState machineState = new MachineState(deployATTransactionData.getCreationBytes()); + + this.atData = new ATData(deployATTransactionData.getATAddress(), machineState.version, machineState.codeByteBuffer.array(), machineState.isSleeping, + machineState.sleepUntilHeight, machineState.isFinished, machineState.hadFatalError, machineState.isFrozen, machineState.frozenBalance, + deployATTransactionData.getSignature()); + + String atAddress = this.atData.getATAddress(); + + int height = this.repository.getBlockRepository().getBlockchainHeight(); + byte[] stateData = machineState.toBytes(); + + this.atStateData = new ATStateData(atAddress, height, stateData); + } + + public void deploy() throws DataException { + this.repository.getATRepository().save(this.atData); + this.repository.getATRepository().save(this.atStateData); + } + + public void undeploy() throws DataException { + // AT states deleted implicitly by repository + this.repository.getATRepository().delete(this.atData.getATAddress()); + } +} diff --git a/src/qora/block/Block.java b/src/qora/block/Block.java index 22c49bf6..5dc2340f 100644 --- a/src/qora/block/Block.java +++ b/src/qora/block/Block.java @@ -60,9 +60,21 @@ public class Block { // Validation results public enum ValidationResult { - OK(1), REFERENCE_MISSING(10), PARENT_DOES_NOT_EXIST(11), BLOCKCHAIN_NOT_EMPTY(12), TIMESTAMP_OLDER_THAN_PARENT(20), TIMESTAMP_IN_FUTURE( - 21), TIMESTAMP_MS_INCORRECT(22), VERSION_INCORRECT(30), FEATURE_NOT_YET_RELEASED(31), GENERATING_BALANCE_INCORRECT(40), GENERATOR_NOT_ACCEPTED( - 41), GENESIS_TRANSACTIONS_INVALID(50), TRANSACTION_TIMESTAMP_INVALID(51), TRANSACTION_INVALID(52), TRANSACTION_PROCESSING_FAILED(53); + OK(1), + REFERENCE_MISSING(10), + PARENT_DOES_NOT_EXIST(11), + BLOCKCHAIN_NOT_EMPTY(12), + TIMESTAMP_OLDER_THAN_PARENT(20), + TIMESTAMP_IN_FUTURE(21), + TIMESTAMP_MS_INCORRECT(22), + VERSION_INCORRECT(30), + FEATURE_NOT_YET_RELEASED(31), + GENERATING_BALANCE_INCORRECT(40), + GENERATOR_NOT_ACCEPTED(41), + GENESIS_TRANSACTIONS_INVALID(50), + TRANSACTION_TIMESTAMP_INVALID(51), + TRANSACTION_INVALID(52), + TRANSACTION_PROCESSING_FAILED(53); public final int value; @@ -123,6 +135,7 @@ public class Block { this.transactions = new ArrayList(); } + /** Construct a new block for use in tests */ public Block(Repository repository, BlockData parentBlockData, PrivateKeyAccount generator, byte[] atBytes, BigDecimal atFees) throws DataException { this.repository = repository; this.generator = generator; @@ -131,13 +144,20 @@ public class Block { int version = parentBlock.getNextBlockVersion(); byte[] reference = parentBlockData.getSignature(); - long timestamp = parentBlock.calcNextBlockTimestamp(generator); BigDecimal generatingBalance = parentBlock.calcNextBlockGeneratingBalance(); - this.blockData = new BlockData(version, reference, 0, BigDecimal.ZERO.setScale(8), null, 0, timestamp, generatingBalance, generator.getPublicKey(), - null, atBytes, atFees); + byte[] generatorSignature; + try { + generatorSignature = generator + .sign(BlockTransformer.getBytesForGeneratorSignature(parentBlockData.getGeneratorSignature(), generatingBalance, generator)); + } catch (TransformationException e) { + throw new DataException("Unable to calculate next block generator signature", e); + } - calcGeneratorSignature(); + long timestamp = parentBlock.calcNextBlockTimestamp(version, generatorSignature, generator); + + this.blockData = new BlockData(version, reference, 0, BigDecimal.ZERO.setScale(8), null, 0, timestamp, generatingBalance, generator.getPublicKey(), + generatorSignature, atBytes, atFees); this.transactions = new ArrayList(); } @@ -288,8 +308,23 @@ public class Block { return new BigInteger(1, hash); } - private long calcNextBlockTimestamp(Account nextBlockGenerator) throws DataException { - BigInteger hashValue = calcBlockHash(); + private BigInteger calcNextBlockHash(int nextBlockVersion, byte[] preVersion3GeneratorSignature, PublicKeyAccount nextBlockGenerator) { + byte[] hashData; + + if (nextBlockVersion < 3) + hashData = preVersion3GeneratorSignature; + else + hashData = Bytes.concat(this.blockData.getSignature(), nextBlockGenerator.getPublicKey()); + + // Calculate 32-byte hash as pseudo-random, but deterministic, integer (unique to this generator for v3+ blocks) + byte[] hash = Crypto.digest(hashData); + + // Convert hash to BigInteger form + return new BigInteger(1, hash); + } + + private long calcNextBlockTimestamp(int nextBlockVersion, byte[] nextBlockGeneratorSignature, PrivateKeyAccount nextBlockGenerator) throws DataException { + BigInteger hashValue = calcNextBlockHash(nextBlockVersion, nextBlockGeneratorSignature, nextBlockGenerator); BigInteger target = calcGeneratorsTarget(nextBlockGenerator); // If target is zero then generator has no balance so return longest value @@ -417,6 +452,12 @@ public class Block { * Recalculate block's generator signature. *

* Requires block's {@code generator} being a {@code PrivateKeyAccount}. + *

+ * Generator signature is made by the generator signing the following data: + *

+ * previous block's generator signature + this block's generating balance + generator's public key + *

+ * (Previous block's generator signature is extracted from this block's reference). * * @throws IllegalStateException * if block's {@code generator} is not a {@code PrivateKeyAccount}. @@ -538,11 +579,12 @@ public class Block { return ValidationResult.GENERATOR_NOT_ACCEPTED; // XXX Odd gen1 test: "CHECK IF FIRST BLOCK OF USER" - // Is the comment wrong and this each second elapsed allows generator to test a new "target" window against hashValue? + // Is the comment wrong? Does each second elapsed allows generator to test a new "target" window against hashValue? if (hashValue.compareTo(lowerTarget) < 0) return ValidationResult.GENERATOR_NOT_ACCEPTED; - // Check CIYAM AT + // Process CIYAM ATs, prepending AT-Transactions to block then compare post-execution checksums + // XXX We should pre-calculate, and cache, next block's AT-transactions after processing each block to save repeated work if (this.blockData.getAtBytes() != null && this.blockData.getAtBytes().length > 0) { // TODO // try { @@ -591,8 +633,8 @@ public class Block { this.repository.discardChanges(); } catch (DataException e) { /* - * Discard failure most likely due to prior DataException, so catch discardChanges' DataException and discard. Prior DataException propagates to - * caller. Successful completion of try-block continues on after discard. + * discardChanges failure most likely due to prior DataException, so catch discardChanges' DataException and ignore. Prior DataException + * propagates to caller. */ } } diff --git a/src/qora/block/BlockChain.java b/src/qora/block/BlockChain.java index 3a2b4074..a35ca067 100644 --- a/src/qora/block/BlockChain.java +++ b/src/qora/block/BlockChain.java @@ -43,6 +43,7 @@ public class BlockChain { private static final long ISSUE_ASSET_V2_TIMESTAMP = 1552500000000L; // 2019-03-13T18:00:00+00:00 // Future Qora v2 ISSUE ASSET transactions private static final long CREATE_ORDER_V2_TIMESTAMP = 1552500000000L; // 2019-03-13T18:00:00+00:00 // Future Qora v2 CREATE ORDER transactions private static final long ARBITRARY_TRANSACTION_V2_TIMESTAMP = 1552500000000L; // 2019-03-13T18:00:00+00:00 // Future Qora v2 ARBITRARY transactions + private static final long DEPLOY_AT_V2_TIMESTAMP = 1552500000000L; // 2019-03-13T18:00:00+00:00 // Future Qora v2 DEPLOY AT transactions /** * Some sort start-up/initialization/checking method. @@ -173,4 +174,11 @@ public class BlockChain { return ARBITRARY_TRANSACTION_V2_TIMESTAMP; } + public static long getDeployATV2Timestamp() { + if (Settings.getInstance().isTestNet()) + return 0; + + return DEPLOY_AT_V2_TIMESTAMP; + } + } diff --git a/src/qora/transaction/DeployATTransaction.java b/src/qora/transaction/DeployATTransaction.java new file mode 100644 index 00000000..45d77600 --- /dev/null +++ b/src/qora/transaction/DeployATTransaction.java @@ -0,0 +1,217 @@ +package qora.transaction; + +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.google.common.base.Utf8; + +import data.transaction.DeployATTransactionData; +import data.transaction.TransactionData; +import qora.account.Account; +import qora.assets.Asset; +import qora.at.AT; +import qora.block.BlockChain; +import qora.crypto.Crypto; +import repository.DataException; +import repository.Repository; +import transform.Transformer; + +public class DeployATTransaction extends Transaction { + + // Properties + private DeployATTransactionData deployATTransactionData; + + // Other useful constants + public static final int MAX_NAME_SIZE = 200; + public static final int MAX_DESCRIPTION_SIZE = 2000; + public static final int MAX_AT_TYPE_SIZE = 200; + public static final int MAX_TAGS_SIZE = 200; + public static final int MAX_CREATION_BYTES_SIZE = 100_000; + + // Constructors + + public DeployATTransaction(Repository repository, TransactionData transactionData) { + super(repository, transactionData); + + this.deployATTransactionData = (DeployATTransactionData) this.transactionData; + } + + // More information + + @Override + public List getRecipientAccounts() throws DataException { + return new ArrayList(); + } + + @Override + public boolean isInvolved(Account account) throws DataException { + String address = account.getAddress(); + + if (address.equals(this.getCreator().getAddress())) + return true; + + if (address.equals(this.getATAccount().getAddress())) + return true; + + return false; + } + + @Override + public BigDecimal getAmount(Account account) throws DataException { + String address = account.getAddress(); + BigDecimal amount = BigDecimal.ZERO.setScale(8); + + if (address.equals(this.getCreator().getAddress())) + amount = amount.subtract(this.deployATTransactionData.getAmount()).subtract(this.transactionData.getFee()); + + if (address.equals(this.getATAccount().getAddress())) + amount = amount.add(this.deployATTransactionData.getAmount()); + + return amount; + } + + /** Make sure deployATTransactionData has an ATAddress */ + private void ensureATAddress() throws DataException { + if (this.deployATTransactionData.getATAddress() != null) + return; + + int blockHeight = this.getHeight(); + if (blockHeight == 0) + blockHeight = this.repository.getBlockRepository().getBlockchainHeight(); + + try { + byte[] name = this.deployATTransactionData.getName().getBytes("UTF-8"); + byte[] description = this.deployATTransactionData.getDescription().replaceAll("\\s", "").getBytes("UTF-8"); + byte[] creatorPublicKey = this.deployATTransactionData.getCreatorPublicKey(); + byte[] creationBytes = this.deployATTransactionData.getCreationBytes(); + + ByteBuffer byteBuffer = ByteBuffer + .allocate(name.length + description.length + creatorPublicKey.length + creationBytes.length + Transformer.INT_LENGTH); + + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + + byteBuffer.put(name); + byteBuffer.put(description); + byteBuffer.put(creatorPublicKey); + byteBuffer.put(creationBytes); + byteBuffer.putInt(blockHeight); + + String atAddress = Crypto.toATAddress(byteBuffer.array()); + + this.deployATTransactionData.setATAddress(atAddress); + } catch (UnsupportedEncodingException e) { + throw new DataException("Unable to generate AT account from Deploy AT transaction data", e); + } + } + + // Navigation + + public Account getATAccount() throws DataException { + ensureATAddress(); + + return new Account(this.repository, this.deployATTransactionData.getATAddress()); + } + + // Processing + + @Override + public ValidationResult isValid() throws DataException { + if (this.repository.getBlockRepository().getBlockchainHeight() < BlockChain.getATReleaseHeight()) + return ValidationResult.NOT_YET_RELEASED; + + // Check name size bounds + int nameLength = Utf8.encodedLength(deployATTransactionData.getName()); + if (nameLength < 1 || nameLength > MAX_NAME_SIZE) + return ValidationResult.INVALID_NAME_LENGTH; + + // Check description size bounds + int descriptionlength = Utf8.encodedLength(deployATTransactionData.getDescription()); + if (descriptionlength < 1 || descriptionlength > MAX_DESCRIPTION_SIZE) + return ValidationResult.INVALID_DESCRIPTION_LENGTH; + + // Check AT-type size bounds + int ATTypeLength = Utf8.encodedLength(deployATTransactionData.getATType()); + if (ATTypeLength < 1 || ATTypeLength > MAX_AT_TYPE_SIZE) + return ValidationResult.INVALID_AT_TYPE_LENGTH; + + // Check tags size bounds + int tagsLength = Utf8.encodedLength(deployATTransactionData.getTags()); + if (tagsLength < 1 || tagsLength > MAX_TAGS_SIZE) + return ValidationResult.INVALID_TAGS_LENGTH; + + // Check amount is positive + if (deployATTransactionData.getAmount().compareTo(BigDecimal.ZERO) <= 0) + return ValidationResult.NEGATIVE_AMOUNT; + + // Check fee is positive + if (deployATTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) + return ValidationResult.NEGATIVE_FEE; + + // Check reference is correct + Account creator = getCreator(); + + if (!Arrays.equals(creator.getLastReference(), deployATTransactionData.getReference())) + return ValidationResult.INVALID_REFERENCE; + + // Check creator has enough funds + BigDecimal minimumBalance = deployATTransactionData.getFee().add(deployATTransactionData.getAmount()); + if (creator.getConfirmedBalance(Asset.QORA).compareTo(minimumBalance) < 0) + return ValidationResult.NO_BALANCE; + + // Check creation bytes are valid (for v3+) + byte[] creationBytes = deployATTransactionData.getCreationBytes(); + short version = (short) (creationBytes[0] | (creationBytes[1] << 8)); // Little-endian + + if (version >= 3) { + // Do actual validation + } else { + // Skip validation for old, dead ATs + } + + return ValidationResult.OK; + } + + @Override + public void process() throws DataException { + ensureATAddress(); + + // Deploy AT, saving into repository + AT at = new AT(this.repository, this.deployATTransactionData); + at.deploy(); + + // Save this transaction itself + this.repository.getTransactionRepository().save(this.transactionData); + + // Update creator's balance + Account creator = getCreator(); + creator.setConfirmedBalance(Asset.QORA, creator.getConfirmedBalance(Asset.QORA).subtract(deployATTransactionData.getAmount())); + creator.setConfirmedBalance(Asset.QORA, creator.getConfirmedBalance(Asset.QORA).subtract(deployATTransactionData.getFee())); + + // Update creator's reference + creator.setLastReference(deployATTransactionData.getSignature()); + } + + @Override + public void orphan() throws DataException { + // Delete AT from repository + AT at = new AT(this.repository, this.deployATTransactionData); + at.undeploy(); + + // Delete this transaction itself + this.repository.getTransactionRepository().delete(deployATTransactionData); + + // Update creator's balance + Account creator = getCreator(); + creator.setConfirmedBalance(Asset.QORA, creator.getConfirmedBalance(Asset.QORA).add(deployATTransactionData.getAmount())); + creator.setConfirmedBalance(Asset.QORA, creator.getConfirmedBalance(Asset.QORA).add(deployATTransactionData.getFee())); + + // Update creator's reference + creator.setLastReference(deployATTransactionData.getReference()); + } + +} diff --git a/src/qora/transaction/GenesisTransaction.java b/src/qora/transaction/GenesisTransaction.java index ed2330b8..d3ae1bc1 100644 --- a/src/qora/transaction/GenesisTransaction.java +++ b/src/qora/transaction/GenesisTransaction.java @@ -118,7 +118,7 @@ public class GenesisTransaction extends Transaction { @Override public ValidationResult isValid() { // Check amount is zero or positive - if (genesisTransactionData.getAmount().compareTo(BigDecimal.ZERO) >= 0) + if (genesisTransactionData.getAmount().compareTo(BigDecimal.ZERO) < 0) return ValidationResult.NEGATIVE_AMOUNT; // Check recipient address is valid diff --git a/src/qora/transaction/Transaction.java b/src/qora/transaction/Transaction.java index 42b7c418..aaba8836 100644 --- a/src/qora/transaction/Transaction.java +++ b/src/qora/transaction/Transaction.java @@ -24,8 +24,27 @@ public abstract class Transaction { // Transaction types public enum TransactionType { - GENESIS(1), PAYMENT(2), REGISTER_NAME(3), UPDATE_NAME(4), SELL_NAME(5), CANCEL_SELL_NAME(6), BUY_NAME(7), CREATE_POLL(8), VOTE_ON_POLL(9), ARBITRARY( - 10), ISSUE_ASSET(11), TRANSFER_ASSET(12), CREATE_ASSET_ORDER(13), CANCEL_ASSET_ORDER(14), MULTIPAYMENT(15), DEPLOY_AT(16), MESSAGE(17); + GENESIS(1), + PAYMENT(2), + REGISTER_NAME(3), + UPDATE_NAME(4), + SELL_NAME(5), + CANCEL_SELL_NAME(6), + BUY_NAME(7), + CREATE_POLL(8), + VOTE_ON_POLL(9), + ARBITRARY(10), + ISSUE_ASSET(11), + TRANSFER_ASSET(12), + CREATE_ASSET_ORDER(13), + CANCEL_ASSET_ORDER(14), + MULTIPAYMENT(15), + DEPLOY_AT(16), + MESSAGE(17), + DELEGATION(18), + SUPERNODE(19), + AIRDROP(20), + AT(21); public final int value; @@ -42,14 +61,44 @@ public abstract class Transaction { // Validation results public enum ValidationResult { - OK(1), INVALID_ADDRESS(2), NEGATIVE_AMOUNT(3), NEGATIVE_FEE(4), NO_BALANCE(5), INVALID_REFERENCE(6), INVALID_NAME_LENGTH(7), INVALID_VALUE_LENGTH( - 8), NAME_ALREADY_REGISTERED(9), NAME_DOES_NOT_EXIST(10), INVALID_NAME_OWNER(11), NAME_ALREADY_FOR_SALE(12), NAME_NOT_FOR_SALE( - 13), BUYER_ALREADY_OWNER(14), INVALID_AMOUNT(15), INVALID_SELLER(16), NAME_NOT_LOWER_CASE(17), INVALID_DESCRIPTION_LENGTH( - 18), INVALID_OPTIONS_COUNT(19), INVALID_OPTION_LENGTH(20), DUPLICATE_OPTION(21), POLL_ALREADY_EXISTS(22), POLL_DOES_NOT_EXIST( - 24), POLL_OPTION_DOES_NOT_EXIST(25), ALREADY_VOTED_FOR_THAT_OPTION(26), INVALID_DATA_LENGTH(27), INVALID_QUANTITY( - 28), ASSET_DOES_NOT_EXIST(29), INVALID_RETURN(30), HAVE_EQUALS_WANT(31), ORDER_DOES_NOT_EXIST( - 32), INVALID_ORDER_CREATOR(33), INVALID_PAYMENTS_COUNT( - 34), NEGATIVE_PRICE(35), ASSET_ALREADY_EXISTS(43), NOT_YET_RELEASED(1000); + OK(1), + INVALID_ADDRESS(2), + NEGATIVE_AMOUNT(3), + NEGATIVE_FEE(4), + NO_BALANCE(5), + INVALID_REFERENCE(6), + INVALID_NAME_LENGTH(7), + INVALID_VALUE_LENGTH(8), + NAME_ALREADY_REGISTERED(9), + NAME_DOES_NOT_EXIST(10), + INVALID_NAME_OWNER(11), + NAME_ALREADY_FOR_SALE(12), + NAME_NOT_FOR_SALE(13), + BUYER_ALREADY_OWNER(14), + INVALID_AMOUNT(15), + INVALID_SELLER(16), + NAME_NOT_LOWER_CASE(17), + INVALID_DESCRIPTION_LENGTH(18), + INVALID_OPTIONS_COUNT(19), + INVALID_OPTION_LENGTH(20), + DUPLICATE_OPTION(21), + POLL_ALREADY_EXISTS(22), + POLL_DOES_NOT_EXIST(24), + POLL_OPTION_DOES_NOT_EXIST(25), + ALREADY_VOTED_FOR_THAT_OPTION(26), + INVALID_DATA_LENGTH(27), + INVALID_QUANTITY(28), + ASSET_DOES_NOT_EXIST(29), + INVALID_RETURN(30), + HAVE_EQUALS_WANT(31), + ORDER_DOES_NOT_EXIST(32), + INVALID_ORDER_CREATOR(33), + INVALID_PAYMENTS_COUNT(34), + NEGATIVE_PRICE(35), + INVALID_TAGS_LENGTH(37), + INVALID_AT_TYPE_LENGTH(38), + ASSET_ALREADY_EXISTS(43), + NOT_YET_RELEASED(1000); public final int value; @@ -147,6 +196,9 @@ public abstract class Transaction { case MESSAGE: return new MessageTransaction(repository, transactionData); + case DEPLOY_AT: + return new DeployATTransaction(repository, transactionData); + default: throw new IllegalStateException("Unsupported transaction type [" + transactionData.getType().value + "] during fetch from repository"); } diff --git a/src/qora/transaction/TransferAssetTransaction.java b/src/qora/transaction/TransferAssetTransaction.java index 40bda07b..5325c9f0 100644 --- a/src/qora/transaction/TransferAssetTransaction.java +++ b/src/qora/transaction/TransferAssetTransaction.java @@ -84,9 +84,9 @@ public class TransferAssetTransaction extends Transaction { @Override public ValidationResult isValid() throws DataException { - // Are IssueAssetTransactions even allowed at this point? + // Are TransferAssetTransactions even allowed at this point? // XXX In gen1 this used NTP.getTime() but surely the transaction's timestamp should be used? - if (this.transferAssetTransactionData.getTimestamp() < BlockChain.getVotingReleaseTimestamp()) + if (this.transferAssetTransactionData.getTimestamp() < BlockChain.getAssetsReleaseTimestamp()) return ValidationResult.NOT_YET_RELEASED; // Check reference is correct diff --git a/src/repository/ATRepository.java b/src/repository/ATRepository.java new file mode 100644 index 00000000..fef0c76e --- /dev/null +++ b/src/repository/ATRepository.java @@ -0,0 +1,24 @@ +package repository; + +import data.at.ATData; +import data.at.ATStateData; + +public interface ATRepository { + + // CIYAM AutomatedTransactions + + public ATData fromATAddress(String atAddress) throws DataException; + + public void save(ATData atData) throws DataException; + + public void delete(String atAddress) throws DataException; + + // AT States + + public ATStateData getATState(String atAddress, int height) throws DataException; + + public void save(ATStateData atStateData) throws DataException; + + public void delete(String atAddress, int height) throws DataException; + +} diff --git a/src/repository/Repository.java b/src/repository/Repository.java index fa969947..f5bc16ba 100644 --- a/src/repository/Repository.java +++ b/src/repository/Repository.java @@ -2,6 +2,8 @@ package repository; public interface Repository extends AutoCloseable { + public ATRepository getATRepository(); + public AccountRepository getAccountRepository(); public AssetRepository getAssetRepository(); diff --git a/src/repository/hsqldb/HSQLDBATRepository.java b/src/repository/hsqldb/HSQLDBATRepository.java new file mode 100644 index 00000000..5c2cc88d --- /dev/null +++ b/src/repository/hsqldb/HSQLDBATRepository.java @@ -0,0 +1,117 @@ +package repository.hsqldb; + +import java.math.BigDecimal; +import java.sql.ResultSet; +import java.sql.SQLException; + +import data.at.ATData; +import data.at.ATStateData; +import repository.ATRepository; +import repository.DataException; + +public class HSQLDBATRepository implements ATRepository { + + protected HSQLDBRepository repository; + + public HSQLDBATRepository(HSQLDBRepository repository) { + this.repository = repository; + } + + // ATs + + @Override + public ATData fromATAddress(String atAddress) throws DataException { + try (ResultSet resultSet = this.repository + .checkedExecute("SELECT owner, asset_name, description, quantity, is_divisible, reference FROM Assets WHERE AT_address = ?", atAddress)) { + if (resultSet == null) + return null; + + int version = resultSet.getInt(1); + byte[] codeBytes = resultSet.getBytes(2); // Actually BLOB + boolean isSleeping = resultSet.getBoolean(3); + + Integer sleepUntilHeight = resultSet.getInt(4); + if (resultSet.wasNull()) + sleepUntilHeight = null; + + boolean isFinished = resultSet.getBoolean(5); + boolean hadFatalError = resultSet.getBoolean(6); + boolean isFrozen = resultSet.getBoolean(7); + + BigDecimal frozenBalance = resultSet.getBigDecimal(8); + if (resultSet.wasNull()) + frozenBalance = null; + + byte[] deploySignature = resultSet.getBytes(9); + + return new ATData(atAddress, version, codeBytes, isSleeping, sleepUntilHeight, isFinished, hadFatalError, isFrozen, frozenBalance, deploySignature); + } catch (SQLException e) { + throw new DataException("Unable to fetch AT from repository", e); + } + } + + @Override + public void save(ATData atData) throws DataException { + HSQLDBSaver saveHelper = new HSQLDBSaver("ATs"); + + saveHelper.bind("AT_address", atData.getATAddress()).bind("version", atData.getVersion()).bind("code_bytes", atData.getCodeBytes()) + .bind("is_sleeping", atData.getIsSleeping()).bind("sleep_until_height", atData.getSleepUntilHeight()) + .bind("is_finished", atData.getIsFinished()).bind("had_fatal_error", atData.getHadFatalError()).bind("is_frozen", atData.getIsFrozen()) + .bind("frozen_balance", atData.getFrozenBalance()).bind("deploy_signature", atData.getDeploySignature()); + + try { + saveHelper.execute(this.repository); + } catch (SQLException e) { + throw new DataException("Unable to save AT into repository", e); + } + } + + @Override + public void delete(String atAddress) throws DataException { + try { + this.repository.delete("ATs", "atAddress = ?", atAddress); + // AT States also deleted via ON DELETE CASCADE + } catch (SQLException e) { + throw new DataException("Unable to delete AT from repository", e); + } + } + + // AT State + + @Override + public ATStateData getATState(String atAddress, int height) throws DataException { + try (ResultSet resultSet = this.repository.checkedExecute("SELECT state_data FROM ATStates WHERE AT_address = ? AND height = ?", atAddress, height)) { + if (resultSet == null) + return null; + + byte[] stateData = resultSet.getBytes(1); // Actually BLOB + + return new ATStateData(atAddress, height, stateData); + } catch (SQLException e) { + throw new DataException("Unable to fetch AT State from repository", e); + } + } + + @Override + public void save(ATStateData atStateData) throws DataException { + HSQLDBSaver saveHelper = new HSQLDBSaver("ATStates"); + + saveHelper.bind("AT_address", atStateData.getATAddress()).bind("height", atStateData.getHeight()).bind("state_data", atStateData.getStateData()); + + try { + saveHelper.execute(this.repository); + } catch (SQLException e) { + throw new DataException("Unable to save AT State into repository", e); + } + } + + @Override + public void delete(String atAddress, int height) throws DataException { + try { + this.repository.delete("ATStates", "AT_address = ? AND height = ?", atAddress, height); + } catch (SQLException e) { + throw new DataException("Unable to delete AT State from repository", e); + } + } + +} diff --git a/src/repository/hsqldb/HSQLDBDatabaseUpdates.java b/src/repository/hsqldb/HSQLDBDatabaseUpdates.java index 836b2a1e..f3f1dba7 100644 --- a/src/repository/hsqldb/HSQLDBDatabaseUpdates.java +++ b/src/repository/hsqldb/HSQLDBDatabaseUpdates.java @@ -77,7 +77,8 @@ public class HSQLDBDatabaseUpdates { stmt.execute("SET DATABASE COLLATION SQL_TEXT NO PAD"); stmt.execute("CREATE COLLATION SQL_TEXT_UCC_NO_PAD FOR SQL_TEXT FROM SQL_TEXT_UCC NO PAD"); stmt.execute("CREATE COLLATION SQL_TEXT_NO_PAD FOR SQL_TEXT FROM SQL_TEXT NO PAD"); - stmt.execute("SET FILES SPACE TRUE"); + stmt.execute("SET FILES SPACE TRUE"); // Enable per-table block space within .data file, useful for CACHED table types + stmt.execute("SET FILES LOB SCALE 1"); // LOB granularity is 1KB stmt.execute("CREATE TABLE DatabaseInfo ( version INTEGER NOT NULL )"); stmt.execute("INSERT INTO DatabaseInfo VALUES ( 0 )"); stmt.execute("CREATE TYPE BlockSignature AS VARBINARY(128)"); @@ -96,6 +97,9 @@ public class HSQLDBDatabaseUpdates { stmt.execute("CREATE TYPE AssetOrderID AS VARBINARY(64)"); stmt.execute("CREATE TYPE ATName AS VARCHAR(200) COLLATE SQL_TEXT_UCC_NO_PAD"); stmt.execute("CREATE TYPE ATType AS VARCHAR(200) COLLATE SQL_TEXT_UCC_NO_PAD"); + stmt.execute("CREATE TYPE ATCode AS BLOB(64K)"); // 16bit * 1 + stmt.execute("CREATE TYPE ATState AS BLOB(1M)"); // 16bit * 8 + 16bit * 4 + 16bit * 4 + stmt.execute("CREATE TYPE ATMessage AS VARBINARY(256)"); break; case 1: @@ -269,7 +273,7 @@ public class HSQLDBDatabaseUpdates { // Deploy CIYAM AT Transactions stmt.execute("CREATE TABLE DeployATTransactions (signature Signature, creator QoraPublicKey NOT NULL, AT_name ATName NOT NULL, " + "description VARCHAR(2000) NOT NULL, AT_type ATType NOT NULL, AT_tags VARCHAR(200) NOT NULL, " - + "creation_bytes VARBINARY(100000) NOT NULL, amount QoraAmount NOT NULL, " + + "creation_bytes VARBINARY(100000) NOT NULL, amount QoraAmount NOT NULL, AT_address QoraAddress, " + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)"); break; @@ -346,6 +350,22 @@ public class HSQLDBDatabaseUpdates { + "PRIMARY KEY (name))"); break; + case 27: + // CIYAM Automated Transactions + stmt.execute("CREATE TABLE ATs (AT_address QoraAddress, version INTEGER NOT NULL, code_bytes ATCode NOT NULL, " + + "is_sleeping BOOLEAN NOT NULL, sleep_until_height INTEGER, is_finished BOOLEAN NOT NULL, had_fatal_error BOOLEAN NOT NULL, " + + "is_frozen BOOLEAN NOT NULL, frozen_balance QoraAmount, deploy_signature Signature NOT NULL, PRIMARY key (AT_address))"); + // For finding executable ATs + stmt.execute("CREATE INDEX ATIndex on ATs (is_finished, AT_address)"); + // AT state on a per-block basis + stmt.execute("CREATE TABLE ATStates (AT_address QoraAddress, height INTEGER NOT NULL, state_data ATState, " + + "PRIMARY KEY (AT_address, height), FOREIGN KEY (AT_address) REFERENCES ATs (AT_address) ON DELETE CASCADE)"); + // Generated AT Transactions + stmt.execute( + "CREATE TABLE ATTransactions (signature Signature, sender QoraPublicKey NOT NULL, recipient QoraAddress, amount QoraAmount NOT NULL, message ATMessage, " + + "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)"); + break; + default: // nothing to do return false; diff --git a/src/repository/hsqldb/HSQLDBRepository.java b/src/repository/hsqldb/HSQLDBRepository.java index 3081ee55..fc181eea 100644 --- a/src/repository/hsqldb/HSQLDBRepository.java +++ b/src/repository/hsqldb/HSQLDBRepository.java @@ -11,6 +11,7 @@ import java.util.TimeZone; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import repository.ATRepository; import repository.AccountRepository; import repository.AssetRepository; import repository.BlockRepository; @@ -34,6 +35,11 @@ public class HSQLDBRepository implements Repository { this.connection = connection; } + @Override + public ATRepository getATRepository() { + return new HSQLDBATRepository(this); + } + @Override public AccountRepository getAccountRepository() { return new HSQLDBAccountRepository(this); @@ -84,6 +90,12 @@ public class HSQLDBRepository implements Repository { @Override public void close() throws DataException { + // Already closed? No need to do anything but maybe report double-call + if (this.connection == null) { + LOGGER.warn("HSQLDBRepository.close() called when repository already closed", new Exception("Repository already closed")); + return; + } + try (Statement stmt = this.connection.createStatement()) { // Diagnostic check for uncommitted changes if (!stmt.execute("SELECT transaction, transaction_size FROM information_schema.system_sessions")) // TRANSACTION_SIZE() broken? @@ -96,7 +108,7 @@ public class HSQLDBRepository implements Repository { boolean inTransaction = resultSet.getBoolean(1); int transactionCount = resultSet.getInt(2); if (inTransaction && transactionCount != 0) - LOGGER.warn("Uncommitted changes (" + transactionCount + ") during repository close"); + LOGGER.warn("Uncommitted changes (" + transactionCount + ") during repository close", new Exception("Uncommitted repository changes")); } // give connection back to the pool diff --git a/src/repository/hsqldb/transaction/HSQLDBDeployATTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBDeployATTransactionRepository.java new file mode 100644 index 00000000..a4d54d41 --- /dev/null +++ b/src/repository/hsqldb/transaction/HSQLDBDeployATTransactionRepository.java @@ -0,0 +1,63 @@ +package repository.hsqldb.transaction; + +import java.math.BigDecimal; +import java.sql.ResultSet; +import java.sql.SQLException; + +import data.transaction.DeployATTransactionData; +import data.transaction.TransactionData; +import repository.DataException; +import repository.hsqldb.HSQLDBRepository; +import repository.hsqldb.HSQLDBSaver; + +public class HSQLDBDeployATTransactionRepository extends HSQLDBTransactionRepository { + + public HSQLDBDeployATTransactionRepository(HSQLDBRepository repository) { + this.repository = repository; + } + + TransactionData fromBase(byte[] signature, byte[] reference, byte[] creatorPublicKey, long timestamp, BigDecimal fee) throws DataException { + try (ResultSet resultSet = this.repository.checkedExecute( + "SELECT AT_name, description, AT_type, AT_tags, creation_bytes, amount, AT_address FROM DeployATTransactions WHERE signature = ?", signature)) { + if (resultSet == null) + return null; + + String name = resultSet.getString(1); + String description = resultSet.getString(2); + String ATType = resultSet.getString(3); + String tags = resultSet.getString(4); + byte[] creationBytes = resultSet.getBytes(5); + BigDecimal amount = resultSet.getBigDecimal(6).setScale(8); + + // Special null-checking for AT address + String ATAddress = resultSet.getString(7); + if (resultSet.wasNull()) + ATAddress = null; + + return new DeployATTransactionData(ATAddress, creatorPublicKey, name, description, ATType, tags, creationBytes, amount, fee, timestamp, reference, + signature); + } catch (SQLException e) { + throw new DataException("Unable to fetch deploy AT transaction from repository", e); + } + } + + @Override + public void save(TransactionData transactionData) throws DataException { + DeployATTransactionData deployATTransactionData = (DeployATTransactionData) transactionData; + + HSQLDBSaver saveHelper = new HSQLDBSaver("DeployATTransactions"); + + saveHelper.bind("signature", deployATTransactionData.getSignature()).bind("creator", deployATTransactionData.getCreatorPublicKey()) + .bind("AT_name", deployATTransactionData.getName()).bind("description", deployATTransactionData.getDescription()) + .bind("AT_type", deployATTransactionData.getATType()).bind("AT_tags", deployATTransactionData.getTags()) + .bind("creation_bytes", deployATTransactionData.getCreationBytes()).bind("amount", deployATTransactionData.getAmount()) + .bind("AT_address", deployATTransactionData.getATAddress()); + + try { + saveHelper.execute(this.repository); + } catch (SQLException e) { + throw new DataException("Unable to save deploy AT transaction into repository", e); + } + } + +} diff --git a/src/repository/hsqldb/transaction/HSQLDBTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBTransactionRepository.java index f6d7fc59..316ee6a7 100644 --- a/src/repository/hsqldb/transaction/HSQLDBTransactionRepository.java +++ b/src/repository/hsqldb/transaction/HSQLDBTransactionRepository.java @@ -35,6 +35,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository { private HSQLDBCreateOrderTransactionRepository createOrderTransactionRepository; private HSQLDBCancelOrderTransactionRepository cancelOrderTransactionRepository; private HSQLDBMultiPaymentTransactionRepository multiPaymentTransactionRepository; + private HSQLDBDeployATTransactionRepository deployATTransactionRepository; private HSQLDBMessageTransactionRepository messageTransactionRepository; public HSQLDBTransactionRepository(HSQLDBRepository repository) { @@ -54,6 +55,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository { this.createOrderTransactionRepository = new HSQLDBCreateOrderTransactionRepository(repository); this.cancelOrderTransactionRepository = new HSQLDBCancelOrderTransactionRepository(repository); this.multiPaymentTransactionRepository = new HSQLDBMultiPaymentTransactionRepository(repository); + this.deployATTransactionRepository = new HSQLDBDeployATTransactionRepository(repository); this.messageTransactionRepository = new HSQLDBMessageTransactionRepository(repository); } @@ -146,6 +148,9 @@ public class HSQLDBTransactionRepository implements TransactionRepository { case MULTIPAYMENT: return this.multiPaymentTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee); + case DEPLOY_AT: + return this.deployATTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee); + case MESSAGE: return this.messageTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee); @@ -304,6 +309,10 @@ public class HSQLDBTransactionRepository implements TransactionRepository { this.multiPaymentTransactionRepository.save(transactionData); break; + case DEPLOY_AT: + this.deployATTransactionRepository.save(transactionData); + break; + case MESSAGE: this.messageTransactionRepository.save(transactionData); break; diff --git a/src/test/ATTests.java b/src/test/ATTests.java new file mode 100644 index 00000000..076bb927 --- /dev/null +++ b/src/test/ATTests.java @@ -0,0 +1,96 @@ +package test; + +import static org.junit.Assert.*; + +import java.math.BigDecimal; +import java.util.Arrays; + +import org.junit.Test; + +import com.google.common.hash.HashCode; + +import data.block.BlockData; +import data.block.BlockTransactionData; +import data.transaction.DeployATTransactionData; +import qora.transaction.DeployATTransaction; +import repository.DataException; +import repository.Repository; +import repository.RepositoryManager; +import transform.TransformationException; +import utils.Base58; + +public class ATTests extends Common { + + @Test + public void testATAccount() throws TransformationException, DataException { + // 2dZ4megUyNoYYY7qWmuSd4xw1yUKgPPF97yBbeddh8aKuC8PLpz7Xvf3r6Zjv1zwGrR8fEAHuaztCPD4KQp76KdL at height 125598 + // AT address: AaaUn82XV4YcUtsQ3rHa5ZgqyiK35rVfE3 + + String expectedAddress = "AaaUn82XV4YcUtsQ3rHa5ZgqyiK35rVfE3"; + + byte[] creatorPublicKey = HashCode.fromString("c74d71ecec6b37890f26573186e634986cc90a507af01749f92aa2c7c95ad05f").asBytes(); + String name = "QORABURST @ 1.00"; + String description = "Initiators BURST address: BURST-LKGW-Z2JK-EZ99-E7CUE"; + String ATType = "acct"; + String tags = "acct,atomic cross chain tx,initiate,initiator"; + byte[] creationBytes = HashCode + .fromString("010000000100010000000000" + "0094357700" + "000000bf" + + "3501030900000006040000000900000029302009000000040000000f1ab4000000330403090000003525010a000000260a000000320903350703090000003526010a0000001b0a000000cd322801331601000000003317010100000033180102000000331901030000003505020a0000001b0a000000a1320b033205041e050000001833000509000000320a033203041ab400000033160105000000331701060000003318010700000033190108000000320304320b033203041ab7" + + "00000048" + + "5e211280259d2f3130248482c2dfc53be2fd5f9bedc9bc21425f951e8097a21900000000c80000003ac8716ad810191acf270d22e9f47f27806256c10d6ba6144900000000000000") + .asBytes(); + BigDecimal amount = BigDecimal.valueOf(500.0).setScale(8); + BigDecimal fee = BigDecimal.valueOf(20.0).setScale(8); + long timestamp = 1439997077932L; + byte[] reference = Base58.decode("2D3jX1pEgu6irsQ7QzJb85QP1D9M45dNyP5M9a3WFHndU5ZywF4F5pnUurcbzMnGMcTwpAY6H7DuLw8cUBU66ao1"); + byte[] signature = Base58.decode("2dZ4megUyNoYYY7qWmuSd4xw1yUKgPPF97yBbeddh8aKuC8PLpz7Xvf3r6Zjv1zwGrR8fEAHuaztCPD4KQp76KdL"); + + DeployATTransactionData transactionData = new DeployATTransactionData(creatorPublicKey, name, description, ATType, tags, creationBytes, amount, fee, + timestamp, reference, signature); + + try (final Repository repository = RepositoryManager.getRepository()) { + repository.getTransactionRepository().save(transactionData); + + DeployATTransaction transaction = new DeployATTransaction(repository, transactionData); + + // Fake entry for this transaction at block height 125598 if it doesn't already exist + if (transaction.getHeight() == 0) { + byte[] blockSignature = Base58.decode( + "2amu634LnAbxeLfDtWdTLiCWtKu1XM2XLK9o6fDM7yGNNoh5Tq2KxSLdx8AS486zUU1wYNGCm8mcGxjMiww979MxdPVB2PQzaKrW2aFn9hpdSNN6Nk7EmeYKwsZdx9tkpHfBt5thSrUUrhzXJju9KYCAP6p3Ty4zccFkaxCP15j332U"); + byte[] generatorSignature = Arrays.copyOfRange(blockSignature, 0, 64); + byte[] transactionsSignature = Arrays.copyOfRange(blockSignature, 64, 128); + + // Check block exists too + if (repository.getBlockRepository().fromSignature(blockSignature) == null) { + int version = 2; + byte[] blockReference = blockSignature; + int transactionCount = 0; + BigDecimal totalFees = BigDecimal.valueOf(70.0).setScale(8); + int height = 125598; + long blockTimestamp = 1439997158336L; + BigDecimal generatingBalance = BigDecimal.valueOf(1440368826L).setScale(8); + byte[] generatorPublicKey = Base58.decode("X4s833bbtghh7gejmaBMbWqD44HrUobw93ANUuaNhFc"); + byte[] atBytes = HashCode.fromString("17950a6c62d17ff0caa545651c054a105f1c464daca443df846cc6a3d58f764b78c09cff50f0fd9ec2").asBytes(); + BigDecimal atFees = BigDecimal.valueOf(50.0).setScale(8); + + BlockData blockData = new BlockData(version, blockReference, transactionCount, totalFees, transactionsSignature, height, blockTimestamp, + generatingBalance, generatorPublicKey, generatorSignature, atBytes, atFees); + + repository.getBlockRepository().save(blockData); + } + + int sequence = 0; + + BlockTransactionData blockTransactionData = new BlockTransactionData(blockSignature, sequence, signature); + repository.getBlockRepository().save(blockTransactionData); + } + + String actualAddress = transaction.getATAccount().getAddress(); + + repository.discardChanges(); + + assertEquals(expectedAddress, actualAddress); + } + } + +} diff --git a/src/test/RepositoryTests.java b/src/test/RepositoryTests.java index e2df05ec..a37b71c6 100644 --- a/src/test/RepositoryTests.java +++ b/src/test/RepositoryTests.java @@ -2,6 +2,8 @@ package test; import static org.junit.Assert.*; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.junit.Test; import repository.DataException; @@ -10,6 +12,8 @@ import repository.RepositoryManager; public class RepositoryTests extends Common { + private static final Logger LOGGER = LogManager.getLogger(RepositoryTests.class); + @Test public void testGetRepository() throws DataException { try (final Repository repository = RepositoryManager.getRepository()) { @@ -45,6 +49,8 @@ public class RepositoryTests extends Common { fail(); } catch (NullPointerException | DataException e) { } + + LOGGER.warn("Expect \"repository already closed\" complaint below"); } } diff --git a/src/test/SaveTests.java b/src/test/SaveTests.java index e57ab170..1338e3b8 100644 --- a/src/test/SaveTests.java +++ b/src/test/SaveTests.java @@ -27,6 +27,8 @@ public class SaveTests extends Common { BigDecimal.ONE, Instant.now().getEpochSecond(), reference, signature); repository.getTransactionRepository().save(paymentTransactionData); + + repository.discardChanges(); } } diff --git a/src/test/TransactionTests.java b/src/test/TransactionTests.java index 9365f91f..f976c59f 100644 --- a/src/test/TransactionTests.java +++ b/src/test/TransactionTests.java @@ -4,6 +4,7 @@ import static org.junit.Assert.*; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; +import java.math.RoundingMode; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -616,7 +617,7 @@ public class TransactionTests { String assetName = "test asset"; String description = "test asset description"; long quantity = 1_000_000L; - boolean isDivisible = false; + boolean isDivisible = true; BigDecimal fee = BigDecimal.ONE; long timestamp = parentBlockData.getTimestamp() + 1_000; @@ -956,16 +957,20 @@ public class TransactionTests { assertNotNull(originalOrderData); assertFalse(originalOrderData.getIsClosed()); + // Unfulfilled order: "buyer" has 10 QORA and wants to buy "test asset" at a price of 50 "test asset" per QORA. + // buyer's order: have=QORA, amount=10, want=test-asset, price=50 (test-asset per QORA, so max return is 500 test-asset) + // Original asset owner (sender) will sell asset to "buyer" // Order: seller has 40 "test asset" and wants to buy QORA at a price of 1/60 QORA per "test asset". // This order should be a partial match for original order, and at a better price than asked - long haveAssetId = Asset.QORA; + long haveAssetId = assetId; BigDecimal amount = BigDecimal.valueOf(40).setScale(8); - long wantAssetId = assetId; - BigDecimal price = BigDecimal.ONE.setScale(8).divide(BigDecimal.valueOf(60).setScale(8)); + long wantAssetId = Asset.QORA; + BigDecimal price = BigDecimal.ONE.setScale(8).divide(BigDecimal.valueOf(60).setScale(8), RoundingMode.DOWN); BigDecimal fee = BigDecimal.ONE; long timestamp = parentBlockData.getTimestamp() + 1_000; + BigDecimal senderPreTradeWantBalance = sender.getConfirmedBalance(wantAssetId); CreateOrderTransactionData createOrderTransactionData = new CreateOrderTransactionData(sender.getPublicKey(), haveAssetId, wantAssetId, amount, price, fee, timestamp, reference); @@ -989,20 +994,19 @@ public class TransactionTests { byte[] orderId = createOrderTransactionData.getSignature(); OrderData orderData = assetRepo.fromOrderId(orderId); assertNotNull(orderData); - assertFalse(orderData.getIsFulfilled()); // Check order has trades List trades = assetRepo.getOrdersTrades(orderId); assertNotNull(trades); - assertEquals(1, trades.size()); + assertEquals("Trade didn't happen", 1, trades.size()); TradeData tradeData = trades.get(0); // Check trade has correct values - BigDecimal expectedAmount = amount.multiply(price); + BigDecimal expectedAmount = amount.divide(originalOrderData.getPrice()).setScale(8); BigDecimal actualAmount = tradeData.getAmount(); assertTrue(expectedAmount.compareTo(actualAmount) == 0); - BigDecimal expectedPrice = originalOrderData.getPrice().multiply(amount); + BigDecimal expectedPrice = amount; BigDecimal actualPrice = tradeData.getPrice(); assertTrue(expectedPrice.compareTo(actualPrice) == 0); @@ -1017,10 +1021,17 @@ public class TransactionTests { assertTrue(expectedBalance.compareTo(actualBalance) == 0); // Check seller's QORA balance - expectedBalance = initialSenderBalance.subtract(BigDecimal.ONE).subtract(BigDecimal.ONE); + expectedBalance = senderPreTradeWantBalance.subtract(BigDecimal.ONE).add(expectedAmount); actualBalance = sender.getConfirmedBalance(wantAssetId); assertTrue(expectedBalance.compareTo(actualBalance) == 0); + // Check seller's order is correctly fulfilled + assertTrue(orderData.getIsFulfilled()); + + // Check buyer's order is still not fulfilled + OrderData buyersOrderData = assetRepo.fromOrderId(originalOrderData.getOrderId()); + assertFalse(buyersOrderData.getIsFulfilled()); + // Orphan transaction block.orphan(); repository.saveChanges(); diff --git a/src/transform/block/BlockTransformer.java b/src/transform/block/BlockTransformer.java index 0542948a..7d7336ae 100644 --- a/src/transform/block/BlockTransformer.java +++ b/src/transform/block/BlockTransformer.java @@ -289,6 +289,24 @@ public class BlockTransformer extends Transformer { } } + public static byte[] getBytesForGeneratorSignature(byte[] generatorSignature, BigDecimal generatingBalance, PublicKeyAccount generator) + throws TransformationException { + try { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(GENERATOR_SIGNATURE_LENGTH + GENERATING_BALANCE_LENGTH + GENERATOR_LENGTH); + + bytes.write(generatorSignature); + + bytes.write(Longs.toByteArray(generatingBalance.longValue())); + + // We're padding here just in case the generator is the genesis account whose public key is only 8 bytes long. + bytes.write(Bytes.ensureCapacity(generator.getPublicKey(), GENERATOR_LENGTH, 0)); + + return bytes.toByteArray(); + } catch (IOException e) { + throw new TransformationException(e); + } + } + public static byte[] getBytesForTransactionsSignature(Block block) throws TransformationException { ByteArrayOutputStream bytes = new ByteArrayOutputStream( GENERATOR_SIGNATURE_LENGTH + block.getBlockData().getTransactionCount() * TransactionTransformer.SIGNATURE_LENGTH); diff --git a/src/transform/transaction/DeployATTransactionTransformer.java b/src/transform/transaction/DeployATTransactionTransformer.java new file mode 100644 index 00000000..0699084f --- /dev/null +++ b/src/transform/transaction/DeployATTransactionTransformer.java @@ -0,0 +1,188 @@ +package transform.transaction; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigDecimal; +import java.nio.ByteBuffer; + +import org.json.simple.JSONObject; + +import com.google.common.base.Utf8; +import com.google.common.hash.HashCode; +import com.google.common.primitives.Ints; +import com.google.common.primitives.Longs; + +import data.transaction.TransactionData; +import qora.account.PublicKeyAccount; +import qora.block.BlockChain; +import qora.transaction.DeployATTransaction; +import data.transaction.DeployATTransactionData; +import transform.TransformationException; +import utils.Serialization; + +public class DeployATTransactionTransformer extends TransactionTransformer { + + // Property lengths + private static final int CREATOR_LENGTH = PUBLIC_KEY_LENGTH; + private static final int NAME_SIZE_LENGTH = INT_LENGTH; + private static final int DESCRIPTION_SIZE_LENGTH = INT_LENGTH; + private static final int AT_TYPE_SIZE_LENGTH = INT_LENGTH; + private static final int TAGS_SIZE_LENGTH = INT_LENGTH; + private static final int CREATION_BYTES_SIZE_LENGTH = INT_LENGTH; + private static final int AMOUNT_LENGTH = LONG_LENGTH; + + private static final int TYPELESS_LENGTH = BASE_TYPELESS_LENGTH + CREATOR_LENGTH + NAME_SIZE_LENGTH + DESCRIPTION_SIZE_LENGTH + AT_TYPE_SIZE_LENGTH + + TAGS_SIZE_LENGTH + CREATION_BYTES_SIZE_LENGTH + AMOUNT_LENGTH; + + static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException { + long timestamp = byteBuffer.getLong(); + + byte[] reference = new byte[REFERENCE_LENGTH]; + byteBuffer.get(reference); + + byte[] creatorPublicKey = Serialization.deserializePublicKey(byteBuffer); + + String name = Serialization.deserializeSizedString(byteBuffer, DeployATTransaction.MAX_NAME_SIZE); + + String description = Serialization.deserializeSizedString(byteBuffer, DeployATTransaction.MAX_DESCRIPTION_SIZE); + + String ATType = Serialization.deserializeSizedString(byteBuffer, DeployATTransaction.MAX_AT_TYPE_SIZE); + + String tags = Serialization.deserializeSizedString(byteBuffer, DeployATTransaction.MAX_TAGS_SIZE); + + int creationBytesSize = byteBuffer.getInt(); + if (creationBytesSize <= 0 || creationBytesSize > DeployATTransaction.MAX_CREATION_BYTES_SIZE) + throw new TransformationException("Creation bytes size invalid in DeployAT transaction"); + + byte[] creationBytes = new byte[creationBytesSize]; + byteBuffer.get(creationBytes); + + BigDecimal amount = Serialization.deserializeBigDecimal(byteBuffer); + + BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer); + + byte[] signature = new byte[SIGNATURE_LENGTH]; + byteBuffer.get(signature); + + return new DeployATTransactionData(creatorPublicKey, name, description, ATType, tags, creationBytes, amount, fee, timestamp, reference, signature); + } + + public static int getDataLength(TransactionData transactionData) throws TransformationException { + DeployATTransactionData deployATTransactionData = (DeployATTransactionData) transactionData; + + int dataLength = TYPE_LENGTH + TYPELESS_LENGTH + Utf8.encodedLength(deployATTransactionData.getName()) + + Utf8.encodedLength(deployATTransactionData.getDescription()) + Utf8.encodedLength(deployATTransactionData.getATType()) + + Utf8.encodedLength(deployATTransactionData.getTags()) + deployATTransactionData.getCreationBytes().length; + + return dataLength; + } + + public static byte[] toBytes(TransactionData transactionData) throws TransformationException { + try { + DeployATTransactionData deployATTransactionData = (DeployATTransactionData) transactionData; + + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + + bytes.write(Ints.toByteArray(deployATTransactionData.getType().value)); + bytes.write(Longs.toByteArray(deployATTransactionData.getTimestamp())); + bytes.write(deployATTransactionData.getReference()); + + bytes.write(deployATTransactionData.getCreatorPublicKey()); + + Serialization.serializeSizedString(bytes, deployATTransactionData.getName()); + + Serialization.serializeSizedString(bytes, deployATTransactionData.getDescription()); + + Serialization.serializeSizedString(bytes, deployATTransactionData.getATType()); + + Serialization.serializeSizedString(bytes, deployATTransactionData.getTags()); + + bytes.write(deployATTransactionData.getCreationBytes()); + + Serialization.serializeBigDecimal(bytes, deployATTransactionData.getAmount()); + + Serialization.serializeBigDecimal(bytes, deployATTransactionData.getFee()); + + if (deployATTransactionData.getSignature() != null) + bytes.write(deployATTransactionData.getSignature()); + + return bytes.toByteArray(); + } catch (IOException | ClassCastException e) { + throw new TransformationException(e); + } + } + + /** + * In Qora v1, the bytes used for verification omit AT-type and tags so we need to test for v1-ness and adjust the bytes + * accordingly. + * + * @param transactionData + * @return byte[] + * @throws TransformationException + */ + public static byte[] toBytesForSigningImpl(TransactionData transactionData) throws TransformationException { + if (transactionData.getTimestamp() >= BlockChain.getDeployATV2Timestamp()) + return TransactionTransformer.toBytesForSigningImpl(transactionData); + + // Special v1 version + + // Easier to start from scratch + try { + DeployATTransactionData deployATTransactionData = (DeployATTransactionData) transactionData; + + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + + bytes.write(Ints.toByteArray(deployATTransactionData.getType().value)); + bytes.write(Longs.toByteArray(deployATTransactionData.getTimestamp())); + bytes.write(deployATTransactionData.getReference()); + + bytes.write(deployATTransactionData.getCreatorPublicKey()); + + Serialization.serializeSizedString(bytes, deployATTransactionData.getName()); + + Serialization.serializeSizedString(bytes, deployATTransactionData.getDescription()); + + // Omitted: Serialization.serializeSizedString(bytes, deployATTransactionData.getATType()); + + // Omitted: Serialization.serializeSizedString(bytes, deployATTransactionData.getTags()); + + bytes.write(deployATTransactionData.getCreationBytes()); + + Serialization.serializeBigDecimal(bytes, deployATTransactionData.getAmount()); + + Serialization.serializeBigDecimal(bytes, deployATTransactionData.getFee()); + + if (deployATTransactionData.getSignature() != null) + bytes.write(deployATTransactionData.getSignature()); + + return bytes.toByteArray(); + } catch (IOException | ClassCastException e) { + throw new TransformationException(e); + } + } + + @SuppressWarnings("unchecked") + public static JSONObject toJSON(TransactionData transactionData) throws TransformationException { + JSONObject json = TransactionTransformer.getBaseJSON(transactionData); + + try { + DeployATTransactionData deployATTransactionData = (DeployATTransactionData) transactionData; + + byte[] creatorPublicKey = deployATTransactionData.getCreatorPublicKey(); + + json.put("creator", PublicKeyAccount.getAddress(creatorPublicKey)); + json.put("creatorPublicKey", HashCode.fromBytes(creatorPublicKey).toString()); + json.put("name", deployATTransactionData.getName()); + json.put("description", deployATTransactionData.getDescription()); + json.put("atType", deployATTransactionData.getATType()); + json.put("tags", deployATTransactionData.getTags()); + json.put("creationBytes", HashCode.fromBytes(deployATTransactionData.getCreationBytes()).toString()); + json.put("amount", deployATTransactionData.getAmount().toPlainString()); + } catch (ClassCastException e) { + throw new TransformationException(e); + } + + return json; + } + +} diff --git a/src/transform/transaction/IssueAssetTransactionTransformer.java b/src/transform/transaction/IssueAssetTransactionTransformer.java index 15b873e6..a4ec1b5d 100644 --- a/src/transform/transaction/IssueAssetTransactionTransformer.java +++ b/src/transform/transaction/IssueAssetTransactionTransformer.java @@ -99,9 +99,14 @@ public class IssueAssetTransactionTransformer extends TransactionTransformer { bytes.write(Longs.toByteArray(issueAssetTransactionData.getQuantity())); bytes.write((byte) (issueAssetTransactionData.getIsDivisible() ? 1 : 0)); - // In v1, IssueAssetTransaction uses Asset.toBytes which also serializes reference. - if (transactionData.getTimestamp() < BlockChain.getIssueAssetV2Timestamp()) - bytes.write(issueAssetTransactionData.getSignature()); + // In v1, IssueAssetTransaction uses Asset.toBytes which also serializes Asset's reference which is the IssueAssetTransaction's signature + if (transactionData.getTimestamp() < BlockChain.getIssueAssetV2Timestamp()) { + byte[] assetReference = issueAssetTransactionData.getSignature(); + if (assetReference != null) + bytes.write(assetReference); + else + bytes.write(new byte[ASSET_REFERENCE_LENGTH]); + } Serialization.serializeBigDecimal(bytes, issueAssetTransactionData.getFee()); diff --git a/src/transform/transaction/TransactionTransformer.java b/src/transform/transaction/TransactionTransformer.java index 3732f1ee..232ce38f 100644 --- a/src/transform/transaction/TransactionTransformer.java +++ b/src/transform/transaction/TransactionTransformer.java @@ -92,6 +92,9 @@ public class TransactionTransformer extends Transformer { case MESSAGE: return MessageTransactionTransformer.fromByteBuffer(byteBuffer); + case DEPLOY_AT: + return DeployATTransactionTransformer.fromByteBuffer(byteBuffer); + default: throw new TransformationException("Unsupported transaction type [" + type.value + "] during conversion from bytes"); } @@ -150,6 +153,9 @@ public class TransactionTransformer extends Transformer { case MESSAGE: return MessageTransactionTransformer.getDataLength(transactionData); + case DEPLOY_AT: + return DeployATTransactionTransformer.getDataLength(transactionData); + default: throw new TransformationException("Unsupported transaction type [" + transactionData.getType().value + "] when requesting byte length"); } @@ -205,6 +211,9 @@ public class TransactionTransformer extends Transformer { case MESSAGE: return MessageTransactionTransformer.toBytes(transactionData); + case DEPLOY_AT: + return DeployATTransactionTransformer.toBytes(transactionData); + default: throw new TransformationException("Unsupported transaction type [" + transactionData.getType().value + "] during conversion to bytes"); } @@ -269,6 +278,9 @@ public class TransactionTransformer extends Transformer { case MESSAGE: return MessageTransactionTransformer.toBytesForSigningImpl(transactionData); + case DEPLOY_AT: + return DeployATTransactionTransformer.toBytesForSigningImpl(transactionData); + default: throw new TransformationException( "Unsupported transaction type [" + transactionData.getType().value + "] during conversion to bytes for signing"); @@ -345,6 +357,9 @@ public class TransactionTransformer extends Transformer { case MESSAGE: return MessageTransactionTransformer.toJSON(transactionData); + case DEPLOY_AT: + return DeployATTransactionTransformer.toJSON(transactionData); + default: throw new TransformationException("Unsupported transaction type [" + transactionData.getType().value + "] during conversion to JSON"); } diff --git a/src/utils/Serialization.java b/src/utils/Serialization.java index 959c42af..c906ff87 100644 --- a/src/utils/Serialization.java +++ b/src/utils/Serialization.java @@ -17,6 +17,32 @@ public class Serialization { /** * Convert BigDecimal, unscaled, to byte[] then prepend with zero bytes to specified length. * + * @param amount + * @param length + * @return byte[] + * @throws IOException + */ + public static byte[] serializeBigDecimal(BigDecimal amount, int length) throws IOException { + byte[] amountBytes = amount.unscaledValue().toByteArray(); + byte[] output = new byte[length]; + System.arraycopy(amountBytes, 0, output, length - amountBytes.length, amountBytes.length); + return output; + } + + /** + * Convert BigDecimal, unscaled, to byte[] then prepend with zero bytes to fixed length of 8. + * + * @param amount + * @return byte[] + * @throws IOException + */ + public static byte[] serializeBigDecimal(BigDecimal amount) throws IOException { + return serializeBigDecimal(amount, 8); + } + + /** + * Write to ByteBuffer a BigDecimal, unscaled, prepended with zero bytes to specified length. + * * @param ByteArrayOutputStream * @param amount * @param length @@ -30,7 +56,7 @@ public class Serialization { } /** - * Convert BigDecimal, unscaled, to byte[] then prepend with zero bytes to fixed length of 8. + * Write to ByteBuffer a BigDecimal, unscaled, prepended with zero bytes to fixed length of 8. * * @param ByteArrayOutputStream * @param amount @@ -73,9 +99,6 @@ public class Serialization { } public static String deserializeSizedString(ByteBuffer byteBuffer, int maxSize) throws TransformationException { - if (byteBuffer.remaining() < Transformer.INT_LENGTH) - throw new TransformationException("Byte data too short for serialized string size"); - int size = byteBuffer.getInt(); if (size > maxSize) throw new TransformationException("Serialized string too long");