3
0
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:
catbref 2018-10-04 14:38:59 +01:00
parent 0aa0796f35
commit e9d8b3e6e3
40 changed files with 1393 additions and 54 deletions

View File

@ -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.

Binary file not shown.

View File

@ -0,0 +1 @@
1d6f5d634a2c4e570a5a8af260a51653

View File

@ -0,0 +1 @@
c6387380bc5db1f0a98ecbb480b17bd89b564401

View 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>

View File

@ -0,0 +1 @@
42f6e3eb3c6e510f65c963ce97583f05

View File

@ -0,0 +1 @@
490287647d3c69c05bd50ab565ffff86192ff423

View 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>

View File

@ -0,0 +1 @@
bc81bc1f9b74a4eececd5dd8b29e47d8

View File

@ -0,0 +1 @@
feefde4343bda4d6e13159e5c01f8b4f8963a1bc

41
log4j2.properties Normal file
View 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
View File

@ -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
View 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;
}
}

View 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;
}
}

View 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;
}
}

View File

@ -10,7 +10,8 @@ public class ArbitraryTransactionData extends TransactionData {
// "data" field types
public enum DataType {
RAW_DATA, DATA_HASH;
RAW_DATA,
DATA_HASH;
}
// Properties

View 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
View 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());
}
}

View File

@ -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.
*/
}
}

View File

@ -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;
}
}

View 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());
}
}

View File

@ -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

View File

@ -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");
}

View File

@ -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

View 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;
}

View File

@ -2,6 +2,8 @@ package repository;
public interface Repository extends AutoCloseable {
public ATRepository getATRepository();
public AccountRepository getAccountRepository();
public AssetRepository getAssetRepository();

View 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);
}
}
}

View File

@ -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;

View File

@ -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

View File

@ -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);
}
}
}

View File

@ -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
View 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);
}
}
}

View File

@ -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");
}
}

View File

@ -27,6 +27,8 @@ public class SaveTests extends Common {
BigDecimal.ONE, Instant.now().getEpochSecond(), reference, signature);
repository.getTransactionRepository().save(paymentTransactionData);
repository.discardChanges();
}
}

View File

@ -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();

View File

@ -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);

View 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;
}
}

View File

@ -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());

View File

@ -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");
}

View File

@ -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");