forked from Qortal/qortal
Added Vote-on-poll transaction
* Added simple genesis block timestamp setting to help testing * Fixed setting account's last reference * Moved some Poll constants from CreatePollTransaction to Poll * HSQLDB: added TYPE PollOptionIndex * HSQLDB: added previous_option_index to VoteOnPollTransactions table to help orphaning * HSQLDB: renamed "poll" to "poll_name" in same table * HSQLDB: PollOptions now has additional option_index column * Improved TransactionTests to allow for different genesis block timestamps. * Also added VoteOnPollTransaction test
This commit is contained in:
parent
9651192a2d
commit
fe6cb4e366
57
src/data/transaction/VoteOnPollTransactionData.java
Normal file
57
src/data/transaction/VoteOnPollTransactionData.java
Normal file
@ -0,0 +1,57 @@
|
||||
package data.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import qora.transaction.Transaction.TransactionType;
|
||||
|
||||
public class VoteOnPollTransactionData extends TransactionData {
|
||||
|
||||
// Properties
|
||||
private byte[] voterPublicKey;
|
||||
private String pollName;
|
||||
private int optionIndex;
|
||||
private Integer previousOptionIndex;
|
||||
|
||||
// Constructors
|
||||
|
||||
public VoteOnPollTransactionData(byte[] voterPublicKey, String pollName, int optionIndex, Integer previousOptionIndex, BigDecimal fee, long timestamp,
|
||||
byte[] reference, byte[] signature) {
|
||||
super(TransactionType.VOTE_ON_POLL, fee, voterPublicKey, timestamp, reference, signature);
|
||||
|
||||
this.voterPublicKey = voterPublicKey;
|
||||
this.pollName = pollName;
|
||||
this.optionIndex = optionIndex;
|
||||
this.previousOptionIndex = previousOptionIndex;
|
||||
}
|
||||
|
||||
public VoteOnPollTransactionData(byte[] voterPublicKey, String pollName, int optionIndex, BigDecimal fee, long timestamp, byte[] reference,
|
||||
byte[] signature) {
|
||||
this(voterPublicKey, pollName, optionIndex, null, fee, timestamp, reference, signature);
|
||||
}
|
||||
|
||||
public VoteOnPollTransactionData(byte[] voterPublicKey, String pollName, int optionIndex, BigDecimal fee, long timestamp, byte[] reference) {
|
||||
this(voterPublicKey, pollName, optionIndex, null, fee, timestamp, reference, null);
|
||||
}
|
||||
|
||||
// Getters / setters
|
||||
|
||||
public byte[] getVoterPublicKey() {
|
||||
return this.voterPublicKey;
|
||||
}
|
||||
|
||||
public String getPollName() {
|
||||
return this.pollName;
|
||||
}
|
||||
|
||||
public int getOptionIndex() {
|
||||
return this.optionIndex;
|
||||
}
|
||||
|
||||
public Integer getPreviousOptionIndex() {
|
||||
return this.previousOptionIndex;
|
||||
}
|
||||
|
||||
public void setPreviousOptionIndex(Integer previousOptionIndex) {
|
||||
this.previousOptionIndex = previousOptionIndex;
|
||||
}
|
||||
|
||||
}
|
32
src/data/voting/VoteOnPollData.java
Normal file
32
src/data/voting/VoteOnPollData.java
Normal file
@ -0,0 +1,32 @@
|
||||
package data.voting;
|
||||
|
||||
public class VoteOnPollData {
|
||||
|
||||
// Properties
|
||||
private String pollName;
|
||||
private byte[] voterPublicKey;
|
||||
private int optionIndex;
|
||||
|
||||
// Constructors
|
||||
|
||||
public VoteOnPollData(String pollName, byte[] voterPublicKey, int optionIndex) {
|
||||
this.pollName = pollName;
|
||||
this.voterPublicKey = voterPublicKey;
|
||||
this.optionIndex = optionIndex;
|
||||
}
|
||||
|
||||
// Getters/setters
|
||||
|
||||
public String getPollName() {
|
||||
return this.pollName;
|
||||
}
|
||||
|
||||
public byte[] getVoterPublicKey() {
|
||||
return this.voterPublicKey;
|
||||
}
|
||||
|
||||
public int getOptionIndex() {
|
||||
return this.optionIndex;
|
||||
}
|
||||
|
||||
}
|
@ -158,6 +158,8 @@ public class Account {
|
||||
* @throws DataException
|
||||
*/
|
||||
public void setLastReference(byte[] reference) throws DataException {
|
||||
accountData.setReference(reference);
|
||||
|
||||
this.repository.getAccountRepository().save(accountData);
|
||||
}
|
||||
|
||||
|
@ -17,22 +17,24 @@ import qora.crypto.Crypto;
|
||||
import qora.transaction.Transaction;
|
||||
import repository.DataException;
|
||||
import repository.Repository;
|
||||
import utils.NTP;
|
||||
import settings.Settings;
|
||||
|
||||
public class GenesisBlock extends Block {
|
||||
|
||||
// Properties
|
||||
private static final int GENESIS_BLOCK_VERSION = 1;
|
||||
private static final byte[] GENESIS_REFERENCE = new byte[] { 1, 1, 1, 1, 1, 1, 1, 1 }; // NOTE: Neither 64 nor 128 bytes!
|
||||
private static final BigDecimal GENESIS_GENERATING_BALANCE = BigDecimal.valueOf(10_000_000L).setScale(8);
|
||||
private static final byte[] GENESIS_GENERATOR_PUBLIC_KEY = GenesisAccount.PUBLIC_KEY; // NOTE: 8 bytes not 32 bytes!
|
||||
private static final long GENESIS_TIMESTAMP = 1400247274336L; // QORA RELEASE: Fri May 16 13:34:34.336 2014 UTC
|
||||
public static final long GENESIS_TIMESTAMP = 1400247274336L; // QORA RELEASE: Fri May 16 13:34:34.336 2014 UTC
|
||||
private static final byte[] GENESIS_GENERATOR_SIGNATURE = calcSignature();
|
||||
private static final byte[] GENESIS_TRANSACTIONS_SIGNATURE = calcSignature();
|
||||
|
||||
// Constructors
|
||||
|
||||
public GenesisBlock(Repository repository) throws DataException {
|
||||
super(repository, new BlockData(GENESIS_BLOCK_VERSION, GENESIS_REFERENCE, 0, BigDecimal.ZERO.setScale(8), GENESIS_TRANSACTIONS_SIGNATURE, 1,
|
||||
GENESIS_TIMESTAMP, GENESIS_GENERATING_BALANCE, GENESIS_GENERATOR_PUBLIC_KEY, GENESIS_GENERATOR_SIGNATURE, null, null));
|
||||
Settings.getInstance().getGenesisTimestamp(), GENESIS_GENERATING_BALANCE, GENESIS_GENERATOR_PUBLIC_KEY, GENESIS_GENERATOR_SIGNATURE, null, null));
|
||||
|
||||
this.transactions = new ArrayList<Transaction>();
|
||||
|
||||
|
@ -24,11 +24,6 @@ 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) {
|
||||
@ -89,12 +84,12 @@ public class CreatePollTransaction extends Transaction {
|
||||
return ValidationResult.INVALID_ADDRESS;
|
||||
|
||||
// Check name size bounds
|
||||
if (createPollTransactionData.getPollName().length() < 1 || createPollTransactionData.getPollName().length() > CreatePollTransaction.MAX_NAME_SIZE)
|
||||
if (createPollTransactionData.getPollName().length() < 1 || createPollTransactionData.getPollName().length() > Poll.MAX_NAME_SIZE)
|
||||
return ValidationResult.INVALID_NAME_LENGTH;
|
||||
|
||||
// Check description size bounds
|
||||
if (createPollTransactionData.getDescription().length() < 1
|
||||
|| createPollTransactionData.getDescription().length() > CreatePollTransaction.MAX_DESCRIPTION_SIZE)
|
||||
|| createPollTransactionData.getDescription().length() > Poll.MAX_DESCRIPTION_SIZE)
|
||||
return ValidationResult.INVALID_DESCRIPTION_LENGTH;
|
||||
|
||||
// Check poll name is lowercase
|
||||
@ -110,7 +105,7 @@ public class CreatePollTransaction extends Transaction {
|
||||
// Check number of options
|
||||
List<PollOptionData> pollOptions = createPollTransactionData.getPollOptions();
|
||||
int pollOptionsCount = pollOptions.size();
|
||||
if (pollOptionsCount < 1 || pollOptionsCount > MAX_OPTIONS)
|
||||
if (pollOptionsCount < 1 || pollOptionsCount > Poll.MAX_OPTIONS)
|
||||
return ValidationResult.INVALID_OPTIONS_COUNT;
|
||||
|
||||
// Check each option
|
||||
@ -118,7 +113,7 @@ public class CreatePollTransaction extends Transaction {
|
||||
for (PollOptionData pollOptionData : pollOptions) {
|
||||
// Check option length
|
||||
int optionNameLength = pollOptionData.getOptionName().getBytes(StandardCharsets.UTF_8).length;
|
||||
if (optionNameLength < 1 || optionNameLength > MAX_NAME_SIZE)
|
||||
if (optionNameLength < 1 || optionNameLength > Poll.MAX_NAME_SIZE)
|
||||
return ValidationResult.INVALID_OPTION_LENGTH;
|
||||
|
||||
// Check option is unique. NOTE: NOT case-sensitive!
|
||||
|
@ -45,9 +45,10 @@ public abstract class Transaction {
|
||||
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(
|
||||
15), NAME_NOT_LOWER_CASE(17), INVALID_DESCRIPTION_LENGTH(18), INVALID_OPTIONS_COUNT(19), INVALID_OPTION_LENGTH(20), DUPLICATE_OPTION(
|
||||
21), POLL_ALREADY_EXISTS(22), 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);
|
||||
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);
|
||||
|
||||
public final int value;
|
||||
|
||||
@ -106,6 +107,9 @@ public abstract class Transaction {
|
||||
case CREATE_POLL:
|
||||
return new CreatePollTransaction(repository, transactionData);
|
||||
|
||||
case VOTE_ON_POLL:
|
||||
return new VoteOnPollTransaction(repository, transactionData);
|
||||
|
||||
case ISSUE_ASSET:
|
||||
return new IssueAssetTransaction(repository, transactionData);
|
||||
|
||||
|
162
src/qora/transaction/VoteOnPollTransaction.java
Normal file
162
src/qora/transaction/VoteOnPollTransaction.java
Normal file
@ -0,0 +1,162 @@
|
||||
package qora.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import data.transaction.TransactionData;
|
||||
import data.transaction.VoteOnPollTransactionData;
|
||||
import data.voting.PollData;
|
||||
import data.voting.PollOptionData;
|
||||
import data.voting.VoteOnPollData;
|
||||
import qora.account.Account;
|
||||
import qora.account.PublicKeyAccount;
|
||||
import qora.assets.Asset;
|
||||
import qora.block.BlockChain;
|
||||
import qora.voting.Poll;
|
||||
import repository.DataException;
|
||||
import repository.Repository;
|
||||
import repository.VotingRepository;
|
||||
|
||||
public class VoteOnPollTransaction extends Transaction {
|
||||
|
||||
// Properties
|
||||
private VoteOnPollTransactionData voteOnPollTransactionData;
|
||||
|
||||
// Constructors
|
||||
|
||||
public VoteOnPollTransaction(Repository repository, TransactionData transactionData) {
|
||||
super(repository, transactionData);
|
||||
|
||||
this.voteOnPollTransactionData = (VoteOnPollTransactionData) this.transactionData;
|
||||
}
|
||||
|
||||
// More information
|
||||
|
||||
@Override
|
||||
public List<Account> getRecipientAccounts() {
|
||||
return new ArrayList<Account>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInvolved(Account account) throws DataException {
|
||||
return account.getAddress().equals(this.getCreator().getAddress());
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal getAmount(Account account) throws DataException {
|
||||
BigDecimal amount = BigDecimal.ZERO.setScale(8);
|
||||
|
||||
if (account.getAddress().equals(this.getCreator().getAddress()))
|
||||
amount = amount.subtract(this.transactionData.getFee());
|
||||
|
||||
return amount;
|
||||
}
|
||||
|
||||
// Processing
|
||||
|
||||
@Override
|
||||
public ValidationResult isValid() throws DataException {
|
||||
// Are VoteOnPollTransactions even allowed at this point?
|
||||
// XXX In gen1 this used NTP.getTime() but surely the transaction's timestamp should be used?
|
||||
if (this.voteOnPollTransactionData.getTimestamp() < BlockChain.VOTING_RELEASE_TIMESTAMP)
|
||||
return ValidationResult.NOT_YET_RELEASED;
|
||||
|
||||
// Check name size bounds
|
||||
if (voteOnPollTransactionData.getPollName().length() < 1 || voteOnPollTransactionData.getPollName().length() > Poll.MAX_NAME_SIZE)
|
||||
return ValidationResult.INVALID_NAME_LENGTH;
|
||||
|
||||
// Check poll name is lowercase
|
||||
if (!voteOnPollTransactionData.getPollName().equals(voteOnPollTransactionData.getPollName().toLowerCase()))
|
||||
return ValidationResult.NAME_NOT_LOWER_CASE;
|
||||
|
||||
VotingRepository votingRepository = this.repository.getVotingRepository();
|
||||
|
||||
// Check poll exists
|
||||
PollData pollData = votingRepository.fromPollName(voteOnPollTransactionData.getPollName());
|
||||
if (pollData == null)
|
||||
return ValidationResult.POLL_DOES_NOT_EXIST;
|
||||
|
||||
// Check poll option index is within bounds
|
||||
List<PollOptionData> pollOptions = pollData.getPollOptions();
|
||||
int optionIndex = voteOnPollTransactionData.getOptionIndex();
|
||||
|
||||
if (optionIndex < 0 || optionIndex > pollOptions.size() - 1)
|
||||
return ValidationResult.POLL_OPTION_DOES_NOT_EXIST;
|
||||
|
||||
// Check if vote already exists
|
||||
VoteOnPollData voteOnPollData = votingRepository.getVote(voteOnPollTransactionData.getPollName(), voteOnPollTransactionData.getVoterPublicKey());
|
||||
if (voteOnPollData != null && voteOnPollData.getOptionIndex() == optionIndex)
|
||||
return ValidationResult.ALREADY_VOTED_FOR_THAT_OPTION;
|
||||
|
||||
// Check fee is positive
|
||||
if (voteOnPollTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0)
|
||||
return ValidationResult.NEGATIVE_FEE;
|
||||
|
||||
// Check reference is correct
|
||||
PublicKeyAccount creator = new PublicKeyAccount(this.repository, voteOnPollTransactionData.getCreatorPublicKey());
|
||||
|
||||
if (!Arrays.equals(creator.getLastReference(), voteOnPollTransactionData.getReference()))
|
||||
return ValidationResult.INVALID_REFERENCE;
|
||||
|
||||
// Check issuer has enough funds
|
||||
if (creator.getConfirmedBalance(Asset.QORA).compareTo(voteOnPollTransactionData.getFee()) == -1)
|
||||
return ValidationResult.NO_BALANCE;
|
||||
|
||||
return ValidationResult.OK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process() throws DataException {
|
||||
// Update voter's balance
|
||||
Account voter = new PublicKeyAccount(this.repository, voteOnPollTransactionData.getVoterPublicKey());
|
||||
voter.setConfirmedBalance(Asset.QORA, voter.getConfirmedBalance(Asset.QORA).subtract(voteOnPollTransactionData.getFee()));
|
||||
|
||||
// Update vote's reference
|
||||
voter.setLastReference(voteOnPollTransactionData.getSignature());
|
||||
|
||||
VotingRepository votingRepository = this.repository.getVotingRepository();
|
||||
|
||||
// Check for previous vote so we can save option in case of orphaning
|
||||
VoteOnPollData previousVoteOnPollData = votingRepository.getVote(voteOnPollTransactionData.getPollName(),
|
||||
voteOnPollTransactionData.getVoterPublicKey());
|
||||
if (previousVoteOnPollData != null)
|
||||
voteOnPollTransactionData.setPreviousOptionIndex(previousVoteOnPollData.getOptionIndex());
|
||||
|
||||
// Save this transaction, now with possible previous vote
|
||||
this.repository.getTransactionRepository().save(voteOnPollTransactionData);
|
||||
|
||||
// Apply vote to poll
|
||||
VoteOnPollData newVoteOnPollData = new VoteOnPollData(voteOnPollTransactionData.getPollName(), voteOnPollTransactionData.getVoterPublicKey(),
|
||||
voteOnPollTransactionData.getOptionIndex());
|
||||
votingRepository.save(newVoteOnPollData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void orphan() throws DataException {
|
||||
// Update issuer's balance
|
||||
Account voter = new PublicKeyAccount(this.repository, voteOnPollTransactionData.getVoterPublicKey());
|
||||
voter.setConfirmedBalance(Asset.QORA, voter.getConfirmedBalance(Asset.QORA).add(voteOnPollTransactionData.getFee()));
|
||||
|
||||
// Update issuer's reference
|
||||
voter.setLastReference(voteOnPollTransactionData.getReference());
|
||||
|
||||
// Does this transaction have previous vote info?
|
||||
VotingRepository votingRepository = this.repository.getVotingRepository();
|
||||
Integer previousOptionIndex = voteOnPollTransactionData.getPreviousOptionIndex();
|
||||
if (previousOptionIndex != null) {
|
||||
// Reinstate previous vote
|
||||
VoteOnPollData previousVoteOnPollData = new VoteOnPollData(voteOnPollTransactionData.getPollName(), voteOnPollTransactionData.getVoterPublicKey(),
|
||||
previousOptionIndex);
|
||||
votingRepository.save(previousVoteOnPollData);
|
||||
} else {
|
||||
// Delete vote
|
||||
votingRepository.delete(voteOnPollTransactionData.getPollName(), voteOnPollTransactionData.getVoterPublicKey());
|
||||
}
|
||||
|
||||
// Delete this transaction itself
|
||||
this.repository.getTransactionRepository().delete(voteOnPollTransactionData);
|
||||
}
|
||||
|
||||
}
|
@ -11,6 +11,11 @@ public class Poll {
|
||||
private Repository repository;
|
||||
private PollData pollData;
|
||||
|
||||
// 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
|
||||
|
||||
/**
|
||||
|
@ -1,9 +1,12 @@
|
||||
package repository;
|
||||
|
||||
import data.voting.PollData;
|
||||
import data.voting.VoteOnPollData;
|
||||
|
||||
public interface VotingRepository {
|
||||
|
||||
// Polls
|
||||
|
||||
public PollData fromPollName(String pollName) throws DataException;
|
||||
|
||||
public boolean pollExists(String pollName) throws DataException;
|
||||
@ -12,4 +15,12 @@ public interface VotingRepository {
|
||||
|
||||
public void delete(String pollName) throws DataException;
|
||||
|
||||
// Votes
|
||||
|
||||
public VoteOnPollData getVote(String pollName, byte[] voterPublicKey) throws DataException;
|
||||
|
||||
public void save(VoteOnPollData voteOnPollData) throws DataException;
|
||||
|
||||
public void delete(String pollName, byte[] voterPublicKey) throws DataException;
|
||||
|
||||
}
|
||||
|
@ -88,6 +88,7 @@ public class HSQLDBDatabaseUpdates {
|
||||
stmt.execute("CREATE TYPE NameData AS VARCHAR(4000)");
|
||||
stmt.execute("CREATE TYPE PollName AS VARCHAR(400) COLLATE SQL_TEXT_UCC");
|
||||
stmt.execute("CREATE TYPE PollOption AS VARCHAR(400) COLLATE SQL_TEXT_UCC");
|
||||
stmt.execute("CREATE TYPE PollOptionIndex AS INTEGER");
|
||||
stmt.execute("CREATE TYPE DataHash AS VARCHAR(100)");
|
||||
stmt.execute("CREATE TYPE AssetID AS BIGINT");
|
||||
stmt.execute("CREATE TYPE AssetName AS VARCHAR(400) COLLATE SQL_TEXT_UCC");
|
||||
@ -206,8 +207,8 @@ public class HSQLDBDatabaseUpdates {
|
||||
|
||||
case 11:
|
||||
// Vote On Poll Transactions
|
||||
stmt.execute("CREATE TABLE VoteOnPollTransactions (signature Signature, voter QoraPublicKey NOT NULL, poll PollName NOT NULL, "
|
||||
+ "option_index INTEGER NOT NULL, "
|
||||
stmt.execute("CREATE TABLE VoteOnPollTransactions (signature Signature, voter QoraPublicKey NOT NULL, poll_name PollName NOT NULL, "
|
||||
+ "option_index PollOptionIndex NOT NULL, previous_option_index PollOptionIndex, "
|
||||
+ "PRIMARY KEY (signature), FOREIGN KEY (signature) REFERENCES Transactions (signature) ON DELETE CASCADE)");
|
||||
break;
|
||||
|
||||
@ -314,10 +315,10 @@ public class HSQLDBDatabaseUpdates {
|
||||
"CREATE TABLE Polls (poll_name PollName, description VARCHAR(4000) NOT NULL, creator QoraPublicKey NOT NULL, owner QoraAddress NOT NULL, "
|
||||
+ "published TIMESTAMP NOT NULL, " + "PRIMARY KEY (poll_name))");
|
||||
// Various options available on a poll
|
||||
stmt.execute("CREATE TABLE PollOptions (poll_name PollName, option_name PollOption, "
|
||||
+ "PRIMARY KEY (poll_name, option_name), FOREIGN KEY (poll_name) REFERENCES Polls (poll_name) ON DELETE CASCADE)");
|
||||
stmt.execute("CREATE TABLE PollOptions (poll_name PollName, option_index TINYINT NOT NULL, option_name PollOption, "
|
||||
+ "PRIMARY KEY (poll_name, option_index), FOREIGN KEY (poll_name) REFERENCES Polls (poll_name) ON DELETE CASCADE)");
|
||||
// Actual votes cast on a poll by voting users. NOTE: only one vote per user supported at this time.
|
||||
stmt.execute("CREATE TABLE PollVotes (poll_name PollName, voter QoraPublicKey, option_name PollOption, "
|
||||
stmt.execute("CREATE TABLE PollVotes (poll_name PollName, voter QoraPublicKey, option_index PollOptionIndex NOT NULL, "
|
||||
+ "PRIMARY KEY (poll_name, voter), FOREIGN KEY (poll_name) REFERENCES Polls (poll_name) ON DELETE CASCADE)");
|
||||
// For when a user wants to lookup poll they own
|
||||
stmt.execute("CREATE INDEX PollOwnerIndex on Polls (owner)");
|
||||
|
@ -8,6 +8,7 @@ import java.util.List;
|
||||
|
||||
import data.voting.PollData;
|
||||
import data.voting.PollOptionData;
|
||||
import data.voting.VoteOnPollData;
|
||||
import repository.VotingRepository;
|
||||
import repository.DataException;
|
||||
|
||||
@ -19,6 +20,8 @@ public class HSQLDBVotingRepository implements VotingRepository {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
// Votes
|
||||
|
||||
public PollData fromPollName(String pollName) throws DataException {
|
||||
try {
|
||||
ResultSet resultSet = this.repository.checkedExecute("SELECT description, creator, owner, published FROM Polls WHERE poll_name = ?", pollName);
|
||||
@ -30,7 +33,7 @@ public class HSQLDBVotingRepository implements VotingRepository {
|
||||
String owner = resultSet.getString(3);
|
||||
long published = resultSet.getTimestamp(4).getTime();
|
||||
|
||||
resultSet = this.repository.checkedExecute("SELECT option_name FROM PollOptions where poll_name = ?", pollName);
|
||||
resultSet = this.repository.checkedExecute("SELECT option_name FROM PollOptions where poll_name = ? ORDER BY option_index ASC", pollName);
|
||||
if (resultSet == null)
|
||||
return null;
|
||||
|
||||
@ -70,10 +73,13 @@ public class HSQLDBVotingRepository implements VotingRepository {
|
||||
}
|
||||
|
||||
// Now attempt to save poll options
|
||||
for (PollOptionData pollOptionData : pollData.getPollOptions()) {
|
||||
List<PollOptionData> pollOptions = pollData.getPollOptions();
|
||||
for (int optionIndex = 0; optionIndex < pollOptions.size(); ++optionIndex) {
|
||||
PollOptionData pollOptionData = pollOptions.get(optionIndex);
|
||||
|
||||
HSQLDBSaver optionSaveHelper = new HSQLDBSaver("PollOptions");
|
||||
|
||||
optionSaveHelper.bind("poll_name", pollData.getPollName()).bind("option_name", pollOptionData.getOptionName());
|
||||
optionSaveHelper.bind("poll_name", pollData.getPollName()).bind("option_index", optionIndex).bind("option_name", pollOptionData.getOptionName());
|
||||
|
||||
try {
|
||||
optionSaveHelper.execute(this.repository);
|
||||
@ -93,4 +99,42 @@ public class HSQLDBVotingRepository implements VotingRepository {
|
||||
}
|
||||
}
|
||||
|
||||
// Votes
|
||||
|
||||
public VoteOnPollData getVote(String pollName, byte[] voterPublicKey) throws DataException {
|
||||
try {
|
||||
ResultSet resultSet = this.repository.checkedExecute("SELECT option_index FROM PollVotes WHERE poll_name = ? AND voter = ?", pollName,
|
||||
voterPublicKey);
|
||||
if (resultSet == null)
|
||||
return null;
|
||||
|
||||
int optionIndex = resultSet.getInt(1);
|
||||
|
||||
return new VoteOnPollData(pollName, voterPublicKey, optionIndex);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to fetch poll vote from repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void save(VoteOnPollData voteOnPollData) throws DataException {
|
||||
HSQLDBSaver saveHelper = new HSQLDBSaver("PollVotes");
|
||||
|
||||
saveHelper.bind("poll_name", voteOnPollData.getPollName()).bind("voter", voteOnPollData.getVoterPublicKey()).bind("option_index",
|
||||
voteOnPollData.getOptionIndex());
|
||||
|
||||
try {
|
||||
saveHelper.execute(this.repository);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to save poll vote into repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void delete(String pollName, byte[] voterPublicKey) throws DataException {
|
||||
try {
|
||||
this.repository.checkedExecute("DELETE FROM PollVotes WHERE poll_name = ? AND voter = ?", pollName, voterPublicKey);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to delete poll vote from repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
private HSQLDBGenesisTransactionRepository genesisTransactionRepository;
|
||||
private HSQLDBPaymentTransactionRepository paymentTransactionRepository;
|
||||
private HSQLDBCreatePollTransactionRepository createPollTransactionRepository;
|
||||
private HSQLDBVoteOnPollTransactionRepository voteOnPollTransactionRepository;
|
||||
private HSQLDBIssueAssetTransactionRepository issueAssetTransactionRepository;
|
||||
private HSQLDBTransferAssetTransactionRepository transferAssetTransactionRepository;
|
||||
private HSQLDBCreateOrderTransactionRepository createOrderTransactionRepository;
|
||||
@ -34,6 +35,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
this.genesisTransactionRepository = new HSQLDBGenesisTransactionRepository(repository);
|
||||
this.paymentTransactionRepository = new HSQLDBPaymentTransactionRepository(repository);
|
||||
this.createPollTransactionRepository = new HSQLDBCreatePollTransactionRepository(repository);
|
||||
this.voteOnPollTransactionRepository = new HSQLDBVoteOnPollTransactionRepository(repository);
|
||||
this.issueAssetTransactionRepository = new HSQLDBIssueAssetTransactionRepository(repository);
|
||||
this.transferAssetTransactionRepository = new HSQLDBTransferAssetTransactionRepository(repository);
|
||||
this.createOrderTransactionRepository = new HSQLDBCreateOrderTransactionRepository(repository);
|
||||
@ -93,6 +95,9 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
case CREATE_POLL:
|
||||
return this.createPollTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee);
|
||||
|
||||
case VOTE_ON_POLL:
|
||||
return this.voteOnPollTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee);
|
||||
|
||||
case ISSUE_ASSET:
|
||||
return this.issueAssetTransactionRepository.fromBase(signature, reference, creatorPublicKey, timestamp, fee);
|
||||
|
||||
@ -219,6 +224,10 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
this.createPollTransactionRepository.save(transactionData);
|
||||
break;
|
||||
|
||||
case VOTE_ON_POLL:
|
||||
this.voteOnPollTransactionRepository.save(transactionData);
|
||||
break;
|
||||
|
||||
case ISSUE_ASSET:
|
||||
this.issueAssetTransactionRepository.save(transactionData);
|
||||
break;
|
||||
|
@ -0,0 +1,53 @@
|
||||
package repository.hsqldb.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import data.transaction.VoteOnPollTransactionData;
|
||||
import data.transaction.TransactionData;
|
||||
import repository.DataException;
|
||||
import repository.hsqldb.HSQLDBRepository;
|
||||
import repository.hsqldb.HSQLDBSaver;
|
||||
|
||||
public class HSQLDBVoteOnPollTransactionRepository extends HSQLDBTransactionRepository {
|
||||
|
||||
public HSQLDBVoteOnPollTransactionRepository(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 poll_name, option_index, previous_option_index FROM VoteOnPollTransactions WHERE signature = ?", signature);
|
||||
if (rs == null)
|
||||
return null;
|
||||
|
||||
String pollName = rs.getString(1);
|
||||
int optionIndex = rs.getInt(2);
|
||||
Integer previousOptionIndex = rs.getInt(3);
|
||||
|
||||
return new VoteOnPollTransactionData(creatorPublicKey, pollName, optionIndex, previousOptionIndex, fee, timestamp, reference, signature);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to fetch vote on poll transaction from repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(TransactionData transactionData) throws DataException {
|
||||
VoteOnPollTransactionData voteOnPollTransactionData = (VoteOnPollTransactionData) transactionData;
|
||||
|
||||
HSQLDBSaver saveHelper = new HSQLDBSaver("VoteOnPollTransactions");
|
||||
|
||||
saveHelper.bind("signature", voteOnPollTransactionData.getSignature()).bind("poll_name", voteOnPollTransactionData.getPollName())
|
||||
.bind("voter", voteOnPollTransactionData.getVoterPublicKey()).bind("option_index", voteOnPollTransactionData.getOptionIndex())
|
||||
.bind("previous_option_index", voteOnPollTransactionData.getPreviousOptionIndex());
|
||||
|
||||
try {
|
||||
saveHelper.execute(this.repository);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to save vote on poll transaction into repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,13 +1,38 @@
|
||||
package settings;
|
||||
|
||||
import qora.block.GenesisBlock;
|
||||
|
||||
public class Settings {
|
||||
|
||||
private static Settings instance;
|
||||
|
||||
// Properties
|
||||
private long genesisTimestamp = -1;
|
||||
|
||||
public static Settings getInstance() {
|
||||
return new Settings();
|
||||
if (instance == null)
|
||||
instance = new Settings();
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
public int getMaxBytePerFee() {
|
||||
return 1024;
|
||||
}
|
||||
|
||||
public long getGenesisTimestamp() {
|
||||
if (this.genesisTimestamp != -1)
|
||||
return this.genesisTimestamp;
|
||||
|
||||
return GenesisBlock.GENESIS_TIMESTAMP;
|
||||
}
|
||||
|
||||
public void setGenesisTimestamp(long timestamp) {
|
||||
this.genesisTimestamp = timestamp;
|
||||
}
|
||||
|
||||
public void unsetGenesisTimestamp() {
|
||||
this.genesisTimestamp = -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,12 +4,10 @@ import static org.junit.Assert.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
@ -19,6 +17,7 @@ import data.account.AccountData;
|
||||
import data.block.BlockData;
|
||||
import data.transaction.CreatePollTransactionData;
|
||||
import data.transaction.PaymentTransactionData;
|
||||
import data.transaction.VoteOnPollTransactionData;
|
||||
import data.voting.PollData;
|
||||
import data.voting.PollOptionData;
|
||||
import qora.account.Account;
|
||||
@ -31,12 +30,14 @@ import qora.transaction.CreatePollTransaction;
|
||||
import qora.transaction.PaymentTransaction;
|
||||
import qora.transaction.Transaction;
|
||||
import qora.transaction.Transaction.ValidationResult;
|
||||
import qora.transaction.VoteOnPollTransaction;
|
||||
import repository.AccountRepository;
|
||||
import repository.DataException;
|
||||
import repository.Repository;
|
||||
import repository.RepositoryFactory;
|
||||
import repository.RepositoryManager;
|
||||
import repository.hsqldb.HSQLDBRepositoryFactory;
|
||||
import settings.Settings;
|
||||
|
||||
// Don't extend Common as we want to use an in-memory database
|
||||
public class TransactionTests {
|
||||
@ -57,8 +58,7 @@ public class TransactionTests {
|
||||
private PrivateKeyAccount generator;
|
||||
private byte[] reference;
|
||||
|
||||
@Before
|
||||
public void createTestAccounts() throws DataException {
|
||||
public void createTestAccounts(Long genesisTimestamp) throws DataException {
|
||||
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(connectionUrl);
|
||||
RepositoryManager.setRepositoryFactory(repositoryFactory);
|
||||
|
||||
@ -66,6 +66,12 @@ public class TransactionTests {
|
||||
assertEquals("Blockchain should be empty for this test", 0, repository.getBlockRepository().getBlockchainHeight());
|
||||
}
|
||||
|
||||
// [Un]set genesis timestamp as required by test
|
||||
if (genesisTimestamp != null)
|
||||
Settings.getInstance().setGenesisTimestamp(genesisTimestamp);
|
||||
else
|
||||
Settings.getInstance().unsetGenesisTimestamp();
|
||||
|
||||
// This needs to be called outside of acquiring our own repository or it will deadlock
|
||||
BlockChain.validate();
|
||||
|
||||
@ -102,6 +108,8 @@ public class TransactionTests {
|
||||
|
||||
@Test
|
||||
public void testPaymentTransaction() throws DataException {
|
||||
createTestAccounts(null);
|
||||
|
||||
// Make a new payment transaction
|
||||
Account recipient = new PublicKeyAccount(repository, recipientSeed);
|
||||
BigDecimal amount = BigDecimal.valueOf(1_000L);
|
||||
@ -144,7 +152,8 @@ public class TransactionTests {
|
||||
|
||||
@Test
|
||||
public void testCreatePollTransaction() throws DataException {
|
||||
// XXX This test fails unless GenesisBlock's timestamp is set to something after BlockChain.VOTING_RELEASE_TIMESTAMP so we need better testing setup
|
||||
// This test requires GenesisBlock's timestamp is set to something after BlockChain.VOTING_RELEASE_TIMESTAMP
|
||||
createTestAccounts(BlockChain.VOTING_RELEASE_TIMESTAMP + 1_000L);
|
||||
|
||||
// Make a new create poll transaction
|
||||
String pollName = "test poll";
|
||||
@ -166,7 +175,7 @@ public class TransactionTests {
|
||||
assertTrue(createPollTransaction.isSignatureValid());
|
||||
assertEquals(ValidationResult.OK, createPollTransaction.isValid());
|
||||
|
||||
// Forge new block with payment transaction
|
||||
// Forge new block with transaction
|
||||
Block block = new Block(repository, genesisBlockData, generator, null, null);
|
||||
block.addTransaction(createPollTransactionData);
|
||||
block.sign();
|
||||
@ -190,6 +199,57 @@ public class TransactionTests {
|
||||
// Check poll was created
|
||||
PollData actualPollData = this.repository.getVotingRepository().fromPollName(pollName);
|
||||
assertNotNull(actualPollData);
|
||||
|
||||
// Check sender's reference
|
||||
assertTrue("Sender's new reference incorrect", Arrays.equals(createPollTransactionData.getSignature(), sender.getLastReference()));
|
||||
|
||||
// Update reference variable for use by other tests
|
||||
reference = sender.getLastReference();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVoteOnPollTransaction() throws DataException {
|
||||
// Create poll using another test
|
||||
testCreatePollTransaction();
|
||||
|
||||
// Try all options, plus invalid optionIndex (note use of <= for this)
|
||||
String pollName = "test poll";
|
||||
int pollOptionsSize = 3;
|
||||
BigDecimal fee = BigDecimal.ONE;
|
||||
long timestamp = genesisBlockData.getTimestamp() + 1_000;
|
||||
BlockData previousBlockData = genesisBlockData;
|
||||
|
||||
for (int optionIndex = 0; optionIndex <= pollOptionsSize; ++optionIndex) {
|
||||
// Make a vote-on-poll transaction
|
||||
VoteOnPollTransactionData voteOnPollTransactionData = new VoteOnPollTransactionData(sender.getPublicKey(), pollName, optionIndex, fee, timestamp,
|
||||
reference);
|
||||
|
||||
Transaction voteOnPollTransaction = new VoteOnPollTransaction(repository, voteOnPollTransactionData);
|
||||
voteOnPollTransaction.calcSignature(sender);
|
||||
assertTrue(voteOnPollTransaction.isSignatureValid());
|
||||
|
||||
if (optionIndex == pollOptionsSize) {
|
||||
assertEquals(ValidationResult.POLL_OPTION_DOES_NOT_EXIST, voteOnPollTransaction.isValid());
|
||||
break;
|
||||
}
|
||||
assertEquals(ValidationResult.OK, voteOnPollTransaction.isValid());
|
||||
|
||||
// Forge new block with transaction
|
||||
Block block = new Block(repository, previousBlockData, generator, null, null);
|
||||
block.addTransaction(voteOnPollTransactionData);
|
||||
block.sign();
|
||||
|
||||
assertTrue("Block signatures invalid", block.isSignatureValid());
|
||||
assertEquals("Block is invalid", Block.ValidationResult.OK, block.isValid());
|
||||
|
||||
block.process();
|
||||
repository.saveChanges();
|
||||
|
||||
// update variables for next round
|
||||
previousBlockData = block.getBlockData();
|
||||
timestamp += 1_000;
|
||||
reference = voteOnPollTransaction.getTransactionData().getSignature();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -18,7 +18,7 @@ import data.transaction.CreatePollTransactionData;
|
||||
import data.transaction.TransactionData;
|
||||
import data.voting.PollOptionData;
|
||||
import qora.account.PublicKeyAccount;
|
||||
import qora.transaction.CreatePollTransaction;
|
||||
import qora.voting.Poll;
|
||||
import transform.TransformationException;
|
||||
import utils.Base58;
|
||||
import utils.Serialization;
|
||||
@ -46,20 +46,20 @@ public class CreatePollTransactionTransformer extends TransactionTransformer {
|
||||
|
||||
String owner = Serialization.deserializeRecipient(byteBuffer);
|
||||
|
||||
String pollName = Serialization.deserializeSizedString(byteBuffer, CreatePollTransaction.MAX_NAME_SIZE);
|
||||
String description = Serialization.deserializeSizedString(byteBuffer, CreatePollTransaction.MAX_DESCRIPTION_SIZE);
|
||||
String pollName = Serialization.deserializeSizedString(byteBuffer, Poll.MAX_NAME_SIZE);
|
||||
String description = Serialization.deserializeSizedString(byteBuffer, Poll.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)
|
||||
if (optionsCount < 1 || optionsCount > Poll.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);
|
||||
for (int optionIndex = 0; optionIndex < optionsCount; ++optionIndex) {
|
||||
String optionName = Serialization.deserializeSizedString(byteBuffer, Poll.MAX_NAME_SIZE);
|
||||
|
||||
pollOptions.add(new PollOptionData(optionName));
|
||||
}
|
||||
@ -106,9 +106,8 @@ public class CreatePollTransactionTransformer extends TransactionTransformer {
|
||||
List<PollOptionData> pollOptions = createPollTransactionData.getPollOptions();
|
||||
bytes.write(Ints.toByteArray(pollOptions.size()));
|
||||
|
||||
for (PollOptionData pollOptionData : pollOptions) {
|
||||
for (PollOptionData pollOptionData : pollOptions)
|
||||
Serialization.serializeSizedString(bytes, pollOptionData.getOptionName());
|
||||
}
|
||||
|
||||
Serialization.serializeBigDecimal(bytes, createPollTransactionData.getFee());
|
||||
|
||||
@ -138,9 +137,8 @@ public class CreatePollTransactionTransformer extends TransactionTransformer {
|
||||
json.put("description", createPollTransactionData.getDescription());
|
||||
|
||||
JSONArray options = new JSONArray();
|
||||
for (PollOptionData optionData : createPollTransactionData.getPollOptions()) {
|
||||
for (PollOptionData optionData : createPollTransactionData.getPollOptions())
|
||||
options.add(optionData.getOptionName());
|
||||
}
|
||||
|
||||
json.put("options", options);
|
||||
} catch (ClassCastException e) {
|
||||
|
@ -40,6 +40,9 @@ public class TransactionTransformer extends Transformer {
|
||||
case CREATE_POLL:
|
||||
return CreatePollTransactionTransformer.fromByteBuffer(byteBuffer);
|
||||
|
||||
case VOTE_ON_POLL:
|
||||
return VoteOnPollTransactionTransformer.fromByteBuffer(byteBuffer);
|
||||
|
||||
case ISSUE_ASSET:
|
||||
return IssueAssetTransactionTransformer.fromByteBuffer(byteBuffer);
|
||||
|
||||
@ -74,6 +77,9 @@ public class TransactionTransformer extends Transformer {
|
||||
case CREATE_POLL:
|
||||
return CreatePollTransactionTransformer.getDataLength(transactionData);
|
||||
|
||||
case VOTE_ON_POLL:
|
||||
return VoteOnPollTransactionTransformer.getDataLength(transactionData);
|
||||
|
||||
case ISSUE_ASSET:
|
||||
return IssueAssetTransactionTransformer.getDataLength(transactionData);
|
||||
|
||||
@ -108,6 +114,9 @@ public class TransactionTransformer extends Transformer {
|
||||
case CREATE_POLL:
|
||||
return CreatePollTransactionTransformer.toBytes(transactionData);
|
||||
|
||||
case VOTE_ON_POLL:
|
||||
return VoteOnPollTransactionTransformer.toBytes(transactionData);
|
||||
|
||||
case ISSUE_ASSET:
|
||||
return IssueAssetTransactionTransformer.toBytes(transactionData);
|
||||
|
||||
@ -142,6 +151,9 @@ public class TransactionTransformer extends Transformer {
|
||||
case CREATE_POLL:
|
||||
return CreatePollTransactionTransformer.toJSON(transactionData);
|
||||
|
||||
case VOTE_ON_POLL:
|
||||
return VoteOnPollTransactionTransformer.toJSON(transactionData);
|
||||
|
||||
case ISSUE_ASSET:
|
||||
return IssueAssetTransactionTransformer.toJSON(transactionData);
|
||||
|
||||
|
117
src/transform/transaction/VoteOnPollTransactionTransformer.java
Normal file
117
src/transform/transaction/VoteOnPollTransactionTransformer.java
Normal file
@ -0,0 +1,117 @@
|
||||
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.hash.HashCode;
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.google.common.primitives.Longs;
|
||||
|
||||
import data.transaction.TransactionData;
|
||||
import data.transaction.VoteOnPollTransactionData;
|
||||
import qora.account.PublicKeyAccount;
|
||||
import qora.voting.Poll;
|
||||
import transform.TransformationException;
|
||||
import utils.Serialization;
|
||||
|
||||
public class VoteOnPollTransactionTransformer extends TransactionTransformer {
|
||||
|
||||
// Property lengths
|
||||
private static final int VOTER_LENGTH = ADDRESS_LENGTH;
|
||||
private static final int NAME_SIZE_LENGTH = INT_LENGTH;
|
||||
private static final int OPTION_LENGTH = INT_LENGTH;
|
||||
|
||||
private static final int TYPELESS_DATALESS_LENGTH = BASE_TYPELESS_LENGTH + VOTER_LENGTH + NAME_SIZE_LENGTH;
|
||||
|
||||
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
|
||||
if (byteBuffer.remaining() < TYPELESS_DATALESS_LENGTH)
|
||||
throw new TransformationException("Byte data too short for VoteOnPollTransaction");
|
||||
|
||||
long timestamp = byteBuffer.getLong();
|
||||
|
||||
byte[] reference = new byte[REFERENCE_LENGTH];
|
||||
byteBuffer.get(reference);
|
||||
|
||||
byte[] voterPublicKey = Serialization.deserializePublicKey(byteBuffer);
|
||||
|
||||
String pollName = Serialization.deserializeSizedString(byteBuffer, Poll.MAX_NAME_SIZE);
|
||||
|
||||
// Make sure there are enough bytes left for poll options
|
||||
if (byteBuffer.remaining() < OPTION_LENGTH)
|
||||
throw new TransformationException("Byte data too short for VoteOnPollTransaction");
|
||||
|
||||
int optionIndex = byteBuffer.getInt();
|
||||
if (optionIndex < 0 || optionIndex >= Poll.MAX_OPTIONS)
|
||||
throw new TransformationException("Invalid option number for VoteOnPollTransaction");
|
||||
|
||||
// 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 VoteOnPollTransaction");
|
||||
|
||||
BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer);
|
||||
|
||||
byte[] signature = new byte[SIGNATURE_LENGTH];
|
||||
byteBuffer.get(signature);
|
||||
|
||||
return new VoteOnPollTransactionData(voterPublicKey, pollName, optionIndex, fee, timestamp, reference, signature);
|
||||
}
|
||||
|
||||
public static int getDataLength(TransactionData transactionData) throws TransformationException {
|
||||
VoteOnPollTransactionData voteOnPollTransactionData = (VoteOnPollTransactionData) transactionData;
|
||||
|
||||
int dataLength = TYPE_LENGTH + TYPELESS_DATALESS_LENGTH + voteOnPollTransactionData.getPollName().length();
|
||||
|
||||
return dataLength;
|
||||
}
|
||||
|
||||
public static byte[] toBytes(TransactionData transactionData) throws TransformationException {
|
||||
try {
|
||||
VoteOnPollTransactionData voteOnPollTransactionData = (VoteOnPollTransactionData) transactionData;
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
bytes.write(Ints.toByteArray(voteOnPollTransactionData.getType().value));
|
||||
bytes.write(Longs.toByteArray(voteOnPollTransactionData.getTimestamp()));
|
||||
bytes.write(voteOnPollTransactionData.getReference());
|
||||
|
||||
bytes.write(voteOnPollTransactionData.getVoterPublicKey());
|
||||
Serialization.serializeSizedString(bytes, voteOnPollTransactionData.getPollName());
|
||||
bytes.write(voteOnPollTransactionData.getOptionIndex());
|
||||
|
||||
Serialization.serializeBigDecimal(bytes, voteOnPollTransactionData.getFee());
|
||||
|
||||
if (voteOnPollTransactionData.getSignature() != null)
|
||||
bytes.write(voteOnPollTransactionData.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 {
|
||||
VoteOnPollTransactionData voteOnPollTransactionData = (VoteOnPollTransactionData) transactionData;
|
||||
|
||||
byte[] voterPublicKey = voteOnPollTransactionData.getVoterPublicKey();
|
||||
|
||||
json.put("voter", PublicKeyAccount.getAddress(voterPublicKey));
|
||||
json.put("voterPublicKey", HashCode.fromBytes(voterPublicKey).toString());
|
||||
|
||||
json.put("name", voteOnPollTransactionData.getPollName());
|
||||
json.put("optionIndex", voteOnPollTransactionData.getOptionIndex());
|
||||
} catch (ClassCastException e) {
|
||||
throw new TransformationException(e);
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user