forked from Qortal/qortal
Added Create Poll Transaction support (untested)
* Moved Asset issue/deissue code from IssueAssetTransaction to Asset business object. * Added more constructors for Asset using IssueAssetTransactionData or assetId. * Moved some constants from transaction transfers to business objects (e.g. IssueAssetTransaction) (They might now make more sense being in Asset) * Changed some transaction isValid() checks to use transaction's timestamp instead of NTP.getTime() * New VotingRepository - as yet unimplemented Really need to rewrite "migrate" and add a ton of unit tests.
This commit is contained in:
parent
c5a32ffa1c
commit
5e674cbaab
53
src/data/transaction/CreatePollTransactionData.java
Normal file
53
src/data/transaction/CreatePollTransactionData.java
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package data.transaction;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import data.voting.PollOptionData;
|
||||||
|
import qora.transaction.Transaction;
|
||||||
|
|
||||||
|
public class CreatePollTransactionData extends TransactionData {
|
||||||
|
|
||||||
|
// Properties
|
||||||
|
private byte[] creatorPublicKey;
|
||||||
|
private String owner;
|
||||||
|
private String pollName;
|
||||||
|
private String description;
|
||||||
|
private List<PollOptionData> pollOptions;
|
||||||
|
|
||||||
|
// Constructors
|
||||||
|
|
||||||
|
public CreatePollTransactionData(byte[] creatorPublicKey, String owner, String pollName, String description, List<PollOptionData> pollOptions,
|
||||||
|
BigDecimal fee, long timestamp, byte[] reference, byte[] signature) {
|
||||||
|
super(Transaction.TransactionType.CREATE_POLL, fee, creatorPublicKey, timestamp, reference, signature);
|
||||||
|
|
||||||
|
this.creatorPublicKey = creatorPublicKey;
|
||||||
|
this.owner = owner;
|
||||||
|
this.pollName = pollName;
|
||||||
|
this.description = description;
|
||||||
|
this.pollOptions = pollOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters/setters
|
||||||
|
|
||||||
|
public byte[] getCreatorPublicKey() {
|
||||||
|
return this.creatorPublicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOwner() {
|
||||||
|
return this.owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPollName() {
|
||||||
|
return this.pollName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return this.description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<PollOptionData> getPollOptions() {
|
||||||
|
return this.pollOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
47
src/data/voting/PollData.java
Normal file
47
src/data/voting/PollData.java
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package data.voting;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import data.voting.PollOptionData;
|
||||||
|
|
||||||
|
public class PollData {
|
||||||
|
|
||||||
|
// Properties
|
||||||
|
private byte[] creatorPublicKey;
|
||||||
|
private String owner;
|
||||||
|
private String pollName;
|
||||||
|
private String description;
|
||||||
|
private List<PollOptionData> pollOptions;
|
||||||
|
|
||||||
|
// Constructors
|
||||||
|
|
||||||
|
public PollData(byte[] creatorPublicKey, String owner, String pollName, String description, List<PollOptionData> pollOptions) {
|
||||||
|
this.creatorPublicKey = creatorPublicKey;
|
||||||
|
this.pollName = pollName;
|
||||||
|
this.description = description;
|
||||||
|
this.pollOptions = pollOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters/setters
|
||||||
|
|
||||||
|
public byte[] getCreatorPublicKey() {
|
||||||
|
return this.creatorPublicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getOwner() {
|
||||||
|
return this.owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPollName() {
|
||||||
|
return this.pollName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return this.description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<PollOptionData> getPollOptions() {
|
||||||
|
return this.pollOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
20
src/data/voting/PollOptionData.java
Normal file
20
src/data/voting/PollOptionData.java
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package data.voting;
|
||||||
|
|
||||||
|
public class PollOptionData {
|
||||||
|
|
||||||
|
// Properties
|
||||||
|
private String optionName;
|
||||||
|
|
||||||
|
// Constructors
|
||||||
|
|
||||||
|
public PollOptionData(String optionName) {
|
||||||
|
this.optionName = optionName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters/setters
|
||||||
|
|
||||||
|
public String getOptionName() {
|
||||||
|
return this.optionName;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
package qora.assets;
|
package qora.assets;
|
||||||
|
|
||||||
import data.assets.AssetData;
|
import data.assets.AssetData;
|
||||||
|
import data.transaction.IssueAssetTransactionData;
|
||||||
|
import repository.DataException;
|
||||||
import repository.Repository;
|
import repository.Repository;
|
||||||
|
|
||||||
public class Asset {
|
public class Asset {
|
||||||
@ -21,4 +23,32 @@ public class Asset {
|
|||||||
this.assetData = assetData;
|
this.assetData = assetData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Asset(Repository repository, IssueAssetTransactionData issueAssetTransactionData) {
|
||||||
|
this.repository = repository;
|
||||||
|
this.assetData = new AssetData(issueAssetTransactionData.getOwner(), issueAssetTransactionData.getAssetName(),
|
||||||
|
issueAssetTransactionData.getDescription(), issueAssetTransactionData.getQuantity(), issueAssetTransactionData.getIsDivisible(),
|
||||||
|
issueAssetTransactionData.getReference());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Asset(Repository repository, long assetId) throws DataException {
|
||||||
|
this.repository = repository;
|
||||||
|
this.assetData = this.repository.getAssetRepository().fromAssetId(assetId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters/setters
|
||||||
|
|
||||||
|
public AssetData getAssetData() {
|
||||||
|
return this.assetData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Processing
|
||||||
|
|
||||||
|
public void issue() throws DataException {
|
||||||
|
this.repository.getAssetRepository().save(this.assetData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deissue() throws DataException {
|
||||||
|
this.repository.getAssetRepository().delete(this.assetData.getAssetId());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ public class BlockChain {
|
|||||||
public static final int AT_BLOCK_HEIGHT_RELEASE = 99000;
|
public static final int AT_BLOCK_HEIGHT_RELEASE = 99000;
|
||||||
public static final long POWFIX_RELEASE_TIMESTAMP = 1456426800000L; // Block Version 3 // 2016-02-25T19:00:00+00:00
|
public static final long POWFIX_RELEASE_TIMESTAMP = 1456426800000L; // Block Version 3 // 2016-02-25T19:00:00+00:00
|
||||||
public static final long ASSETS_RELEASE_TIMESTAMP = 0L; // From Qora epoch
|
public static final long ASSETS_RELEASE_TIMESTAMP = 0L; // From Qora epoch
|
||||||
|
public static final long VOTING_RELEASE_TIMESTAMP = 1403715600000L; // 2014-06-25T17:00:00+00:00
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Some sort start-up/initialization/checking method.
|
* Some sort start-up/initialization/checking method.
|
||||||
|
183
src/qora/transaction/CreatePollTransaction.java
Normal file
183
src/qora/transaction/CreatePollTransaction.java
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
package qora.transaction;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import data.transaction.CreatePollTransactionData;
|
||||||
|
import data.transaction.TransactionData;
|
||||||
|
import data.voting.PollOptionData;
|
||||||
|
import qora.account.Account;
|
||||||
|
import qora.account.PublicKeyAccount;
|
||||||
|
import qora.assets.Asset;
|
||||||
|
import qora.block.BlockChain;
|
||||||
|
import qora.crypto.Crypto;
|
||||||
|
import qora.voting.Poll;
|
||||||
|
import repository.DataException;
|
||||||
|
import repository.Repository;
|
||||||
|
|
||||||
|
public class CreatePollTransaction extends Transaction {
|
||||||
|
|
||||||
|
// Properties
|
||||||
|
private CreatePollTransactionData createPollTransactionData;
|
||||||
|
|
||||||
|
// Other useful constants
|
||||||
|
public static final int MAX_NAME_SIZE = 400;
|
||||||
|
public static final int MAX_DESCRIPTION_SIZE = 4000;
|
||||||
|
public static final int MAX_OPTIONS = 100;
|
||||||
|
|
||||||
|
// Constructors
|
||||||
|
|
||||||
|
public CreatePollTransaction(Repository repository, TransactionData transactionData) {
|
||||||
|
super(repository, transactionData);
|
||||||
|
|
||||||
|
this.createPollTransactionData = (CreatePollTransactionData) this.transactionData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// More information
|
||||||
|
|
||||||
|
public List<Account> getRecipientAccounts() throws DataException {
|
||||||
|
return Collections.singletonList(getOwner());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isInvolved(Account account) throws DataException {
|
||||||
|
String address = account.getAddress();
|
||||||
|
|
||||||
|
if (address.equals(this.getCreator().getAddress()))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (address.equals(this.getOwner().getAddress()))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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.transactionData.getFee());
|
||||||
|
|
||||||
|
return amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navigation
|
||||||
|
|
||||||
|
public Account getCreator() throws DataException {
|
||||||
|
return new PublicKeyAccount(this.repository, this.createPollTransactionData.getCreatorPublicKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Account getOwner() throws DataException {
|
||||||
|
return new Account(this.repository, this.createPollTransactionData.getOwner());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Processing
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ValidationResult isValid() throws DataException {
|
||||||
|
// Are CreatePollTransactions even allowed at this point?
|
||||||
|
// XXX In gen1 this used NTP.getTime() but surely the transaction's timestamp should be used?
|
||||||
|
if (this.createPollTransactionData.getTimestamp() < BlockChain.VOTING_RELEASE_TIMESTAMP)
|
||||||
|
return ValidationResult.NOT_YET_RELEASED;
|
||||||
|
|
||||||
|
// Check owner address is valid
|
||||||
|
if (!Crypto.isValidAddress(createPollTransactionData.getOwner()))
|
||||||
|
return ValidationResult.INVALID_ADDRESS;
|
||||||
|
|
||||||
|
// Check name size bounds
|
||||||
|
if (createPollTransactionData.getPollName().length() < 1 || createPollTransactionData.getPollName().length() > CreatePollTransaction.MAX_NAME_SIZE)
|
||||||
|
return ValidationResult.INVALID_NAME_LENGTH;
|
||||||
|
|
||||||
|
// Check description size bounds
|
||||||
|
if (createPollTransactionData.getDescription().length() < 1
|
||||||
|
|| createPollTransactionData.getDescription().length() > CreatePollTransaction.MAX_DESCRIPTION_SIZE)
|
||||||
|
return ValidationResult.INVALID_DESCRIPTION_LENGTH;
|
||||||
|
|
||||||
|
// Check poll name is lowercase
|
||||||
|
if (!createPollTransactionData.getPollName().equals(createPollTransactionData.getPollName().toLowerCase()))
|
||||||
|
return ValidationResult.NAME_NOT_LOWER_CASE;
|
||||||
|
|
||||||
|
// Check the poll name isn't already taken
|
||||||
|
if (this.repository.getVotingRepository().pollExists(createPollTransactionData.getPollName()))
|
||||||
|
return ValidationResult.POLL_ALREADY_EXISTS;
|
||||||
|
|
||||||
|
// XXX In gen1 we tested for votes but how can there be any if poll doesn't exist?
|
||||||
|
|
||||||
|
// Check number of options
|
||||||
|
List<PollOptionData> pollOptions = createPollTransactionData.getPollOptions();
|
||||||
|
int pollOptionsCount = pollOptions.size();
|
||||||
|
if (pollOptionsCount < 1 || pollOptionsCount > MAX_OPTIONS)
|
||||||
|
return ValidationResult.INVALID_OPTIONS_COUNT;
|
||||||
|
|
||||||
|
// Check each option
|
||||||
|
List<String> optionNames = new ArrayList<String>();
|
||||||
|
for (PollOptionData pollOptionData : pollOptions) {
|
||||||
|
// Check option length
|
||||||
|
int optionNameLength = pollOptionData.getOptionName().getBytes(StandardCharsets.UTF_8).length;
|
||||||
|
if (optionNameLength < 1 || optionNameLength > MAX_NAME_SIZE)
|
||||||
|
return ValidationResult.INVALID_OPTION_LENGTH;
|
||||||
|
|
||||||
|
// Check option is unique. NOTE: NOT case-sensitive!
|
||||||
|
if (optionNames.contains(pollOptionData.getOptionName())) {
|
||||||
|
return ValidationResult.DUPLICATE_OPTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
optionNames.add(pollOptionData.getOptionName());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check fee is positive
|
||||||
|
if (createPollTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
|
||||||
|
return ValidationResult.NEGATIVE_FEE;
|
||||||
|
|
||||||
|
// Check reference is correct
|
||||||
|
PublicKeyAccount creator = new PublicKeyAccount(this.repository, createPollTransactionData.getCreatorPublicKey());
|
||||||
|
|
||||||
|
if (!Arrays.equals(creator.getLastReference(), createPollTransactionData.getReference()))
|
||||||
|
return ValidationResult.INVALID_REFERENCE;
|
||||||
|
|
||||||
|
// Check issuer has enough funds
|
||||||
|
if (creator.getConfirmedBalance(Asset.QORA).compareTo(createPollTransactionData.getFee()) == -1)
|
||||||
|
return ValidationResult.NO_BALANCE;
|
||||||
|
|
||||||
|
return ValidationResult.OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process() throws DataException {
|
||||||
|
// Publish poll to allow voting
|
||||||
|
Poll poll = new Poll(this.repository, createPollTransactionData);
|
||||||
|
poll.publish();
|
||||||
|
|
||||||
|
// Save this transaction, now with corresponding pollId
|
||||||
|
this.repository.getTransactionRepository().save(createPollTransactionData);
|
||||||
|
|
||||||
|
// Update creator's balance
|
||||||
|
Account creator = new PublicKeyAccount(this.repository, createPollTransactionData.getCreatorPublicKey());
|
||||||
|
creator.setConfirmedBalance(Asset.QORA, creator.getConfirmedBalance(Asset.QORA).subtract(createPollTransactionData.getFee()));
|
||||||
|
|
||||||
|
// Update creator's reference
|
||||||
|
creator.setLastReference(createPollTransactionData.getSignature());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void orphan() throws DataException {
|
||||||
|
// Unpublish poll
|
||||||
|
Poll poll = new Poll(this.repository, createPollTransactionData.getPollName());
|
||||||
|
poll.unpublish();
|
||||||
|
|
||||||
|
// Delete this transaction itself
|
||||||
|
this.repository.getTransactionRepository().delete(createPollTransactionData);
|
||||||
|
|
||||||
|
// Update issuer's balance
|
||||||
|
Account creator = new PublicKeyAccount(this.repository, createPollTransactionData.getCreatorPublicKey());
|
||||||
|
creator.setConfirmedBalance(Asset.QORA, creator.getConfirmedBalance(Asset.QORA).add(createPollTransactionData.getFee()));
|
||||||
|
|
||||||
|
// Update issuer's reference
|
||||||
|
creator.setLastReference(createPollTransactionData.getReference());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -5,7 +5,6 @@ import java.util.Arrays;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import data.assets.AssetData;
|
|
||||||
import data.transaction.IssueAssetTransactionData;
|
import data.transaction.IssueAssetTransactionData;
|
||||||
import data.transaction.TransactionData;
|
import data.transaction.TransactionData;
|
||||||
import qora.account.Account;
|
import qora.account.Account;
|
||||||
@ -15,14 +14,16 @@ import qora.block.BlockChain;
|
|||||||
import qora.crypto.Crypto;
|
import qora.crypto.Crypto;
|
||||||
import repository.DataException;
|
import repository.DataException;
|
||||||
import repository.Repository;
|
import repository.Repository;
|
||||||
import transform.transaction.IssueAssetTransactionTransformer;
|
|
||||||
import utils.NTP;
|
|
||||||
|
|
||||||
public class IssueAssetTransaction extends Transaction {
|
public class IssueAssetTransaction extends Transaction {
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
private IssueAssetTransactionData issueAssetTransactionData;
|
private IssueAssetTransactionData issueAssetTransactionData;
|
||||||
|
|
||||||
|
// Other useful constants
|
||||||
|
public static final int MAX_NAME_SIZE = 400;
|
||||||
|
public static final int MAX_DESCRIPTION_SIZE = 4000;
|
||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
public IssueAssetTransaction(Repository repository, TransactionData transactionData) {
|
public IssueAssetTransaction(Repository repository, TransactionData transactionData) {
|
||||||
@ -75,7 +76,8 @@ public class IssueAssetTransaction extends Transaction {
|
|||||||
|
|
||||||
public ValidationResult isValid() throws DataException {
|
public ValidationResult isValid() throws DataException {
|
||||||
// Are IssueAssetTransactions even allowed at this point?
|
// Are IssueAssetTransactions even allowed at this point?
|
||||||
if (NTP.getTime() < BlockChain.ASSETS_RELEASE_TIMESTAMP)
|
// XXX In gen1 this used NTP.getTime() but surely the transaction's timestamp should be used?
|
||||||
|
if (this.issueAssetTransactionData.getTimestamp() < BlockChain.ASSETS_RELEASE_TIMESTAMP)
|
||||||
return ValidationResult.NOT_YET_RELEASED;
|
return ValidationResult.NOT_YET_RELEASED;
|
||||||
|
|
||||||
// Check owner address is valid
|
// Check owner address is valid
|
||||||
@ -83,13 +85,12 @@ public class IssueAssetTransaction extends Transaction {
|
|||||||
return ValidationResult.INVALID_ADDRESS;
|
return ValidationResult.INVALID_ADDRESS;
|
||||||
|
|
||||||
// Check name size bounds
|
// Check name size bounds
|
||||||
if (issueAssetTransactionData.getAssetName().length() < 1
|
if (issueAssetTransactionData.getAssetName().length() < 1 || issueAssetTransactionData.getAssetName().length() > IssueAssetTransaction.MAX_NAME_SIZE)
|
||||||
|| issueAssetTransactionData.getAssetName().length() > IssueAssetTransactionTransformer.MAX_NAME_SIZE)
|
|
||||||
return ValidationResult.INVALID_NAME_LENGTH;
|
return ValidationResult.INVALID_NAME_LENGTH;
|
||||||
|
|
||||||
// Check description size bounds
|
// Check description size bounds
|
||||||
if (issueAssetTransactionData.getDescription().length() < 1
|
if (issueAssetTransactionData.getDescription().length() < 1
|
||||||
|| issueAssetTransactionData.getDescription().length() > IssueAssetTransactionTransformer.MAX_DESCRIPTION_SIZE)
|
|| issueAssetTransactionData.getDescription().length() > IssueAssetTransaction.MAX_DESCRIPTION_SIZE)
|
||||||
return ValidationResult.INVALID_DESCRIPTION_LENGTH;
|
return ValidationResult.INVALID_DESCRIPTION_LENGTH;
|
||||||
|
|
||||||
// Check quantity - either 10 billion or if that's not enough: a billion billion!
|
// Check quantity - either 10 billion or if that's not enough: a billion billion!
|
||||||
@ -120,13 +121,11 @@ public class IssueAssetTransaction extends Transaction {
|
|||||||
|
|
||||||
public void process() throws DataException {
|
public void process() throws DataException {
|
||||||
// Issue asset
|
// Issue asset
|
||||||
AssetData assetData = new AssetData(issueAssetTransactionData.getOwner(), issueAssetTransactionData.getAssetName(),
|
Asset asset = new Asset(this.repository, issueAssetTransactionData);
|
||||||
issueAssetTransactionData.getDescription(), issueAssetTransactionData.getQuantity(), issueAssetTransactionData.getIsDivisible(),
|
asset.issue();
|
||||||
issueAssetTransactionData.getReference());
|
|
||||||
this.repository.getAssetRepository().save(assetData);
|
|
||||||
|
|
||||||
// Note newly assigned asset ID in our transaction record
|
// Note newly assigned asset ID in our transaction record
|
||||||
issueAssetTransactionData.setAssetId(assetData.getAssetId());
|
issueAssetTransactionData.setAssetId(asset.getAssetData().getAssetId());
|
||||||
|
|
||||||
// Save this transaction, now with corresponding assetId
|
// Save this transaction, now with corresponding assetId
|
||||||
this.repository.getTransactionRepository().save(issueAssetTransactionData);
|
this.repository.getTransactionRepository().save(issueAssetTransactionData);
|
||||||
@ -148,8 +147,9 @@ public class IssueAssetTransaction extends Transaction {
|
|||||||
Account owner = new Account(this.repository, issueAssetTransactionData.getOwner());
|
Account owner = new Account(this.repository, issueAssetTransactionData.getOwner());
|
||||||
owner.deleteBalance(issueAssetTransactionData.getAssetId());
|
owner.deleteBalance(issueAssetTransactionData.getAssetId());
|
||||||
|
|
||||||
// Unissue asset
|
// Issue asset
|
||||||
this.repository.getAssetRepository().delete(issueAssetTransactionData.getAssetId());
|
Asset asset = new Asset(this.repository, issueAssetTransactionData.getAssetId());
|
||||||
|
asset.deissue();
|
||||||
|
|
||||||
// Delete this transaction itself
|
// Delete this transaction itself
|
||||||
this.repository.getTransactionRepository().delete(issueAssetTransactionData);
|
this.repository.getTransactionRepository().delete(issueAssetTransactionData);
|
||||||
|
@ -21,8 +21,8 @@ public class MessageTransaction extends Transaction {
|
|||||||
// Properties
|
// Properties
|
||||||
private MessageTransactionData messageTransactionData;
|
private MessageTransactionData messageTransactionData;
|
||||||
|
|
||||||
// Useful constants
|
// Other useful constants
|
||||||
private static final int MAX_DATA_SIZE = 4000;
|
public static final int MAX_DATA_SIZE = 4000;
|
||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
|
@ -44,9 +44,10 @@ public abstract class Transaction {
|
|||||||
// Validation results
|
// Validation results
|
||||||
public enum ValidationResult {
|
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_AMOUNT(
|
OK(1), INVALID_ADDRESS(2), NEGATIVE_AMOUNT(3), NEGATIVE_FEE(4), NO_BALANCE(5), INVALID_REFERENCE(6), INVALID_NAME_LENGTH(7), INVALID_AMOUNT(
|
||||||
15), INVALID_DESCRIPTION_LENGTH(18), INVALID_DATA_LENGTH(27), INVALID_QUANTITY(28), ASSET_DOES_NOT_EXIST(29), INVALID_RETURN(
|
15), NAME_NOT_LOWER_CASE(17), INVALID_DESCRIPTION_LENGTH(18), INVALID_OPTIONS_COUNT(19), INVALID_OPTION_LENGTH(20), DUPLICATE_OPTION(
|
||||||
30), HAVE_EQUALS_WANT(31), ORDER_DOES_NOT_EXIST(32), INVALID_ORDER_CREATOR(
|
21), POLL_ALREADY_EXISTS(22), INVALID_DATA_LENGTH(27), INVALID_QUANTITY(28), ASSET_DOES_NOT_EXIST(29), INVALID_RETURN(
|
||||||
33), INVALID_PAYMENTS_COUNT(34), NEGATIVE_PRICE(35), ASSET_ALREADY_EXISTS(43), NOT_YET_RELEASED(1000);
|
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);
|
||||||
|
|
||||||
public final int value;
|
public final int value;
|
||||||
|
|
||||||
@ -87,6 +88,9 @@ public abstract class Transaction {
|
|||||||
case PAYMENT:
|
case PAYMENT:
|
||||||
return new PaymentTransaction(repository, transactionData);
|
return new PaymentTransaction(repository, transactionData);
|
||||||
|
|
||||||
|
case CREATE_POLL:
|
||||||
|
return new CreatePollTransaction(repository, transactionData);
|
||||||
|
|
||||||
case ISSUE_ASSET:
|
case ISSUE_ASSET:
|
||||||
return new IssueAssetTransaction(repository, transactionData);
|
return new IssueAssetTransaction(repository, transactionData);
|
||||||
|
|
||||||
|
53
src/qora/voting/Poll.java
Normal file
53
src/qora/voting/Poll.java
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package qora.voting;
|
||||||
|
|
||||||
|
import data.transaction.CreatePollTransactionData;
|
||||||
|
import data.voting.PollData;
|
||||||
|
import repository.DataException;
|
||||||
|
import repository.Repository;
|
||||||
|
|
||||||
|
public class Poll {
|
||||||
|
|
||||||
|
// Properties
|
||||||
|
private Repository repository;
|
||||||
|
private PollData pollData;
|
||||||
|
|
||||||
|
// Constructors
|
||||||
|
|
||||||
|
public Poll(Repository repository, PollData pollData) {
|
||||||
|
this.repository = repository;
|
||||||
|
this.pollData = pollData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create Poll business object using info from create poll transaction.
|
||||||
|
*
|
||||||
|
* @param repository
|
||||||
|
* @param createPollTransactionData
|
||||||
|
*/
|
||||||
|
public Poll(Repository repository, CreatePollTransactionData createPollTransactionData) {
|
||||||
|
this.repository = repository;
|
||||||
|
this.pollData = new PollData(createPollTransactionData.getCreatorPublicKey(), createPollTransactionData.getOwner(),
|
||||||
|
createPollTransactionData.getPollName(), createPollTransactionData.getDescription(), createPollTransactionData.getPollOptions());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Poll(Repository repository, String pollName) throws DataException {
|
||||||
|
this.repository = repository;
|
||||||
|
this.pollData = this.repository.getVotingRepository().fromPollName(pollName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Processing
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "Publish" poll to allow voting.
|
||||||
|
*
|
||||||
|
* @throws DataException
|
||||||
|
*/
|
||||||
|
public void publish() throws DataException {
|
||||||
|
this.repository.getVotingRepository().save(this.pollData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unpublish() throws DataException {
|
||||||
|
this.repository.getVotingRepository().delete(this.pollData.getPollName());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -10,6 +10,8 @@ public interface Repository extends AutoCloseable {
|
|||||||
|
|
||||||
public TransactionRepository getTransactionRepository();
|
public TransactionRepository getTransactionRepository();
|
||||||
|
|
||||||
|
public VotingRepository getVotingRepository();
|
||||||
|
|
||||||
public void saveChanges() throws DataException;
|
public void saveChanges() throws DataException;
|
||||||
|
|
||||||
public void discardChanges() throws DataException;
|
public void discardChanges() throws DataException;
|
||||||
|
15
src/repository/VotingRepository.java
Normal file
15
src/repository/VotingRepository.java
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package repository;
|
||||||
|
|
||||||
|
import data.voting.PollData;
|
||||||
|
|
||||||
|
public interface VotingRepository {
|
||||||
|
|
||||||
|
public PollData fromPollName(String pollName) throws DataException;
|
||||||
|
|
||||||
|
public boolean pollExists(String pollName) throws DataException;
|
||||||
|
|
||||||
|
public void save(PollData pollData) throws DataException;
|
||||||
|
|
||||||
|
public void delete(String pollName) throws DataException;
|
||||||
|
|
||||||
|
}
|
@ -183,8 +183,8 @@ public class HSQLDBDatabaseUpdates {
|
|||||||
|
|
||||||
case 10:
|
case 10:
|
||||||
// Create Poll Transactions
|
// Create Poll Transactions
|
||||||
stmt.execute("CREATE TABLE CreatePollTransactions (signature Signature, creator QoraPublicKey NOT NULL, poll PollName NOT NULL, "
|
stmt.execute("CREATE TABLE CreatePollTransactions (signature Signature, creator QoraPublicKey NOT NULL, owner QoraAddress NOT NULL, "
|
||||||
+ "description VARCHAR(4000) NOT NULL, "
|
+ "poll PollName NOT NULL, description VARCHAR(4000) NOT NULL, "
|
||||||
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
|
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
|
||||||
// Poll options. NB: option is implicitly NON NULL and UNIQUE due to being part of compound primary key
|
// Poll options. NB: option is implicitly NON NULL and UNIQUE due to being part of compound primary key
|
||||||
stmt.execute("CREATE TABLE CreatePollTransactionOptions (signature Signature, option PollOption, "
|
stmt.execute("CREATE TABLE CreatePollTransactionOptions (signature Signature, option PollOption, "
|
||||||
@ -270,7 +270,7 @@ public class HSQLDBDatabaseUpdates {
|
|||||||
case 21:
|
case 21:
|
||||||
// Assets (including QORA coin itself)
|
// Assets (including QORA coin itself)
|
||||||
stmt.execute(
|
stmt.execute(
|
||||||
"CREATE TABLE Assets (asset_id AssetID IDENTITY, owner QoraPublicKey NOT NULL, asset_name AssetName NOT NULL, description VARCHAR(4000) NOT NULL, "
|
"CREATE TABLE Assets (asset_id AssetID IDENTITY, owner QoraAddress NOT NULL, asset_name AssetName NOT NULL, description VARCHAR(4000) NOT NULL, "
|
||||||
+ "quantity BIGINT NOT NULL, is_divisible BOOLEAN NOT NULL, reference Signature NOT NULL)");
|
+ "quantity BIGINT NOT NULL, is_divisible BOOLEAN NOT NULL, reference Signature NOT NULL)");
|
||||||
stmt.execute("CREATE INDEX AssetNameIndex on Assets (asset_name)");
|
stmt.execute("CREATE INDEX AssetNameIndex on Assets (asset_name)");
|
||||||
break;
|
break;
|
||||||
|
@ -15,6 +15,7 @@ import repository.BlockRepository;
|
|||||||
import repository.DataException;
|
import repository.DataException;
|
||||||
import repository.Repository;
|
import repository.Repository;
|
||||||
import repository.TransactionRepository;
|
import repository.TransactionRepository;
|
||||||
|
import repository.VotingRepository;
|
||||||
import repository.hsqldb.transaction.HSQLDBTransactionRepository;
|
import repository.hsqldb.transaction.HSQLDBTransactionRepository;
|
||||||
|
|
||||||
public class HSQLDBRepository implements Repository {
|
public class HSQLDBRepository implements Repository {
|
||||||
@ -46,6 +47,11 @@ public class HSQLDBRepository implements Repository {
|
|||||||
return new HSQLDBTransactionRepository(this);
|
return new HSQLDBTransactionRepository(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VotingRepository getVotingRepository() {
|
||||||
|
return new HSQLDBVotingRepository(this);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void saveChanges() throws DataException {
|
public void saveChanges() throws DataException {
|
||||||
try {
|
try {
|
||||||
|
33
src/repository/hsqldb/HSQLDBVotingRepository.java
Normal file
33
src/repository/hsqldb/HSQLDBVotingRepository.java
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package repository.hsqldb;
|
||||||
|
|
||||||
|
import data.voting.PollData;
|
||||||
|
import repository.VotingRepository;
|
||||||
|
import repository.DataException;
|
||||||
|
|
||||||
|
public class HSQLDBVotingRepository implements VotingRepository {
|
||||||
|
|
||||||
|
protected HSQLDBRepository repository;
|
||||||
|
|
||||||
|
public HSQLDBVotingRepository(HSQLDBRepository repository) {
|
||||||
|
this.repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PollData fromPollName(String pollName) throws DataException {
|
||||||
|
// TODO
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean pollExists(String pollName) throws DataException {
|
||||||
|
// TODO
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save(PollData pollData) throws DataException {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
public void delete(String pollName) throws DataException {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
package repository.hsqldb.transaction;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import data.transaction.CreatePollTransactionData;
|
||||||
|
import data.transaction.TransactionData;
|
||||||
|
import data.voting.PollOptionData;
|
||||||
|
import repository.DataException;
|
||||||
|
import repository.hsqldb.HSQLDBRepository;
|
||||||
|
import repository.hsqldb.HSQLDBSaver;
|
||||||
|
|
||||||
|
public class HSQLDBCreatePollTransactionRepository extends HSQLDBTransactionRepository {
|
||||||
|
|
||||||
|
public HSQLDBCreatePollTransactionRepository(HSQLDBRepository repository) {
|
||||||
|
this.repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
TransactionData fromBase(byte[] signature, byte[] reference, byte[] creatorPublicKey, long timestamp, BigDecimal fee) throws DataException {
|
||||||
|
try {
|
||||||
|
ResultSet rs = this.repository.checkedExecute("SELECT owner, poll_name, description FROM CreatePollTransactions WHERE signature = ?", signature);
|
||||||
|
if (rs == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
String owner = rs.getString(1);
|
||||||
|
String pollName = rs.getString(2);
|
||||||
|
String description = rs.getString(3);
|
||||||
|
|
||||||
|
rs = this.repository.checkedExecute("SELECT option_name FROM CreatePollTransactionOptions where signature = ?", signature);
|
||||||
|
if (rs == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
List<PollOptionData> pollOptions = new ArrayList<PollOptionData>();
|
||||||
|
|
||||||
|
// NOTE: do-while because checkedExecute() above has already called rs.next() for us
|
||||||
|
do {
|
||||||
|
String optionName = rs.getString(1);
|
||||||
|
|
||||||
|
pollOptions.add(new PollOptionData(optionName));
|
||||||
|
} while (rs.next());
|
||||||
|
|
||||||
|
return new CreatePollTransactionData(creatorPublicKey, owner, pollName, description, pollOptions, fee, timestamp, reference, signature);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DataException("Unable to fetch create poll transaction from repository", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save(TransactionData transactionData) throws DataException {
|
||||||
|
CreatePollTransactionData createPollTransactionData = (CreatePollTransactionData) transactionData;
|
||||||
|
|
||||||
|
HSQLDBSaver saveHelper = new HSQLDBSaver("CreatePollTransactions");
|
||||||
|
|
||||||
|
saveHelper.bind("signature", createPollTransactionData.getSignature()).bind("creator", createPollTransactionData.getCreatorPublicKey())
|
||||||
|
.bind("owner", createPollTransactionData.getOwner()).bind("poll_name", createPollTransactionData.getPollName())
|
||||||
|
.bind("description", createPollTransactionData.getDescription());
|
||||||
|
|
||||||
|
try {
|
||||||
|
saveHelper.execute(this.repository);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DataException("Unable to save create poll transaction into repository", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now attempt to save poll options
|
||||||
|
for (PollOptionData pollOptionData : createPollTransactionData.getPollOptions()) {
|
||||||
|
HSQLDBSaver optionSaveHelper = new HSQLDBSaver("CreatePollTransactionOptions");
|
||||||
|
|
||||||
|
optionSaveHelper.bind("signature", createPollTransactionData.getSignature()).bind("option_name", pollOptionData.getOptionName());
|
||||||
|
|
||||||
|
try {
|
||||||
|
optionSaveHelper.execute(this.repository);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DataException("Unable to save create poll transaction option into repository", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -21,6 +21,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
|||||||
protected HSQLDBRepository repository;
|
protected HSQLDBRepository repository;
|
||||||
private HSQLDBGenesisTransactionRepository genesisTransactionRepository;
|
private HSQLDBGenesisTransactionRepository genesisTransactionRepository;
|
||||||
private HSQLDBPaymentTransactionRepository paymentTransactionRepository;
|
private HSQLDBPaymentTransactionRepository paymentTransactionRepository;
|
||||||
|
private HSQLDBCreatePollTransactionRepository createPollTransactionRepository;
|
||||||
private HSQLDBIssueAssetTransactionRepository issueAssetTransactionRepository;
|
private HSQLDBIssueAssetTransactionRepository issueAssetTransactionRepository;
|
||||||
private HSQLDBTransferAssetTransactionRepository transferAssetTransactionRepository;
|
private HSQLDBTransferAssetTransactionRepository transferAssetTransactionRepository;
|
||||||
private HSQLDBCreateOrderTransactionRepository createOrderTransactionRepository;
|
private HSQLDBCreateOrderTransactionRepository createOrderTransactionRepository;
|
||||||
@ -32,6 +33,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
|||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
this.genesisTransactionRepository = new HSQLDBGenesisTransactionRepository(repository);
|
this.genesisTransactionRepository = new HSQLDBGenesisTransactionRepository(repository);
|
||||||
this.paymentTransactionRepository = new HSQLDBPaymentTransactionRepository(repository);
|
this.paymentTransactionRepository = new HSQLDBPaymentTransactionRepository(repository);
|
||||||
|
this.createPollTransactionRepository = new HSQLDBCreatePollTransactionRepository(repository);
|
||||||
this.issueAssetTransactionRepository = new HSQLDBIssueAssetTransactionRepository(repository);
|
this.issueAssetTransactionRepository = new HSQLDBIssueAssetTransactionRepository(repository);
|
||||||
this.transferAssetTransactionRepository = new HSQLDBTransferAssetTransactionRepository(repository);
|
this.transferAssetTransactionRepository = new HSQLDBTransferAssetTransactionRepository(repository);
|
||||||
this.createOrderTransactionRepository = new HSQLDBCreateOrderTransactionRepository(repository);
|
this.createOrderTransactionRepository = new HSQLDBCreateOrderTransactionRepository(repository);
|
||||||
@ -88,6 +90,9 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
|||||||
case PAYMENT:
|
case PAYMENT:
|
||||||
return this.paymentTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee);
|
return this.paymentTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee);
|
||||||
|
|
||||||
|
case CREATE_POLL:
|
||||||
|
return this.createPollTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee);
|
||||||
|
|
||||||
case ISSUE_ASSET:
|
case ISSUE_ASSET:
|
||||||
return this.issueAssetTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee);
|
return this.issueAssetTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee);
|
||||||
|
|
||||||
@ -210,6 +215,10 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
|||||||
this.paymentTransactionRepository.save(transactionData);
|
this.paymentTransactionRepository.save(transactionData);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case CREATE_POLL:
|
||||||
|
this.createPollTransactionRepository.save(transactionData);
|
||||||
|
break;
|
||||||
|
|
||||||
case ISSUE_ASSET:
|
case ISSUE_ASSET:
|
||||||
this.issueAssetTransactionRepository.save(transactionData);
|
this.issueAssetTransactionRepository.save(transactionData);
|
||||||
break;
|
break;
|
||||||
@ -224,9 +233,11 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
|||||||
|
|
||||||
case CANCEL_ASSET_ORDER:
|
case CANCEL_ASSET_ORDER:
|
||||||
this.cancelOrderTransactionRepository.save(transactionData);
|
this.cancelOrderTransactionRepository.save(transactionData);
|
||||||
|
break;
|
||||||
|
|
||||||
case MULTIPAYMENT:
|
case MULTIPAYMENT:
|
||||||
this.multiPaymentTransactionRepository.save(transactionData);
|
this.multiPaymentTransactionRepository.save(transactionData);
|
||||||
|
break;
|
||||||
|
|
||||||
case MESSAGE:
|
case MESSAGE:
|
||||||
this.messageTransactionRepository.save(transactionData);
|
this.messageTransactionRepository.save(transactionData);
|
||||||
|
151
src/transform/transaction/CreatePollTransactionTransformer.java
Normal file
151
src/transform/transaction/CreatePollTransactionTransformer.java
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
package transform.transaction;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.json.simple.JSONArray;
|
||||||
|
import org.json.simple.JSONObject;
|
||||||
|
|
||||||
|
import com.google.common.hash.HashCode;
|
||||||
|
import com.google.common.primitives.Ints;
|
||||||
|
import com.google.common.primitives.Longs;
|
||||||
|
|
||||||
|
import data.transaction.CreatePollTransactionData;
|
||||||
|
import data.transaction.TransactionData;
|
||||||
|
import data.voting.PollOptionData;
|
||||||
|
import qora.account.PublicKeyAccount;
|
||||||
|
import qora.transaction.CreatePollTransaction;
|
||||||
|
import transform.TransformationException;
|
||||||
|
import utils.Base58;
|
||||||
|
import utils.Serialization;
|
||||||
|
|
||||||
|
public class CreatePollTransactionTransformer extends TransactionTransformer {
|
||||||
|
|
||||||
|
// Property lengths
|
||||||
|
private static final int OWNER_LENGTH = ADDRESS_LENGTH;
|
||||||
|
private static final int NAME_SIZE_LENGTH = INT_LENGTH;
|
||||||
|
private static final int DESCRIPTION_SIZE_LENGTH = INT_LENGTH;
|
||||||
|
private static final int OPTIONS_SIZE_LENGTH = INT_LENGTH;
|
||||||
|
|
||||||
|
private static final int TYPELESS_DATALESS_LENGTH = BASE_TYPELESS_LENGTH + OWNER_LENGTH + NAME_SIZE_LENGTH + DESCRIPTION_SIZE_LENGTH;
|
||||||
|
|
||||||
|
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
|
||||||
|
if (byteBuffer.remaining() < TYPELESS_DATALESS_LENGTH)
|
||||||
|
throw new TransformationException("Byte data too short for CreatePollTransaction");
|
||||||
|
|
||||||
|
long timestamp = byteBuffer.getLong();
|
||||||
|
|
||||||
|
byte[] reference = new byte[REFERENCE_LENGTH];
|
||||||
|
byteBuffer.get(reference);
|
||||||
|
|
||||||
|
byte[] creatorPublicKey = Serialization.deserializePublicKey(byteBuffer);
|
||||||
|
|
||||||
|
String owner = Serialization.deserializeRecipient(byteBuffer);
|
||||||
|
|
||||||
|
String pollName = Serialization.deserializeSizedString(byteBuffer, CreatePollTransaction.MAX_NAME_SIZE);
|
||||||
|
String description = Serialization.deserializeSizedString(byteBuffer, CreatePollTransaction.MAX_DESCRIPTION_SIZE);
|
||||||
|
|
||||||
|
// Make sure there are enough bytes left for poll options
|
||||||
|
if (byteBuffer.remaining() < OPTIONS_SIZE_LENGTH)
|
||||||
|
throw new TransformationException("Byte data too short for CreatePollTransaction");
|
||||||
|
|
||||||
|
int optionsCount = byteBuffer.getInt();
|
||||||
|
if (optionsCount < 1 || optionsCount > CreatePollTransaction.MAX_OPTIONS)
|
||||||
|
throw new TransformationException("Invalid number of options for CreatePollTransaction");
|
||||||
|
|
||||||
|
List<PollOptionData> pollOptions = new ArrayList<PollOptionData>();
|
||||||
|
for (int i = 0; i < optionsCount; ++i) {
|
||||||
|
String optionName = Serialization.deserializeSizedString(byteBuffer, CreatePollTransaction.MAX_NAME_SIZE);
|
||||||
|
|
||||||
|
pollOptions.add(new PollOptionData(optionName));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Still need to make sure there are enough bytes left for remaining fields
|
||||||
|
if (byteBuffer.remaining() < FEE_LENGTH + SIGNATURE_LENGTH)
|
||||||
|
throw new TransformationException("Byte data too short for CreatePollTransaction");
|
||||||
|
|
||||||
|
BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer);
|
||||||
|
|
||||||
|
byte[] signature = new byte[SIGNATURE_LENGTH];
|
||||||
|
byteBuffer.get(signature);
|
||||||
|
|
||||||
|
return new CreatePollTransactionData(creatorPublicKey, owner, pollName, description, pollOptions, fee, timestamp, reference, signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getDataLength(TransactionData transactionData) throws TransformationException {
|
||||||
|
CreatePollTransactionData createPollTransactionData = (CreatePollTransactionData) transactionData;
|
||||||
|
|
||||||
|
int dataLength = TYPE_LENGTH + TYPELESS_DATALESS_LENGTH + createPollTransactionData.getPollName().length();
|
||||||
|
|
||||||
|
// Add lengths for each poll options
|
||||||
|
for (PollOptionData pollOptionData : createPollTransactionData.getPollOptions())
|
||||||
|
dataLength += OPTIONS_SIZE_LENGTH + pollOptionData.getOptionName().length();
|
||||||
|
|
||||||
|
return dataLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] toBytes(TransactionData transactionData) throws TransformationException {
|
||||||
|
try {
|
||||||
|
CreatePollTransactionData createPollTransactionData = (CreatePollTransactionData) transactionData;
|
||||||
|
|
||||||
|
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||||
|
|
||||||
|
bytes.write(Ints.toByteArray(createPollTransactionData.getType().value));
|
||||||
|
bytes.write(Longs.toByteArray(createPollTransactionData.getTimestamp()));
|
||||||
|
bytes.write(createPollTransactionData.getReference());
|
||||||
|
|
||||||
|
bytes.write(createPollTransactionData.getCreatorPublicKey());
|
||||||
|
bytes.write(Base58.decode(createPollTransactionData.getOwner()));
|
||||||
|
Serialization.serializeSizedString(bytes, createPollTransactionData.getPollName());
|
||||||
|
Serialization.serializeSizedString(bytes, createPollTransactionData.getDescription());
|
||||||
|
|
||||||
|
List<PollOptionData> pollOptions = createPollTransactionData.getPollOptions();
|
||||||
|
bytes.write(Ints.toByteArray(pollOptions.size()));
|
||||||
|
|
||||||
|
for (PollOptionData pollOptionData : pollOptions) {
|
||||||
|
Serialization.serializeSizedString(bytes, pollOptionData.getOptionName());
|
||||||
|
}
|
||||||
|
|
||||||
|
Serialization.serializeBigDecimal(bytes, createPollTransactionData.getFee());
|
||||||
|
bytes.write(createPollTransactionData.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 {
|
||||||
|
CreatePollTransactionData createPollTransactionData = (CreatePollTransactionData) transactionData;
|
||||||
|
|
||||||
|
byte[] creatorPublicKey = createPollTransactionData.getCreatorPublicKey();
|
||||||
|
|
||||||
|
json.put("creator", PublicKeyAccount.getAddress(creatorPublicKey));
|
||||||
|
json.put("creatorPublicKey", HashCode.fromBytes(creatorPublicKey).toString());
|
||||||
|
|
||||||
|
json.put("owner", createPollTransactionData.getOwner());
|
||||||
|
json.put("name", createPollTransactionData.getPollName());
|
||||||
|
json.put("description", createPollTransactionData.getDescription());
|
||||||
|
|
||||||
|
JSONArray options = new JSONArray();
|
||||||
|
for (PollOptionData optionData : createPollTransactionData.getPollOptions()) {
|
||||||
|
options.add(optionData.getOptionName());
|
||||||
|
}
|
||||||
|
|
||||||
|
json.put("options", options);
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
throw new TransformationException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -13,6 +13,7 @@ import com.google.common.primitives.Longs;
|
|||||||
|
|
||||||
import data.transaction.TransactionData;
|
import data.transaction.TransactionData;
|
||||||
import qora.account.PublicKeyAccount;
|
import qora.account.PublicKeyAccount;
|
||||||
|
import qora.transaction.IssueAssetTransaction;
|
||||||
import data.transaction.IssueAssetTransactionData;
|
import data.transaction.IssueAssetTransactionData;
|
||||||
import transform.TransformationException;
|
import transform.TransformationException;
|
||||||
import utils.Base58;
|
import utils.Base58;
|
||||||
@ -31,10 +32,6 @@ public class IssueAssetTransactionTransformer extends TransactionTransformer {
|
|||||||
private static final int TYPELESS_LENGTH = BASE_TYPELESS_LENGTH + ISSUER_LENGTH + OWNER_LENGTH + NAME_SIZE_LENGTH + DESCRIPTION_SIZE_LENGTH
|
private static final int TYPELESS_LENGTH = BASE_TYPELESS_LENGTH + ISSUER_LENGTH + OWNER_LENGTH + NAME_SIZE_LENGTH + DESCRIPTION_SIZE_LENGTH
|
||||||
+ QUANTITY_LENGTH + IS_DIVISIBLE_LENGTH;
|
+ QUANTITY_LENGTH + IS_DIVISIBLE_LENGTH;
|
||||||
|
|
||||||
// Other useful lengths
|
|
||||||
public static final int MAX_NAME_SIZE = 400;
|
|
||||||
public static final int MAX_DESCRIPTION_SIZE = 4000;
|
|
||||||
|
|
||||||
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
|
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
|
||||||
if (byteBuffer.remaining() < TYPELESS_LENGTH)
|
if (byteBuffer.remaining() < TYPELESS_LENGTH)
|
||||||
throw new TransformationException("Byte data too short for IssueAssetTransaction");
|
throw new TransformationException("Byte data too short for IssueAssetTransaction");
|
||||||
@ -47,11 +44,11 @@ public class IssueAssetTransactionTransformer extends TransactionTransformer {
|
|||||||
byte[] issuerPublicKey = Serialization.deserializePublicKey(byteBuffer);
|
byte[] issuerPublicKey = Serialization.deserializePublicKey(byteBuffer);
|
||||||
String owner = Serialization.deserializeRecipient(byteBuffer);
|
String owner = Serialization.deserializeRecipient(byteBuffer);
|
||||||
|
|
||||||
String assetName = Serialization.deserializeSizedString(byteBuffer, MAX_NAME_SIZE);
|
String assetName = Serialization.deserializeSizedString(byteBuffer, IssueAssetTransaction.MAX_NAME_SIZE);
|
||||||
String description = Serialization.deserializeSizedString(byteBuffer, MAX_DESCRIPTION_SIZE);
|
String description = Serialization.deserializeSizedString(byteBuffer, IssueAssetTransaction.MAX_DESCRIPTION_SIZE);
|
||||||
|
|
||||||
// Still need to make sure there are enough bytes left for remaining fields
|
// Still need to make sure there are enough bytes left for remaining fields
|
||||||
if (byteBuffer.remaining() < QUANTITY_LENGTH + IS_DIVISIBLE_LENGTH + SIGNATURE_LENGTH)
|
if (byteBuffer.remaining() < QUANTITY_LENGTH + IS_DIVISIBLE_LENGTH + FEE_LENGTH + SIGNATURE_LENGTH)
|
||||||
throw new TransformationException("Byte data too short for IssueAssetTransaction");
|
throw new TransformationException("Byte data too short for IssueAssetTransaction");
|
||||||
|
|
||||||
long quantity = byteBuffer.getLong();
|
long quantity = byteBuffer.getLong();
|
||||||
|
@ -37,9 +37,6 @@ public class MessageTransactionTransformer extends TransactionTransformer {
|
|||||||
private static final int TYPELESS_DATALESS_LENGTH_V3 = BASE_TYPELESS_LENGTH + SENDER_LENGTH + RECIPIENT_LENGTH + ASSET_ID_LENGTH + AMOUNT_LENGTH
|
private static final int TYPELESS_DATALESS_LENGTH_V3 = BASE_TYPELESS_LENGTH + SENDER_LENGTH + RECIPIENT_LENGTH + ASSET_ID_LENGTH + AMOUNT_LENGTH
|
||||||
+ DATA_SIZE_LENGTH + IS_TEXT_LENGTH + IS_ENCRYPTED_LENGTH;
|
+ DATA_SIZE_LENGTH + IS_TEXT_LENGTH + IS_ENCRYPTED_LENGTH;
|
||||||
|
|
||||||
// Other property lengths
|
|
||||||
private static final int MAX_DATA_SIZE = 4000;
|
|
||||||
|
|
||||||
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
|
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
|
||||||
if (byteBuffer.remaining() < TYPELESS_DATALESS_LENGTH_V1)
|
if (byteBuffer.remaining() < TYPELESS_DATALESS_LENGTH_V1)
|
||||||
throw new TransformationException("Byte data too short for MessageTransaction");
|
throw new TransformationException("Byte data too short for MessageTransaction");
|
||||||
@ -69,12 +66,16 @@ public class MessageTransactionTransformer extends TransactionTransformer {
|
|||||||
|
|
||||||
int dataSize = byteBuffer.getInt(0);
|
int dataSize = byteBuffer.getInt(0);
|
||||||
// Don't allow invalid dataSize here to avoid run-time issues
|
// Don't allow invalid dataSize here to avoid run-time issues
|
||||||
if (dataSize > MAX_DATA_SIZE)
|
if (dataSize > MessageTransaction.MAX_DATA_SIZE)
|
||||||
throw new TransformationException("MessageTransaction data size too large");
|
throw new TransformationException("MessageTransaction data size too large");
|
||||||
|
|
||||||
byte[] data = new byte[dataSize];
|
byte[] data = new byte[dataSize];
|
||||||
byteBuffer.get(data);
|
byteBuffer.get(data);
|
||||||
|
|
||||||
|
// Still need to make sure there are enough bytes left for remaining fields
|
||||||
|
if (byteBuffer.remaining() < IS_ENCRYPTED_LENGTH + IS_TEXT_LENGTH + FEE_LENGTH + SIGNATURE_LENGTH)
|
||||||
|
throw new TransformationException("Byte data too short for MessageTransaction");
|
||||||
|
|
||||||
boolean isEncrypted = byteBuffer.get() != 0;
|
boolean isEncrypted = byteBuffer.get() != 0;
|
||||||
boolean isText = byteBuffer.get() != 0;
|
boolean isText = byteBuffer.get() != 0;
|
||||||
|
|
||||||
|
@ -37,6 +37,9 @@ public class TransactionTransformer extends Transformer {
|
|||||||
case PAYMENT:
|
case PAYMENT:
|
||||||
return PaymentTransactionTransformer.fromByteBuffer(byteBuffer);
|
return PaymentTransactionTransformer.fromByteBuffer(byteBuffer);
|
||||||
|
|
||||||
|
case CREATE_POLL:
|
||||||
|
return CreatePollTransactionTransformer.fromByteBuffer(byteBuffer);
|
||||||
|
|
||||||
case ISSUE_ASSET:
|
case ISSUE_ASSET:
|
||||||
return IssueAssetTransactionTransformer.fromByteBuffer(byteBuffer);
|
return IssueAssetTransactionTransformer.fromByteBuffer(byteBuffer);
|
||||||
|
|
||||||
@ -68,6 +71,9 @@ public class TransactionTransformer extends Transformer {
|
|||||||
case PAYMENT:
|
case PAYMENT:
|
||||||
return PaymentTransactionTransformer.getDataLength(transactionData);
|
return PaymentTransactionTransformer.getDataLength(transactionData);
|
||||||
|
|
||||||
|
case CREATE_POLL:
|
||||||
|
return CreatePollTransactionTransformer.getDataLength(transactionData);
|
||||||
|
|
||||||
case ISSUE_ASSET:
|
case ISSUE_ASSET:
|
||||||
return IssueAssetTransactionTransformer.getDataLength(transactionData);
|
return IssueAssetTransactionTransformer.getDataLength(transactionData);
|
||||||
|
|
||||||
@ -99,6 +105,9 @@ public class TransactionTransformer extends Transformer {
|
|||||||
case PAYMENT:
|
case PAYMENT:
|
||||||
return PaymentTransactionTransformer.toBytes(transactionData);
|
return PaymentTransactionTransformer.toBytes(transactionData);
|
||||||
|
|
||||||
|
case CREATE_POLL:
|
||||||
|
return CreatePollTransactionTransformer.toBytes(transactionData);
|
||||||
|
|
||||||
case ISSUE_ASSET:
|
case ISSUE_ASSET:
|
||||||
return IssueAssetTransactionTransformer.toBytes(transactionData);
|
return IssueAssetTransactionTransformer.toBytes(transactionData);
|
||||||
|
|
||||||
@ -130,6 +139,9 @@ public class TransactionTransformer extends Transformer {
|
|||||||
case PAYMENT:
|
case PAYMENT:
|
||||||
return PaymentTransactionTransformer.toJSON(transactionData);
|
return PaymentTransactionTransformer.toJSON(transactionData);
|
||||||
|
|
||||||
|
case CREATE_POLL:
|
||||||
|
return CreatePollTransactionTransformer.toJSON(transactionData);
|
||||||
|
|
||||||
case ISSUE_ASSET:
|
case ISSUE_ASSET:
|
||||||
return IssueAssetTransactionTransformer.toJSON(transactionData);
|
return IssueAssetTransactionTransformer.toJSON(transactionData);
|
||||||
|
|
||||||
|
@ -68,10 +68,16 @@ public class Serialization {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static String deserializeSizedString(ByteBuffer byteBuffer, int maxSize) throws TransformationException {
|
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();
|
int size = byteBuffer.getInt();
|
||||||
if (size > maxSize || size > byteBuffer.remaining())
|
if (size > maxSize)
|
||||||
throw new TransformationException("Serialized string too long");
|
throw new TransformationException("Serialized string too long");
|
||||||
|
|
||||||
|
if (size > byteBuffer.remaining())
|
||||||
|
throw new TransformationException("Byte data too short for serialized string");
|
||||||
|
|
||||||
byte[] bytes = new byte[size];
|
byte[] bytes = new byte[size];
|
||||||
byteBuffer.get(bytes);
|
byteBuffer.get(bytes);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user