mirror of
https://github.com/Qortal/qortal.git
synced 2025-02-11 17:55:50 +00:00
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.
This commit is contained in:
parent
0aa0796f35
commit
e9d8b3e6e3
26
README.md
26
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.
|
BIN
lib/org/ciyam/at/1.0/at-1.0.jar
Normal file
BIN
lib/org/ciyam/at/1.0/at-1.0.jar
Normal file
Binary file not shown.
1
lib/org/ciyam/at/1.0/at-1.0.jar.md5
Normal file
1
lib/org/ciyam/at/1.0/at-1.0.jar.md5
Normal file
@ -0,0 +1 @@
|
||||
1d6f5d634a2c4e570a5a8af260a51653
|
1
lib/org/ciyam/at/1.0/at-1.0.jar.sha1
Normal file
1
lib/org/ciyam/at/1.0/at-1.0.jar.sha1
Normal file
@ -0,0 +1 @@
|
||||
c6387380bc5db1f0a98ecbb480b17bd89b564401
|
8
lib/org/ciyam/at/1.0/at-1.0.pom
Normal file
8
lib/org/ciyam/at/1.0/at-1.0.pom
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.ciyam</groupId>
|
||||
<artifactId>at</artifactId>
|
||||
<version>1.0</version>
|
||||
</project>
|
1
lib/org/ciyam/at/1.0/at-1.0.pom.md5
Normal file
1
lib/org/ciyam/at/1.0/at-1.0.pom.md5
Normal file
@ -0,0 +1 @@
|
||||
42f6e3eb3c6e510f65c963ce97583f05
|
1
lib/org/ciyam/at/1.0/at-1.0.pom.sha1
Normal file
1
lib/org/ciyam/at/1.0/at-1.0.pom.sha1
Normal file
@ -0,0 +1 @@
|
||||
490287647d3c69c05bd50ab565ffff86192ff423
|
12
lib/org/ciyam/at/maven-metadata.xml
Normal file
12
lib/org/ciyam/at/maven-metadata.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<metadata>
|
||||
<groupId>org.ciyam</groupId>
|
||||
<artifactId>at</artifactId>
|
||||
<versioning>
|
||||
<release>1.0</release>
|
||||
<versions>
|
||||
<version>1.0</version>
|
||||
</versions>
|
||||
<lastUpdated>20181003154752</lastUpdated>
|
||||
</versioning>
|
||||
</metadata>
|
1
lib/org/ciyam/at/maven-metadata.xml.md5
Normal file
1
lib/org/ciyam/at/maven-metadata.xml.md5
Normal file
@ -0,0 +1 @@
|
||||
bc81bc1f9b74a4eececd5dd8b29e47d8
|
1
lib/org/ciyam/at/maven-metadata.xml.sha1
Normal file
1
lib/org/ciyam/at/maven-metadata.xml.sha1
Normal file
@ -0,0 +1 @@
|
||||
feefde4343bda4d6e13159e5c01f8b4f8963a1bc
|
41
log4j2.properties
Normal file
41
log4j2.properties
Normal file
@ -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
|
11
pom.xml
11
pom.xml
@ -107,5 +107,16 @@
|
||||
<artifactId>jersey-media-moxy</artifactId>
|
||||
<version>2.27</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.ciyam</groupId>
|
||||
<artifactId>at</artifactId>
|
||||
<version>1.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hsqldb</groupId>
|
||||
<artifactId>sqltool</artifactId>
|
||||
<version>2.4.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
110
src/data/at/ATData.java
Normal file
110
src/data/at/ATData.java
Normal file
@ -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;
|
||||
}
|
||||
|
||||
}
|
32
src/data/at/ATStateData.java
Normal file
32
src/data/at/ATStateData.java
Normal file
@ -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;
|
||||
}
|
||||
|
||||
}
|
49
src/data/transaction/ATTransactionData.java
Normal file
49
src/data/transaction/ATTransactionData.java
Normal file
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -10,7 +10,8 @@ public class ArbitraryTransactionData extends TransactionData {
|
||||
|
||||
// "data" field types
|
||||
public enum DataType {
|
||||
RAW_DATA, DATA_HASH;
|
||||
RAW_DATA,
|
||||
DATA_HASH;
|
||||
}
|
||||
|
||||
// Properties
|
||||
|
77
src/data/transaction/DeployATTransactionData.java
Normal file
77
src/data/transaction/DeployATTransactionData.java
Normal file
@ -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;
|
||||
}
|
||||
|
||||
}
|
52
src/qora/at/AT.java
Normal file
52
src/qora/at/AT.java
Normal file
@ -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());
|
||||
}
|
||||
}
|
@ -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<Transaction>();
|
||||
}
|
||||
|
||||
/** 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<Transaction>();
|
||||
}
|
||||
@ -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.
|
||||
* <p>
|
||||
* Requires block's {@code generator} being a {@code PrivateKeyAccount}.
|
||||
* <p>
|
||||
* Generator signature is made by the generator signing the following data:
|
||||
* <p>
|
||||
* previous block's generator signature + this block's generating balance + generator's public key
|
||||
* <p>
|
||||
* (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.
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
217
src/qora/transaction/DeployATTransaction.java
Normal file
217
src/qora/transaction/DeployATTransaction.java
Normal file
@ -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<Account> getRecipientAccounts() throws DataException {
|
||||
return new ArrayList<Account>();
|
||||
}
|
||||
|
||||
@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());
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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
|
||||
|
24
src/repository/ATRepository.java
Normal file
24
src/repository/ATRepository.java
Normal file
@ -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;
|
||||
|
||||
}
|
@ -2,6 +2,8 @@ package repository;
|
||||
|
||||
public interface Repository extends AutoCloseable {
|
||||
|
||||
public ATRepository getATRepository();
|
||||
|
||||
public AccountRepository getAccountRepository();
|
||||
|
||||
public AssetRepository getAssetRepository();
|
||||
|
117
src/repository/hsqldb/HSQLDBATRepository.java
Normal file
117
src/repository/hsqldb/HSQLDBATRepository.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
96
src/test/ATTests.java
Normal file
96
src/test/ATTests.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,8 @@ public class SaveTests extends Common {
|
||||
BigDecimal.ONE, Instant.now().getEpochSecond(), reference, signature);
|
||||
|
||||
repository.getTransactionRepository().save(paymentTransactionData);
|
||||
|
||||
repository.discardChanges();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<TradeData> 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();
|
||||
|
@ -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);
|
||||
|
188
src/transform/transaction/DeployATTransactionTransformer.java
Normal file
188
src/transform/transaction/DeployATTransactionTransformer.java
Normal file
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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");
|
||||
|
Loading…
x
Reference in New Issue
Block a user