From 9bb673ba82ee4d116c26e2410bd0b9e7d30b7b2d Mon Sep 17 00:00:00 2001 From: catbref Date: Sat, 1 Jun 2019 10:26:20 +0100 Subject: [PATCH] Interim commit of *Transaction refactoring Transaction subclass isValid methods have last reference checks removed. (Now handled by Transaction.hasValidReference) Some checks in subclass' isValid moved to isProcessable, typically for transaction types that require group-approval. Removed fee extraction and last-reference update code from subclass' process() method to Transaction.processReferencesAndFees. Other changes to transaction/block processing. --- src/main/java/org/qora/block/Block.java | 43 +++++++++--- .../java/org/qora/block/BlockGenerator.java | 6 +- .../java/org/qora/block/GenesisBlock.java | 11 ++- src/main/java/org/qora/payment/Payment.java | 68 +++++++++++++++---- .../org/qora/repository/ATRepository.java | 3 + .../org/qora/repository/GroupRepository.java | 4 ++ .../repository/hsqldb/HSQLDBATRepository.java | 9 +++ .../hsqldb/HSQLDBGroupRepository.java | 16 +++++ .../HSQLDBTransactionRepository.java | 7 +- .../transaction/AccountFlagsTransaction.java | 12 ---- .../transaction/AddGroupAdminTransaction.java | 30 +++----- .../transaction/ArbitraryTransaction.java | 24 ++++--- .../org/qora/transaction/AtTransaction.java | 16 ++++- .../qora/transaction/BuyNameTransaction.java | 12 ---- .../CancelAssetOrderTransaction.java | 13 ---- .../CancelGroupBanTransaction.java | 12 ---- .../CancelGroupInviteTransaction.java | 12 ---- .../CancelSellNameTransaction.java | 12 ---- .../transaction/CreateGroupTransaction.java | 25 +++---- .../transaction/CreatePollTransaction.java | 24 +++---- .../qora/transaction/DeployAtTransaction.java | 38 ++++++++--- .../transaction/EnableForgingTransaction.java | 11 --- .../qora/transaction/GenesisTransaction.java | 13 +++- .../transaction/GroupApprovalTransaction.java | 12 ---- .../qora/transaction/GroupBanTransaction.java | 12 ---- .../transaction/GroupInviteTransaction.java | 12 ---- .../transaction/GroupKickTransaction.java | 12 ---- .../transaction/IssueAssetTransaction.java | 17 ++--- .../transaction/JoinGroupTransaction.java | 13 ---- .../transaction/LeaveGroupTransaction.java | 12 ---- .../qora/transaction/MessageTransaction.java | 25 +++++-- .../transaction/MultiPaymentTransaction.java | 22 ++++-- .../qora/transaction/PaymentTransaction.java | 21 ++++-- .../transaction/ProxyForgingTransaction.java | 13 ++-- .../transaction/RegisterNameTransaction.java | 35 ++++------ .../RemoveGroupAdminTransaction.java | 12 ---- .../qora/transaction/SellNameTransaction.java | 12 ---- .../qora/transaction/SetGroupTransaction.java | 11 --- .../org/qora/transaction/Transaction.java | 35 +++++++++- .../transaction/TransferAssetTransaction.java | 20 ++++-- .../transaction/UpdateAssetTransaction.java | 30 ++++---- .../transaction/UpdateGroupTransaction.java | 35 +++++----- .../transaction/UpdateNameTransaction.java | 36 +++++----- .../transaction/VoteOnPollTransaction.java | 9 --- 44 files changed, 394 insertions(+), 433 deletions(-) diff --git a/src/main/java/org/qora/block/Block.java b/src/main/java/org/qora/block/Block.java index e1933fbf..d2a1cb59 100644 --- a/src/main/java/org/qora/block/Block.java +++ b/src/main/java/org/qora/block/Block.java @@ -32,7 +32,6 @@ import org.qora.repository.BlockRepository; import org.qora.repository.DataException; import org.qora.repository.Repository; import org.qora.transaction.AtTransaction; -import org.qora.transaction.GenesisTransaction; import org.qora.transaction.Transaction; import org.qora.transaction.Transaction.ApprovalStatus; import org.qora.transaction.Transaction.TransactionType; @@ -896,33 +895,55 @@ public class Block { repository.setSavepoint(); for (Transaction transaction : this.getTransactions()) { + TransactionData transactionData = transaction.getTransactionData(); + // GenesisTransactions are not allowed (GenesisBlock overrides isValid() to allow them) - if (transaction instanceof GenesisTransaction) + if (transactionData.getType() == TransactionType.GENESIS || transactionData.getType() == TransactionType.ACCOUNT_FLAGS) return ValidationResult.GENESIS_TRANSACTIONS_INVALID; // Check timestamp and deadline - if (transaction.getTransactionData().getTimestamp() > this.blockData.getTimestamp() + if (transactionData.getTimestamp() > this.blockData.getTimestamp() || transaction.getDeadline() <= this.blockData.getTimestamp()) return ValidationResult.TRANSACTION_TIMESTAMP_INVALID; // Check transaction isn't already included in a block - if (this.repository.getTransactionRepository().isConfirmed(transaction.getTransactionData().getSignature())) + if (this.repository.getTransactionRepository().isConfirmed(transactionData.getSignature())) return ValidationResult.TRANSACTION_ALREADY_PROCESSED; + // Check transaction has correct reference, etc. + if (!transaction.hasValidReference()) { + LOGGER.debug("Error during transaction validation, tx " + Base58.encode(transactionData.getSignature()) + ": INVALID_REFERENCE"); + return ValidationResult.TRANSACTION_INVALID; + } + // Check transaction is even valid // NOTE: in Gen1 there was an extra block height passed to DeployATTransaction.isValid Transaction.ValidationResult validationResult = transaction.isValid(); if (validationResult != Transaction.ValidationResult.OK) { - LOGGER.debug("Error during transaction validation, tx " + Base58.encode(transaction.getTransactionData().getSignature()) + ": " + LOGGER.debug("Error during transaction validation, tx " + Base58.encode(transactionData.getSignature()) + ": " + + validationResult.name()); + return ValidationResult.TRANSACTION_INVALID; + } + + // Check transaction can even be processed + validationResult = transaction.isProcessable(); + if (validationResult != Transaction.ValidationResult.OK) { + LOGGER.debug("Error during transaction validation, tx " + Base58.encode(transactionData.getSignature()) + ": " + validationResult.name()); return ValidationResult.TRANSACTION_INVALID; } // Process transaction to make sure other transactions validate properly try { - transaction.process(); + // Only process transactions that don't require group-approval. + // Group-approval transactions are dealt with later. + if (transactionData.getApprovalStatus() == ApprovalStatus.NOT_REQUIRED) + transaction.process(); + + // Regardless of group-approval, update relevant info for creator (e.g. lastReference) + transaction.processReferencesAndFees(); } catch (Exception e) { - LOGGER.error("Exception during transaction validation, tx " + Base58.encode(transaction.getTransactionData().getSignature()), e); + LOGGER.error("Exception during transaction validation, tx " + Base58.encode(transactionData.getSignature()), e); e.printStackTrace(); return ValidationResult.TRANSACTION_PROCESSING_FAILED; } @@ -1056,7 +1077,7 @@ public class Block { transaction.process(); // Regardless of group-approval, update relevant info for creator (e.g. lastReference) - transaction.processCreatorUpdates(); + transaction.processReferencesAndFees(); } // Group-approval transactions @@ -1133,15 +1154,15 @@ public class Block { continue; } - // Approved, but check transaction is still valid - if (transaction.isValid() != Transaction.ValidationResult.OK) { + // Approved, but check transaction can still be processed + if (transaction.isProcessable() != Transaction.ValidationResult.OK) { transactionData.setApprovalStatus(ApprovalStatus.INVALID); this.repository.getTransactionRepository().save(transactionData); continue; } // APPROVED, in which case do transaction.process(); - transactionData.setApprovalStatus(ApprovalStatus.INVALID); + transactionData.setApprovalStatus(ApprovalStatus.APPROVED); this.repository.getTransactionRepository().save(transactionData); transaction.process(); diff --git a/src/main/java/org/qora/block/BlockGenerator.java b/src/main/java/org/qora/block/BlockGenerator.java index 1df953a8..135cf11d 100644 --- a/src/main/java/org/qora/block/BlockGenerator.java +++ b/src/main/java/org/qora/block/BlockGenerator.java @@ -242,15 +242,15 @@ public class BlockGenerator extends Thread { } } + // Sign to create block's signature, needed by Block.isValid() + newBlock.sign(); + // Attempt to add transactions until block is full, or we run out // If a transaction makes the block invalid then skip it and it'll either expire or be in next block. for (TransactionData transactionData : unconfirmedTransactions) { if (!newBlock.addTransaction(transactionData)) break; - // Sign to create block's signature - newBlock.sign(); - // If newBlock is no longer valid then we can't use transaction ValidationResult validationResult = newBlock.isValid(); if (validationResult != ValidationResult.OK) { diff --git a/src/main/java/org/qora/block/GenesisBlock.java b/src/main/java/org/qora/block/GenesisBlock.java index 246cc88c..cb6ddc41 100644 --- a/src/main/java/org/qora/block/GenesisBlock.java +++ b/src/main/java/org/qora/block/GenesisBlock.java @@ -26,6 +26,7 @@ import org.qora.group.Group; import org.qora.repository.DataException; import org.qora.repository.Repository; import org.qora.transaction.Transaction; +import org.qora.transaction.Transaction.ApprovalStatus; import org.qora.transaction.Transaction.TransactionType; import org.qora.transform.TransformationException; import org.qora.transform.transaction.TransactionTransformer; @@ -300,8 +301,11 @@ public class GenesisBlock extends Block { TransactionData transactionData = transaction.getTransactionData(); Account creator = new PublicKeyAccount(this.repository, transactionData.getCreatorPublicKey()); + // Missing reference? if (transactionData.getReference() == null) transactionData.setReference(creator.getLastReference()); + + // Missing signature? if (transactionData.getSignature() == null) { byte[] digest = Crypto.digest(TransactionTransformer.toBytesForSigning(transactionData)); byte[] signature = Bytes.concat(digest, digest); @@ -309,7 +313,12 @@ public class GenesisBlock extends Block { transactionData.setSignature(signature); } - transaction.process(); + // Missing approval status (not used in V1) + transactionData.setApprovalStatus(ApprovalStatus.NOT_REQUIRED); + + // Ask transaction to update references, etc. + transaction.processReferencesAndFees(); + creator.setLastReference(transactionData.getSignature()); } } catch (TransformationException e) { diff --git a/src/main/java/org/qora/payment/Payment.java b/src/main/java/org/qora/payment/Payment.java index 9a1ff7b1..ea0fd348 100644 --- a/src/main/java/org/qora/payment/Payment.java +++ b/src/main/java/org/qora/payment/Payment.java @@ -33,7 +33,7 @@ public class Payment { // Processing - // Validate multiple payments + /** Are payments valid? */ public ValidationResult isValid(byte[] senderPublicKey, List payments, BigDecimal fee, boolean isZeroAmountValid) throws DataException { AssetRepository assetRepository = this.repository.getAssetRepository(); @@ -100,20 +100,70 @@ public class Payment { return ValidationResult.OK; } + /** Are payments valid? */ public ValidationResult isValid(byte[] senderPublicKey, List payments, BigDecimal fee) throws DataException { return isValid(senderPublicKey, payments, fee, false); } - // Single payment forms + /** Is single payment valid? */ public ValidationResult isValid(byte[] senderPublicKey, PaymentData paymentData, BigDecimal fee, boolean isZeroAmountValid) throws DataException { return isValid(senderPublicKey, Collections.singletonList(paymentData), fee, isZeroAmountValid); } + /** Is single payment valid? */ public ValidationResult isValid(byte[] senderPublicKey, PaymentData paymentData, BigDecimal fee) throws DataException { return isValid(senderPublicKey, paymentData, fee, false); } - public void process(byte[] senderPublicKey, List payments, BigDecimal fee, byte[] signature, boolean alwaysInitializeRecipientReference) + /** Are multiple payments processable? */ + public ValidationResult isProcessable(byte[] senderPublicKey, List payments, BigDecimal fee, boolean isZeroAmountValid) throws DataException { + // Essentially the same as isValid... + return isValid(senderPublicKey, payments, fee, isZeroAmountValid); + } + + /** Are multiple payments processable? */ + public ValidationResult isProcessable(byte[] senderPublicKey, List payments, BigDecimal fee) throws DataException { + return isProcessable(senderPublicKey, payments, fee, false); + } + + /** Is single payment processable? */ + public ValidationResult isProcessable(byte[] senderPublicKey, PaymentData paymentData, BigDecimal fee, boolean isZeroAmountValid) throws DataException { + return isProcessable(senderPublicKey, Collections.singletonList(paymentData), fee, isZeroAmountValid); + } + + /** Is single payment processable? */ + public ValidationResult isProcessable(byte[] senderPublicKey, PaymentData paymentData, BigDecimal fee) throws DataException { + return isProcessable(senderPublicKey, paymentData, fee, false); + } + + /** Multiple payment processing */ + public void process(byte[] senderPublicKey, List payments, BigDecimal fee, byte[] signature) + throws DataException { + Account sender = new PublicKeyAccount(this.repository, senderPublicKey); + + // Process all payments + for (PaymentData paymentData : payments) { + Account recipient = new Account(this.repository, paymentData.getRecipient()); + + long assetId = paymentData.getAssetId(); + BigDecimal amount = paymentData.getAmount(); + + // Update sender's balance due to amount + sender.setConfirmedBalance(assetId, sender.getConfirmedBalance(assetId).subtract(amount)); + + // Update recipient's balance + recipient.setConfirmedBalance(assetId, recipient.getConfirmedBalance(assetId).add(amount)); + } + } + + /** Single payment processing */ + public void process(byte[] senderPublicKey, PaymentData paymentData, BigDecimal fee, byte[] signature) + throws DataException { + process(senderPublicKey, Collections.singletonList(paymentData), fee, signature); + } + + /** Multiple payment reference processing */ + public void processReferencesAndFees(byte[] senderPublicKey, List payments, BigDecimal fee, byte[] signature, boolean alwaysInitializeRecipientReference) throws DataException { Account sender = new PublicKeyAccount(this.repository, senderPublicKey); @@ -128,13 +178,6 @@ public class Payment { Account recipient = new Account(this.repository, paymentData.getRecipient()); long assetId = paymentData.getAssetId(); - BigDecimal amount = paymentData.getAmount(); - - // Update sender's balance due to amount - sender.setConfirmedBalance(assetId, sender.getConfirmedBalance(assetId).subtract(amount)); - - // Update recipient's balance - recipient.setConfirmedBalance(assetId, recipient.getConfirmedBalance(assetId).add(amount)); // For QORA amounts only: if recipient has no reference yet, then this is their starting reference if ((alwaysInitializeRecipientReference || assetId == Asset.QORA) && recipient.getLastReference() == null) @@ -142,9 +185,10 @@ public class Payment { } } - public void process(byte[] senderPublicKey, PaymentData paymentData, BigDecimal fee, byte[] signature, boolean alwaysInitializeRecipientReference) + /** Multiple payment reference processing */ + public void processReferencesAndFees(byte[] senderPublicKey, PaymentData payment, BigDecimal fee, byte[] signature, boolean alwaysInitializeRecipientReference) throws DataException { - process(senderPublicKey, Collections.singletonList(paymentData), fee, signature, alwaysInitializeRecipientReference); + processReferencesAndFees(senderPublicKey, Collections.singletonList(payment), fee, signature, alwaysInitializeRecipientReference); } public void orphan(byte[] senderPublicKey, List payments, BigDecimal fee, byte[] signature, byte[] reference, diff --git a/src/main/java/org/qora/repository/ATRepository.java b/src/main/java/org/qora/repository/ATRepository.java index 2fe00368..60b83fba 100644 --- a/src/main/java/org/qora/repository/ATRepository.java +++ b/src/main/java/org/qora/repository/ATRepository.java @@ -12,6 +12,9 @@ public interface ATRepository { /** Returns ATData using AT's address or null if none found */ public ATData fromATAddress(String atAddress) throws DataException; + /** Returns where AT with passed address exists in repository */ + public boolean exists(String atAddress) throws DataException; + /** Returns list of executable ATs, empty if none found */ public List getAllExecutableATs() throws DataException; diff --git a/src/main/java/org/qora/repository/GroupRepository.java b/src/main/java/org/qora/repository/GroupRepository.java index bc8d57ec..b55e7119 100644 --- a/src/main/java/org/qora/repository/GroupRepository.java +++ b/src/main/java/org/qora/repository/GroupRepository.java @@ -45,6 +45,10 @@ public interface GroupRepository { public void delete(String groupName) throws DataException; + // Group Owner + + public String getOwner(int groupId) throws DataException; + // Group Admins public GroupAdminData getAdmin(int groupId, String address) throws DataException; diff --git a/src/main/java/org/qora/repository/hsqldb/HSQLDBATRepository.java b/src/main/java/org/qora/repository/hsqldb/HSQLDBATRepository.java index 9fab4c69..f62c2fe3 100644 --- a/src/main/java/org/qora/repository/hsqldb/HSQLDBATRepository.java +++ b/src/main/java/org/qora/repository/hsqldb/HSQLDBATRepository.java @@ -57,6 +57,15 @@ public class HSQLDBATRepository implements ATRepository { } } + @Override + public boolean exists(String atAddress) throws DataException { + try { + return this.repository.exists("ATs", "AT_address = ?", atAddress); + } catch (SQLException e) { + throw new DataException("Unable to check for AT in repository", e); + } + } + @Override public List getAllExecutableATs() throws DataException { List executableATs = new ArrayList(); diff --git a/src/main/java/org/qora/repository/hsqldb/HSQLDBGroupRepository.java b/src/main/java/org/qora/repository/hsqldb/HSQLDBGroupRepository.java index 54834b05..1841ea49 100644 --- a/src/main/java/org/qora/repository/hsqldb/HSQLDBGroupRepository.java +++ b/src/main/java/org/qora/repository/hsqldb/HSQLDBGroupRepository.java @@ -289,6 +289,22 @@ public class HSQLDBGroupRepository implements GroupRepository { } } + // Group Owner + + @Override + public String getOwner(int groupId) throws DataException { + try (ResultSet resultSet = this.repository.checkedExecute("SELECT owner FROM Groups WHERE group_id = ?", groupId)) { + if (resultSet == null) + return null; + + String owner = resultSet.getString(1); + + return owner; + } catch (SQLException e) { + throw new DataException("Unable to fetch group owner from repository", e); + } + } + // Group Admins @Override diff --git a/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBTransactionRepository.java b/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBTransactionRepository.java index c27c0a8d..ac744e49 100644 --- a/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBTransactionRepository.java +++ b/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBTransactionRepository.java @@ -657,7 +657,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository { List transactions = new ArrayList(); - try (ResultSet resultSet = this.repository.checkedExecute(sql, ApprovalStatus.PENDING, blockHeight)) { + try (ResultSet resultSet = this.repository.checkedExecute(sql, ApprovalStatus.PENDING.value, blockHeight)) { if (resultSet == null) return transactions; @@ -688,7 +688,7 @@ public class HSQLDBTransactionRepository implements TransactionRepository { List transactions = new ArrayList(); - try (ResultSet resultSet = this.repository.checkedExecute(sql, ApprovalStatus.PENDING, blockHeight)) { + try (ResultSet resultSet = this.repository.checkedExecute(sql, ApprovalStatus.PENDING.value, blockHeight)) { if (resultSet == null) return transactions; @@ -861,7 +861,8 @@ public class HSQLDBTransactionRepository implements TransactionRepository { saver.bind("signature", transactionData.getSignature()).bind("reference", transactionData.getReference()) .bind("type", transactionData.getType().value) .bind("creator", transactionData.getCreatorPublicKey()).bind("creation", new Timestamp(transactionData.getTimestamp())) - .bind("fee", transactionData.getFee()).bind("milestone_block", null).bind("tx_group_id", transactionData.getTxGroupId()); + .bind("fee", transactionData.getFee()).bind("milestone_block", null).bind("tx_group_id", transactionData.getTxGroupId()) + .bind("approval_status", transactionData.getApprovalStatus().value); try { saver.execute(this.repository); diff --git a/src/main/java/org/qora/transaction/AccountFlagsTransaction.java b/src/main/java/org/qora/transaction/AccountFlagsTransaction.java index a49e0c40..34eb233a 100644 --- a/src/main/java/org/qora/transaction/AccountFlagsTransaction.java +++ b/src/main/java/org/qora/transaction/AccountFlagsTransaction.java @@ -1,7 +1,6 @@ package org.qora.transaction; import java.math.BigDecimal; -import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -77,10 +76,6 @@ public class AccountFlagsTransaction extends Transaction { if (accountFlagsTransactionData.getFee().compareTo(BigDecimal.ZERO) < 0) return ValidationResult.NEGATIVE_FEE; - // Check reference - if (!Arrays.equals(creator.getLastReference(), accountFlagsTransactionData.getReference())) - return ValidationResult.INVALID_REFERENCE; - // Check creator has enough funds if (creator.getConfirmedBalance(Asset.QORA).compareTo(accountFlagsTransactionData.getFee()) < 0) return ValidationResult.NO_BALANCE; @@ -107,13 +102,6 @@ public class AccountFlagsTransaction extends Transaction { | accountFlagsTransactionData.getOrMask() ^ accountFlagsTransactionData.getXorMask(); target.setFlags(newFlags); - - // Update creator's balance - Account creator = getCreator(); - creator.setConfirmedBalance(Asset.QORA, creator.getConfirmedBalance(Asset.QORA).subtract(accountFlagsTransactionData.getFee())); - - // Update creator's reference - creator.setLastReference(accountFlagsTransactionData.getSignature()); } @Override diff --git a/src/main/java/org/qora/transaction/AddGroupAdminTransaction.java b/src/main/java/org/qora/transaction/AddGroupAdminTransaction.java index 9e027d63..1c5bddf4 100644 --- a/src/main/java/org/qora/transaction/AddGroupAdminTransaction.java +++ b/src/main/java/org/qora/transaction/AddGroupAdminTransaction.java @@ -1,7 +1,6 @@ package org.qora.transaction; import java.math.BigDecimal; -import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -10,7 +9,6 @@ import org.qora.account.PublicKeyAccount; import org.qora.asset.Asset; import org.qora.crypto.Crypto; import org.qora.data.transaction.AddGroupAdminTransactionData; -import org.qora.data.group.GroupData; import org.qora.data.transaction.TransactionData; import org.qora.group.Group; import org.qora.repository.DataException; @@ -78,16 +76,19 @@ public class AddGroupAdminTransaction extends Transaction { if (!Crypto.isValidAddress(addGroupAdminTransactionData.getMember())) return ValidationResult.INVALID_ADDRESS; - GroupData groupData = this.repository.getGroupRepository().fromGroupId(addGroupAdminTransactionData.getGroupId()); - // Check group exists - if (groupData == null) + if (!this.repository.getGroupRepository().groupExists(addGroupAdminTransactionData.getGroupId())) return ValidationResult.GROUP_DOES_NOT_EXIST; + // Check fee is positive + if (addGroupAdminTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) + return ValidationResult.NEGATIVE_FEE; + Account owner = getOwner(); + String groupOwner = this.repository.getGroupRepository().getOwner(addGroupAdminTransactionData.getGroupId()); // Check transaction's public key matches group's current owner - if (!owner.getAddress().equals(groupData.getOwner())) + if (!owner.getAddress().equals(groupOwner)) return ValidationResult.INVALID_GROUP_OWNER; Account member = getMember(); @@ -100,15 +101,7 @@ public class AddGroupAdminTransaction extends Transaction { if (this.repository.getGroupRepository().adminExists(addGroupAdminTransactionData.getGroupId(), member.getAddress())) return ValidationResult.ALREADY_GROUP_ADMIN; - // Check fee is positive - if (addGroupAdminTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) - return ValidationResult.NEGATIVE_FEE; - - // Check reference - if (!Arrays.equals(owner.getLastReference(), addGroupAdminTransactionData.getReference())) - return ValidationResult.INVALID_REFERENCE; - - // Check creator has enough funds + // Check group owner has enough funds if (owner.getConfirmedBalance(Asset.QORA).compareTo(addGroupAdminTransactionData.getFee()) < 0) return ValidationResult.NO_BALANCE; @@ -122,13 +115,6 @@ public class AddGroupAdminTransaction extends Transaction { group.promoteToAdmin(addGroupAdminTransactionData); // We would save updated transaction at this point, but it hasn't been modified - - // Update owner's balance - Account owner = getOwner(); - owner.setConfirmedBalance(Asset.QORA, owner.getConfirmedBalance(Asset.QORA).subtract(addGroupAdminTransactionData.getFee())); - - // Update owner's reference - owner.setLastReference(addGroupAdminTransactionData.getSignature()); } @Override diff --git a/src/main/java/org/qora/transaction/ArbitraryTransaction.java b/src/main/java/org/qora/transaction/ArbitraryTransaction.java index e203321b..8fda1acc 100644 --- a/src/main/java/org/qora/transaction/ArbitraryTransaction.java +++ b/src/main/java/org/qora/transaction/ArbitraryTransaction.java @@ -2,7 +2,6 @@ package org.qora.transaction; import java.math.BigDecimal; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import org.qora.account.Account; @@ -103,22 +102,31 @@ public class ArbitraryTransaction extends Transaction { if (arbitraryTransactionData.getData().length < 1 || arbitraryTransactionData.getData().length > MAX_DATA_SIZE) return ValidationResult.INVALID_DATA_LENGTH; - // Check reference is correct - Account sender = getSender(); - if (!Arrays.equals(sender.getLastReference(), arbitraryTransactionData.getReference())) - return ValidationResult.INVALID_REFERENCE; - - // Wrap and delegate final payment checks to Payment class + // Wrap and delegate final payment validity checks to Payment class return new Payment(this.repository).isValid(arbitraryTransactionData.getSenderPublicKey(), arbitraryTransactionData.getPayments(), arbitraryTransactionData.getFee()); } + @Override + public ValidationResult isProcessable() throws DataException { + // Wrap and delegate final payment processable checks to Payment class + return new Payment(this.repository).isProcessable(arbitraryTransactionData.getSenderPublicKey(), arbitraryTransactionData.getPayments(), + arbitraryTransactionData.getFee()); + } + @Override public void process() throws DataException { // We would save updated transaction at this point, but it hasn't been modified - // Wrap and delegate payment processing to Payment class. Always update recipients' last references regardless of asset. + // Wrap and delegate payment processing to Payment class. new Payment(this.repository).process(arbitraryTransactionData.getSenderPublicKey(), arbitraryTransactionData.getPayments(), + arbitraryTransactionData.getFee(), arbitraryTransactionData.getSignature()); + } + + @Override + public void processReferencesAndFees() throws DataException { + // Wrap and delegate reference and fee processing to Payment class. Always update recipients' last references regardless of asset. + new Payment(this.repository).processReferencesAndFees(arbitraryTransactionData.getSenderPublicKey(), arbitraryTransactionData.getPayments(), arbitraryTransactionData.getFee(), arbitraryTransactionData.getSignature(), true); } diff --git a/src/main/java/org/qora/transaction/AtTransaction.java b/src/main/java/org/qora/transaction/AtTransaction.java index 58f3fc65..fdf872f2 100644 --- a/src/main/java/org/qora/transaction/AtTransaction.java +++ b/src/main/java/org/qora/transaction/AtTransaction.java @@ -108,12 +108,14 @@ public class AtTransaction extends Transaction { // Processing @Override - public ValidationResult isValid() throws DataException { + public boolean hasValidReference() throws DataException { // Check reference is correct Account atAccount = getATAccount(); - if (!Arrays.equals(atAccount.getLastReference(), atTransactionData.getReference())) - return ValidationResult.INVALID_REFERENCE; + return Arrays.equals(atAccount.getLastReference(), atTransactionData.getReference()); + } + @Override + public ValidationResult isValid() throws DataException { if (this.atTransactionData.getMessage().length > MAX_DATA_SIZE) return ValidationResult.INVALID_DATA_LENGTH; @@ -173,6 +175,14 @@ public class AtTransaction extends Transaction { // Update recipient's balance recipient.setConfirmedBalance(assetId, recipient.getConfirmedBalance(assetId).add(amount)); + } + } + + @Override + public void processReferencesAndFees() throws DataException { + if (this.atTransactionData.getAmount() != null) { + Account recipient = getRecipient(); + long assetId = this.atTransactionData.getAssetId(); // For QORA amounts only: if recipient has no reference yet, then this is their starting reference if (assetId == Asset.QORA && recipient.getLastReference() == null) diff --git a/src/main/java/org/qora/transaction/BuyNameTransaction.java b/src/main/java/org/qora/transaction/BuyNameTransaction.java index 9a2fbb4c..69b5693b 100644 --- a/src/main/java/org/qora/transaction/BuyNameTransaction.java +++ b/src/main/java/org/qora/transaction/BuyNameTransaction.java @@ -1,7 +1,6 @@ package org.qora.transaction; import java.math.BigDecimal; -import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -110,10 +109,6 @@ public class BuyNameTransaction extends Transaction { if (buyNameTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) return ValidationResult.NEGATIVE_FEE; - // Check reference is correct - if (!Arrays.equals(buyer.getLastReference(), buyNameTransactionData.getReference())) - return ValidationResult.INVALID_REFERENCE; - // Check issuer has enough funds if (buyer.getConfirmedBalance(Asset.QORA).compareTo(buyNameTransactionData.getFee()) < 0) return ValidationResult.NO_BALANCE; @@ -129,13 +124,6 @@ public class BuyNameTransaction extends Transaction { // Save transaction with updated "name reference" pointing to previous transaction that updated name this.repository.getTransactionRepository().save(buyNameTransactionData); - - // Update buyer's balance - Account buyer = getBuyer(); - buyer.setConfirmedBalance(Asset.QORA, buyer.getConfirmedBalance(Asset.QORA).subtract(buyNameTransactionData.getFee())); - - // Update buyer's reference - buyer.setLastReference(buyNameTransactionData.getSignature()); } @Override diff --git a/src/main/java/org/qora/transaction/CancelAssetOrderTransaction.java b/src/main/java/org/qora/transaction/CancelAssetOrderTransaction.java index 0e5c4337..91954f5e 100644 --- a/src/main/java/org/qora/transaction/CancelAssetOrderTransaction.java +++ b/src/main/java/org/qora/transaction/CancelAssetOrderTransaction.java @@ -2,7 +2,6 @@ package org.qora.transaction; import java.math.BigDecimal; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import org.qora.account.Account; @@ -90,25 +89,13 @@ public class CancelAssetOrderTransaction extends Transaction { if (creator.getConfirmedBalance(Asset.QORA).compareTo(cancelOrderTransactionData.getFee()) < 0) return ValidationResult.NO_BALANCE; - // Check reference is correct - if (!Arrays.equals(creator.getLastReference(), cancelOrderTransactionData.getReference())) - return ValidationResult.INVALID_REFERENCE; - return ValidationResult.OK; } @Override public void process() throws DataException { - Account creator = getCreator(); - // We would save updated transaction at this point, but it hasn't been modified - // Update creator's balance regarding fee - creator.setConfirmedBalance(Asset.QORA, creator.getConfirmedBalance(Asset.QORA).subtract(cancelOrderTransactionData.getFee())); - - // Update creator's last reference - creator.setLastReference(cancelOrderTransactionData.getSignature()); - // Mark Order as completed so no more trades can happen OrderData orderData = this.repository.getAssetRepository().fromOrderId(cancelOrderTransactionData.getOrderId()); Order order = new Order(this.repository, orderData); diff --git a/src/main/java/org/qora/transaction/CancelGroupBanTransaction.java b/src/main/java/org/qora/transaction/CancelGroupBanTransaction.java index 9121999d..a8d0470c 100644 --- a/src/main/java/org/qora/transaction/CancelGroupBanTransaction.java +++ b/src/main/java/org/qora/transaction/CancelGroupBanTransaction.java @@ -1,7 +1,6 @@ package org.qora.transaction; import java.math.BigDecimal; -import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -100,10 +99,6 @@ public class CancelGroupBanTransaction extends Transaction { if (groupUnbanTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) return ValidationResult.NEGATIVE_FEE; - // Check reference - if (!Arrays.equals(admin.getLastReference(), groupUnbanTransactionData.getReference())) - return ValidationResult.INVALID_REFERENCE; - // Check creator has enough funds if (admin.getConfirmedBalance(Asset.QORA).compareTo(groupUnbanTransactionData.getFee()) < 0) return ValidationResult.NO_BALANCE; @@ -119,13 +114,6 @@ public class CancelGroupBanTransaction extends Transaction { // Save this transaction with updated member/admin references to transactions that can help restore state this.repository.getTransactionRepository().save(groupUnbanTransactionData); - - // Update admin's balance - Account admin = getAdmin(); - admin.setConfirmedBalance(Asset.QORA, admin.getConfirmedBalance(Asset.QORA).subtract(groupUnbanTransactionData.getFee())); - - // Update admin's reference - admin.setLastReference(groupUnbanTransactionData.getSignature()); } @Override diff --git a/src/main/java/org/qora/transaction/CancelGroupInviteTransaction.java b/src/main/java/org/qora/transaction/CancelGroupInviteTransaction.java index f7c876f0..7c7d728c 100644 --- a/src/main/java/org/qora/transaction/CancelGroupInviteTransaction.java +++ b/src/main/java/org/qora/transaction/CancelGroupInviteTransaction.java @@ -1,7 +1,6 @@ package org.qora.transaction; import java.math.BigDecimal; -import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -100,10 +99,6 @@ public class CancelGroupInviteTransaction extends Transaction { if (cancelGroupInviteTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) return ValidationResult.NEGATIVE_FEE; - // Check reference - if (!Arrays.equals(admin.getLastReference(), cancelGroupInviteTransactionData.getReference())) - return ValidationResult.INVALID_REFERENCE; - // Check creator has enough funds if (admin.getConfirmedBalance(Asset.QORA).compareTo(cancelGroupInviteTransactionData.getFee()) < 0) return ValidationResult.NO_BALANCE; @@ -119,13 +114,6 @@ public class CancelGroupInviteTransaction extends Transaction { // Save this transaction with updated member/admin references to transactions that can help restore state this.repository.getTransactionRepository().save(cancelGroupInviteTransactionData); - - // Update admin's balance - Account admin = getAdmin(); - admin.setConfirmedBalance(Asset.QORA, admin.getConfirmedBalance(Asset.QORA).subtract(cancelGroupInviteTransactionData.getFee())); - - // Update admin's reference - admin.setLastReference(cancelGroupInviteTransactionData.getSignature()); } @Override diff --git a/src/main/java/org/qora/transaction/CancelSellNameTransaction.java b/src/main/java/org/qora/transaction/CancelSellNameTransaction.java index 1331982b..7bbfd7ab 100644 --- a/src/main/java/org/qora/transaction/CancelSellNameTransaction.java +++ b/src/main/java/org/qora/transaction/CancelSellNameTransaction.java @@ -2,7 +2,6 @@ package org.qora.transaction; import java.math.BigDecimal; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import org.qora.account.Account; @@ -96,10 +95,6 @@ public class CancelSellNameTransaction extends Transaction { if (cancelSellNameTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) return ValidationResult.NEGATIVE_FEE; - // Check reference is correct - if (!Arrays.equals(owner.getLastReference(), cancelSellNameTransactionData.getReference())) - return ValidationResult.INVALID_REFERENCE; - // Check issuer has enough funds if (owner.getConfirmedBalance(Asset.QORA).compareTo(cancelSellNameTransactionData.getFee()) < 0) return ValidationResult.NO_BALANCE; @@ -116,13 +111,6 @@ public class CancelSellNameTransaction extends Transaction { // Save this transaction, with updated "name reference" to previous transaction that updated name this.repository.getTransactionRepository().save(cancelSellNameTransactionData); - - // Update owner's balance - Account owner = getOwner(); - owner.setConfirmedBalance(Asset.QORA, owner.getConfirmedBalance(Asset.QORA).subtract(cancelSellNameTransactionData.getFee())); - - // Update owner's reference - owner.setLastReference(cancelSellNameTransactionData.getSignature()); } @Override diff --git a/src/main/java/org/qora/transaction/CreateGroupTransaction.java b/src/main/java/org/qora/transaction/CreateGroupTransaction.java index 669d557b..b0584506 100644 --- a/src/main/java/org/qora/transaction/CreateGroupTransaction.java +++ b/src/main/java/org/qora/transaction/CreateGroupTransaction.java @@ -1,7 +1,6 @@ package org.qora.transaction; import java.math.BigDecimal; -import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -92,20 +91,12 @@ public class CreateGroupTransaction extends Transaction { if (!createGroupTransactionData.getGroupName().equals(createGroupTransactionData.getGroupName().toLowerCase())) return ValidationResult.NAME_NOT_LOWER_CASE; - // Check the group name isn't already taken - if (this.repository.getGroupRepository().groupExists(createGroupTransactionData.getGroupName())) - return ValidationResult.GROUP_ALREADY_EXISTS; - // Check fee is positive if (createGroupTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) return ValidationResult.NEGATIVE_FEE; Account creator = getCreator(); - // Check reference is correct - if (!Arrays.equals(creator.getLastReference(), createGroupTransactionData.getReference())) - return ValidationResult.INVALID_REFERENCE; - // Check creator has enough funds if (creator.getConfirmedBalance(Asset.QORA).compareTo(createGroupTransactionData.getFee()) < 0) return ValidationResult.NO_BALANCE; @@ -113,6 +104,15 @@ public class CreateGroupTransaction extends Transaction { return ValidationResult.OK; } + @Override + public ValidationResult isProcessable() throws DataException { + // Check the group name isn't already taken + if (this.repository.getGroupRepository().groupExists(createGroupTransactionData.getGroupName())) + return ValidationResult.GROUP_ALREADY_EXISTS; + + return ValidationResult.OK; + } + @Override public void process() throws DataException { // Create Group @@ -124,13 +124,6 @@ public class CreateGroupTransaction extends Transaction { // Save this transaction this.repository.getTransactionRepository().save(createGroupTransactionData); - - // Update creator's balance - Account creator = getCreator(); - creator.setConfirmedBalance(Asset.QORA, creator.getConfirmedBalance(Asset.QORA).subtract(createGroupTransactionData.getFee())); - - // Update creator's reference - creator.setLastReference(createGroupTransactionData.getSignature()); } @Override diff --git a/src/main/java/org/qora/transaction/CreatePollTransaction.java b/src/main/java/org/qora/transaction/CreatePollTransaction.java index f0bbd6ec..b2718441 100644 --- a/src/main/java/org/qora/transaction/CreatePollTransaction.java +++ b/src/main/java/org/qora/transaction/CreatePollTransaction.java @@ -2,7 +2,6 @@ package org.qora.transaction; import java.math.BigDecimal; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -102,10 +101,6 @@ public class CreatePollTransaction extends Transaction { 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; - // In gen1 we tested for presence of existing votes but how could there be any if poll doesn't exist? // Check number of options @@ -137,9 +132,6 @@ public class CreatePollTransaction extends Transaction { // Check reference is correct Account creator = getCreator(); - if (!Arrays.equals(creator.getLastReference(), createPollTransactionData.getReference())) - return ValidationResult.INVALID_REFERENCE; - // Check issuer has enough funds if (creator.getConfirmedBalance(Asset.QORA).compareTo(createPollTransactionData.getFee()) < 0) return ValidationResult.NO_BALANCE; @@ -147,6 +139,15 @@ public class CreatePollTransaction extends Transaction { return ValidationResult.OK; } + @Override + public ValidationResult isProcessable() throws DataException { + // Check the poll name isn't already taken + if (this.repository.getVotingRepository().pollExists(createPollTransactionData.getPollName())) + return ValidationResult.POLL_ALREADY_EXISTS; + + return ValidationResult.OK; + } + @Override public void process() throws DataException { // Publish poll to allow voting @@ -154,13 +155,6 @@ public class CreatePollTransaction extends Transaction { poll.publish(); // We would save updated transaction at this point, but it hasn't been modified - - // Update creator's balance - Account creator = getCreator(); - creator.setConfirmedBalance(Asset.QORA, creator.getConfirmedBalance(Asset.QORA).subtract(createPollTransactionData.getFee())); - - // Update creator's reference - creator.setLastReference(createPollTransactionData.getSignature()); } @Override diff --git a/src/main/java/org/qora/transaction/DeployAtTransaction.java b/src/main/java/org/qora/transaction/DeployAtTransaction.java index 61bc6531..331a4b7e 100644 --- a/src/main/java/org/qora/transaction/DeployAtTransaction.java +++ b/src/main/java/org/qora/transaction/DeployAtTransaction.java @@ -5,7 +5,6 @@ import java.math.BigDecimal; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import org.ciyam.at.MachineState; @@ -171,12 +170,8 @@ public class DeployAtTransaction extends Transaction { if (deployATTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) return ValidationResult.NEGATIVE_FEE; - // Check reference is correct Account creator = getCreator(); - if (!Arrays.equals(creator.getLastReference(), deployATTransactionData.getReference())) - return ValidationResult.INVALID_REFERENCE; - // Check creator has enough funds if (assetId == Asset.QORA) { // Simple case: amount and fee both in Qora @@ -208,6 +203,33 @@ public class DeployAtTransaction extends Transaction { return ValidationResult.OK; } + @Override + public ValidationResult isProcessable() throws DataException { + Account creator = getCreator(); + long assetId = deployATTransactionData.getAssetId(); + + // Check creator has enough funds + if (assetId == Asset.QORA) { + // Simple case: amount and fee both in Qora + BigDecimal minimumBalance = deployATTransactionData.getFee().add(deployATTransactionData.getAmount()); + + if (creator.getConfirmedBalance(Asset.QORA).compareTo(minimumBalance) < 0) + return ValidationResult.NO_BALANCE; + } else { + if (creator.getConfirmedBalance(Asset.QORA).compareTo(deployATTransactionData.getFee()) < 0) + return ValidationResult.NO_BALANCE; + + if (creator.getConfirmedBalance(assetId).compareTo(deployATTransactionData.getAmount()) < 0) + return ValidationResult.NO_BALANCE; + } + + // Check AT doesn't already exist + if (this.repository.getATRepository().exists(deployATTransactionData.getAtAddress())) + return ValidationResult.AT_ALREADY_EXISTS; + + return ValidationResult.OK; + } + @Override public void process() throws DataException { ensureATAddress(); @@ -220,13 +242,9 @@ public class DeployAtTransaction extends Transaction { long assetId = deployATTransactionData.getAssetId(); - // Update creator's balance + // Update creator's balance regarding initial payment to AT Account creator = getCreator(); creator.setConfirmedBalance(assetId, creator.getConfirmedBalance(assetId).subtract(deployATTransactionData.getAmount())); - creator.setConfirmedBalance(Asset.QORA, creator.getConfirmedBalance(Asset.QORA).subtract(deployATTransactionData.getFee())); - - // Update creator's reference - creator.setLastReference(deployATTransactionData.getSignature()); // Update AT's reference, which also creates AT account Account atAccount = this.getATAccount(); diff --git a/src/main/java/org/qora/transaction/EnableForgingTransaction.java b/src/main/java/org/qora/transaction/EnableForgingTransaction.java index 22eb1944..d9190608 100644 --- a/src/main/java/org/qora/transaction/EnableForgingTransaction.java +++ b/src/main/java/org/qora/transaction/EnableForgingTransaction.java @@ -1,7 +1,6 @@ package org.qora.transaction; import java.math.BigDecimal; -import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -121,10 +120,6 @@ public class EnableForgingTransaction extends Transaction { if (enableForgingTransactionData.getFee().compareTo(BigDecimal.ZERO) < 0) return ValidationResult.NEGATIVE_FEE; - // Check reference - if (!Arrays.equals(creator.getLastReference(), enableForgingTransactionData.getReference())) - return ValidationResult.INVALID_REFERENCE; - // Check creator has enough funds if (creator.getConfirmedBalance(Asset.QORA).compareTo(enableForgingTransactionData.getFee()) < 0) return ValidationResult.NO_BALANCE; @@ -153,12 +148,6 @@ public class EnableForgingTransaction extends Transaction { target.setForgingEnabler(creator.getAddress()); // We would save updated transaction at this point, but it hasn't been modified - - // Update creator's balance - creator.setConfirmedBalance(Asset.QORA, creator.getConfirmedBalance(Asset.QORA).subtract(enableForgingTransactionData.getFee())); - - // Update creator's reference - creator.setLastReference(enableForgingTransactionData.getSignature()); } @Override diff --git a/src/main/java/org/qora/transaction/GenesisTransaction.java b/src/main/java/org/qora/transaction/GenesisTransaction.java index 2c9048a3..f6a755f3 100644 --- a/src/main/java/org/qora/transaction/GenesisTransaction.java +++ b/src/main/java/org/qora/transaction/GenesisTransaction.java @@ -140,13 +140,20 @@ public class GenesisTransaction extends Transaction { Account recipient = new Account(repository, genesisTransactionData.getRecipient()); - // Set recipient's starting reference (also creates account) - recipient.setLastReference(genesisTransactionData.getSignature()); - // Update recipient's balance recipient.setConfirmedBalance(Asset.QORA, genesisTransactionData.getAmount()); } + @Override + public void processReferencesAndFees() throws DataException { + // Do not attempt to update non-existent genesis account's reference! + + Account recipient = new Account(repository, genesisTransactionData.getRecipient()); + + // Set recipient's starting reference (also creates account) + recipient.setLastReference(genesisTransactionData.getSignature()); + } + @Override public void orphan() throws DataException { // We would save updated transaction at this point, but it hasn't been modified diff --git a/src/main/java/org/qora/transaction/GroupApprovalTransaction.java b/src/main/java/org/qora/transaction/GroupApprovalTransaction.java index 2cc78c5e..848fd0cc 100644 --- a/src/main/java/org/qora/transaction/GroupApprovalTransaction.java +++ b/src/main/java/org/qora/transaction/GroupApprovalTransaction.java @@ -1,7 +1,6 @@ package org.qora.transaction; import java.math.BigDecimal; -import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -83,10 +82,6 @@ public class GroupApprovalTransaction extends Transaction { if (groupApprovalTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) return ValidationResult.NEGATIVE_FEE; - // Check reference - if (!Arrays.equals(admin.getLastReference(), groupApprovalTransactionData.getReference())) - return ValidationResult.INVALID_REFERENCE; - // Check creator has enough funds if (admin.getConfirmedBalance(Asset.QORA).compareTo(groupApprovalTransactionData.getFee()) < 0) return ValidationResult.NO_BALANCE; @@ -104,13 +99,6 @@ public class GroupApprovalTransaction extends Transaction { // Save this transaction with updated prior reference to transaction that can help restore state this.repository.getTransactionRepository().save(groupApprovalTransactionData); - - // Update admin's balance - Account admin = getAdmin(); - admin.setConfirmedBalance(Asset.QORA, admin.getConfirmedBalance(Asset.QORA).subtract(groupApprovalTransactionData.getFee())); - - // Update admin's reference - admin.setLastReference(groupApprovalTransactionData.getSignature()); } @Override diff --git a/src/main/java/org/qora/transaction/GroupBanTransaction.java b/src/main/java/org/qora/transaction/GroupBanTransaction.java index 9231d5cf..01e07619 100644 --- a/src/main/java/org/qora/transaction/GroupBanTransaction.java +++ b/src/main/java/org/qora/transaction/GroupBanTransaction.java @@ -1,7 +1,6 @@ package org.qora.transaction; import java.math.BigDecimal; -import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -101,10 +100,6 @@ public class GroupBanTransaction extends Transaction { if (groupBanTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) return ValidationResult.NEGATIVE_FEE; - // Check reference - if (!Arrays.equals(admin.getLastReference(), groupBanTransactionData.getReference())) - return ValidationResult.INVALID_REFERENCE; - // Check admin has enough funds if (admin.getConfirmedBalance(Asset.QORA).compareTo(groupBanTransactionData.getFee()) < 0) return ValidationResult.NO_BALANCE; @@ -120,13 +115,6 @@ public class GroupBanTransaction extends Transaction { // Save this transaction with updated member/admin references to transactions that can help restore state this.repository.getTransactionRepository().save(groupBanTransactionData); - - // Update admin's balance - Account admin = getAdmin(); - admin.setConfirmedBalance(Asset.QORA, admin.getConfirmedBalance(Asset.QORA).subtract(groupBanTransactionData.getFee())); - - // Update admin's reference - admin.setLastReference(groupBanTransactionData.getSignature()); } @Override diff --git a/src/main/java/org/qora/transaction/GroupInviteTransaction.java b/src/main/java/org/qora/transaction/GroupInviteTransaction.java index fb39f809..a77a2c15 100644 --- a/src/main/java/org/qora/transaction/GroupInviteTransaction.java +++ b/src/main/java/org/qora/transaction/GroupInviteTransaction.java @@ -1,7 +1,6 @@ package org.qora.transaction; import java.math.BigDecimal; -import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -107,10 +106,6 @@ public class GroupInviteTransaction extends Transaction { if (groupInviteTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) return ValidationResult.NEGATIVE_FEE; - // Check reference - if (!Arrays.equals(admin.getLastReference(), groupInviteTransactionData.getReference())) - return ValidationResult.INVALID_REFERENCE; - // Check creator has enough funds if (admin.getConfirmedBalance(Asset.QORA).compareTo(groupInviteTransactionData.getFee()) < 0) return ValidationResult.NO_BALANCE; @@ -126,13 +121,6 @@ public class GroupInviteTransaction extends Transaction { // Save this transaction with updated member/admin references to transactions that can help restore state this.repository.getTransactionRepository().save(groupInviteTransactionData); - - // Update admin's balance - Account admin = getAdmin(); - admin.setConfirmedBalance(Asset.QORA, admin.getConfirmedBalance(Asset.QORA).subtract(groupInviteTransactionData.getFee())); - - // Update admin's reference - admin.setLastReference(groupInviteTransactionData.getSignature()); } @Override diff --git a/src/main/java/org/qora/transaction/GroupKickTransaction.java b/src/main/java/org/qora/transaction/GroupKickTransaction.java index 2c8c2b14..96b36bf7 100644 --- a/src/main/java/org/qora/transaction/GroupKickTransaction.java +++ b/src/main/java/org/qora/transaction/GroupKickTransaction.java @@ -1,7 +1,6 @@ package org.qora.transaction; import java.math.BigDecimal; -import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -107,10 +106,6 @@ public class GroupKickTransaction extends Transaction { if (groupKickTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) return ValidationResult.NEGATIVE_FEE; - // Check reference - if (!Arrays.equals(admin.getLastReference(), groupKickTransactionData.getReference())) - return ValidationResult.INVALID_REFERENCE; - // Check creator has enough funds if (admin.getConfirmedBalance(Asset.QORA).compareTo(groupKickTransactionData.getFee()) < 0) return ValidationResult.NO_BALANCE; @@ -126,13 +121,6 @@ public class GroupKickTransaction extends Transaction { // Save this transaction with updated member/admin references to transactions that can help restore state this.repository.getTransactionRepository().save(groupKickTransactionData); - - // Update admin's balance - Account admin = getAdmin(); - admin.setConfirmedBalance(Asset.QORA, admin.getConfirmedBalance(Asset.QORA).subtract(groupKickTransactionData.getFee())); - - // Update admin's reference - admin.setLastReference(groupKickTransactionData.getSignature()); } @Override diff --git a/src/main/java/org/qora/transaction/IssueAssetTransaction.java b/src/main/java/org/qora/transaction/IssueAssetTransaction.java index 37ef0139..eb3ed87c 100644 --- a/src/main/java/org/qora/transaction/IssueAssetTransaction.java +++ b/src/main/java/org/qora/transaction/IssueAssetTransaction.java @@ -1,7 +1,6 @@ package org.qora.transaction; import java.math.BigDecimal; -import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -118,16 +117,17 @@ public class IssueAssetTransaction extends Transaction { if (issueAssetTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) return ValidationResult.NEGATIVE_FEE; - // Check reference is correct Account issuer = getIssuer(); - if (!Arrays.equals(issuer.getLastReference(), issueAssetTransactionData.getReference())) - return ValidationResult.INVALID_REFERENCE; - // Check issuer has enough funds if (issuer.getConfirmedBalance(Asset.QORA).compareTo(issueAssetTransactionData.getFee()) < 0) return ValidationResult.NO_BALANCE; + return ValidationResult.OK; + } + + @Override + public ValidationResult isProcessable() throws DataException { // Check the asset name isn't already taken. This check is not present in gen1. if (issueAssetTransactionData.getTimestamp() >= BlockChain.getInstance().getQoraV2Timestamp()) if (this.repository.getAssetRepository().assetExists(issueAssetTransactionData.getAssetName())) @@ -148,13 +148,6 @@ public class IssueAssetTransaction extends Transaction { // Save this transaction, now with corresponding assetId this.repository.getTransactionRepository().save(issueAssetTransactionData); - // Update issuer's balance - Account issuer = getIssuer(); - issuer.setConfirmedBalance(Asset.QORA, issuer.getConfirmedBalance(Asset.QORA).subtract(issueAssetTransactionData.getFee())); - - // Update issuer's reference - issuer.setLastReference(issueAssetTransactionData.getSignature()); - // Add asset to owner Account owner = getOwner(); owner.setConfirmedBalance(issueAssetTransactionData.getAssetId(), BigDecimal.valueOf(issueAssetTransactionData.getQuantity()).setScale(8)); diff --git a/src/main/java/org/qora/transaction/JoinGroupTransaction.java b/src/main/java/org/qora/transaction/JoinGroupTransaction.java index c63f17d1..240d7672 100644 --- a/src/main/java/org/qora/transaction/JoinGroupTransaction.java +++ b/src/main/java/org/qora/transaction/JoinGroupTransaction.java @@ -1,7 +1,6 @@ package org.qora.transaction; import java.math.BigDecimal; -import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -87,10 +86,6 @@ public class JoinGroupTransaction extends Transaction { // Check fee is positive if (joinGroupTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) return ValidationResult.NEGATIVE_FEE; - - if (!Arrays.equals(joiner.getLastReference(), joinGroupTransactionData.getReference())) - return ValidationResult.INVALID_REFERENCE; - // Check creator has enough funds if (joiner.getConfirmedBalance(Asset.QORA).compareTo(joinGroupTransactionData.getFee()) < 0) return ValidationResult.NO_BALANCE; @@ -106,14 +101,6 @@ public class JoinGroupTransaction extends Transaction { // Save this transaction with cached references to transactions that can help restore state this.repository.getTransactionRepository().save(joinGroupTransactionData); - - // Update joiner's balance - Account joiner = getJoiner(); - joiner.setConfirmedBalance(Asset.QORA, joiner.getConfirmedBalance(Asset.QORA).subtract(joinGroupTransactionData.getFee())); - - // Update joiner's reference - joiner.setLastReference(joinGroupTransactionData.getSignature()); - } @Override diff --git a/src/main/java/org/qora/transaction/LeaveGroupTransaction.java b/src/main/java/org/qora/transaction/LeaveGroupTransaction.java index d708dad3..fbc1dad4 100644 --- a/src/main/java/org/qora/transaction/LeaveGroupTransaction.java +++ b/src/main/java/org/qora/transaction/LeaveGroupTransaction.java @@ -1,7 +1,6 @@ package org.qora.transaction; import java.math.BigDecimal; -import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -86,10 +85,6 @@ public class LeaveGroupTransaction extends Transaction { if (leaveGroupTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) return ValidationResult.NEGATIVE_FEE; - // Check reference - if (!Arrays.equals(leaver.getLastReference(), leaveGroupTransactionData.getReference())) - return ValidationResult.INVALID_REFERENCE; - // Check creator has enough funds if (leaver.getConfirmedBalance(Asset.QORA).compareTo(leaveGroupTransactionData.getFee()) < 0) return ValidationResult.NO_BALANCE; @@ -105,13 +100,6 @@ public class LeaveGroupTransaction extends Transaction { // Save this transaction with updated member/admin references to transactions that can help restore state this.repository.getTransactionRepository().save(leaveGroupTransactionData); - - // Update leaver's balance - Account leaver = getLeaver(); - leaver.setConfirmedBalance(Asset.QORA, leaver.getConfirmedBalance(Asset.QORA).subtract(leaveGroupTransactionData.getFee())); - - // Update leaver's reference - leaver.setLastReference(leaveGroupTransactionData.getSignature()); } @Override diff --git a/src/main/java/org/qora/transaction/MessageTransaction.java b/src/main/java/org/qora/transaction/MessageTransaction.java index 0c919ef1..24dab77e 100644 --- a/src/main/java/org/qora/transaction/MessageTransaction.java +++ b/src/main/java/org/qora/transaction/MessageTransaction.java @@ -1,7 +1,6 @@ package org.qora.transaction; import java.math.BigDecimal; -import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -101,11 +100,6 @@ public class MessageTransaction extends Transaction { if (messageTransactionData.getData().length < 1 || messageTransactionData.getData().length > MAX_DATA_SIZE) return ValidationResult.INVALID_DATA_LENGTH; - // Check reference is correct - Account sender = getSender(); - if (!Arrays.equals(sender.getLastReference(), messageTransactionData.getReference())) - return ValidationResult.INVALID_REFERENCE; - // Zero-amount payments (i.e. message-only) only valid for versions later than 1 boolean isZeroAmountValid = messageTransactionData.getVersion() > 1; @@ -114,12 +108,29 @@ public class MessageTransaction extends Transaction { isZeroAmountValid); } + @Override + public ValidationResult isProcessable() throws DataException { + // Zero-amount payments (i.e. message-only) only valid for versions later than 1 + boolean isZeroAmountValid = messageTransactionData.getVersion() > 1; + + // Wrap and delegate final processable checks to Payment class + return new Payment(this.repository).isProcessable(messageTransactionData.getSenderPublicKey(), getPaymentData(), messageTransactionData.getFee(), + isZeroAmountValid); + } + @Override public void process() throws DataException { // We would save updated transaction at this point, but it hasn't been modified - // Wrap and delegate payment processing to Payment class. Only update recipient's last reference if transferring QORA. + // Wrap and delegate payment processing to Payment class. new Payment(this.repository).process(messageTransactionData.getSenderPublicKey(), getPaymentData(), messageTransactionData.getFee(), + messageTransactionData.getSignature()); + } + + @Override + public void processReferencesAndFees() throws DataException { + // Wrap and delegate references processing to Payment class. Only update recipient's last reference if transferring QORA. + new Payment(this.repository).processReferencesAndFees(messageTransactionData.getSenderPublicKey(), getPaymentData(), messageTransactionData.getFee(), messageTransactionData.getSignature(), false); } diff --git a/src/main/java/org/qora/transaction/MultiPaymentTransaction.java b/src/main/java/org/qora/transaction/MultiPaymentTransaction.java index aa858d8d..68b5ad0e 100644 --- a/src/main/java/org/qora/transaction/MultiPaymentTransaction.java +++ b/src/main/java/org/qora/transaction/MultiPaymentTransaction.java @@ -2,7 +2,6 @@ package org.qora.transaction; import java.math.BigDecimal; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import org.qora.account.Account; @@ -102,9 +101,6 @@ public class MultiPaymentTransaction extends Transaction { // Check reference is correct Account sender = getSender(); - if (!Arrays.equals(sender.getLastReference(), multiPaymentTransactionData.getReference())) - return ValidationResult.INVALID_REFERENCE; - // Check sender has enough funds for fee // NOTE: in Gen1 pre-POWFIX-RELEASE transactions didn't have this check if (multiPaymentTransactionData.getTimestamp() >= BlockChain.getInstance().getPowFixReleaseTimestamp() @@ -114,12 +110,28 @@ public class MultiPaymentTransaction extends Transaction { return new Payment(this.repository).isValid(multiPaymentTransactionData.getSenderPublicKey(), payments, multiPaymentTransactionData.getFee()); } + @Override + public ValidationResult isProcessable() throws DataException { + List payments = multiPaymentTransactionData.getPayments(); + + return new Payment(this.repository).isProcessable(multiPaymentTransactionData.getSenderPublicKey(), payments, multiPaymentTransactionData.getFee()); + } + @Override public void process() throws DataException { // We would save updated transaction at this point, but it hasn't been modified - // Wrap and delegate payment processing to Payment class. Always update recipients' last references regardless of asset. + // Wrap and delegate payment processing to Payment class. new Payment(this.repository).process(multiPaymentTransactionData.getSenderPublicKey(), multiPaymentTransactionData.getPayments(), + multiPaymentTransactionData.getFee(), multiPaymentTransactionData.getSignature()); + } + + @Override + public void processReferencesAndFees() throws DataException { + // We would save updated transaction at this point, but it hasn't been modified + + // Wrap and delegate reference processing to Payment class. Always update recipients' last references regardless of asset. + new Payment(this.repository).processReferencesAndFees(multiPaymentTransactionData.getSenderPublicKey(), multiPaymentTransactionData.getPayments(), multiPaymentTransactionData.getFee(), multiPaymentTransactionData.getSignature(), true); } diff --git a/src/main/java/org/qora/transaction/PaymentTransaction.java b/src/main/java/org/qora/transaction/PaymentTransaction.java index ce1c1f54..fffdc0fa 100644 --- a/src/main/java/org/qora/transaction/PaymentTransaction.java +++ b/src/main/java/org/qora/transaction/PaymentTransaction.java @@ -1,7 +1,6 @@ package org.qora.transaction; import java.math.BigDecimal; -import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -77,21 +76,29 @@ public class PaymentTransaction extends Transaction { @Override public ValidationResult isValid() throws DataException { - // Check reference is correct - Account sender = getSender(); - if (!Arrays.equals(sender.getLastReference(), paymentTransactionData.getReference())) - return ValidationResult.INVALID_REFERENCE; - // Wrap and delegate final payment checks to Payment class return new Payment(this.repository).isValid(paymentTransactionData.getSenderPublicKey(), getPaymentData(), paymentTransactionData.getFee()); } + @Override + public ValidationResult isProcessable() throws DataException { + // Wrap and delegate final processable checks to Payment class + return new Payment(this.repository).isProcessable(paymentTransactionData.getSenderPublicKey(), getPaymentData(), paymentTransactionData.getFee()); + } + @Override public void process() throws DataException { // We would save updated transaction at this point, but it hasn't been modified - // Wrap and delegate payment processing to Payment class. Only update recipient's last reference if transferring QORA. + // Wrap and delegate payment processing to Payment class. new Payment(this.repository).process(paymentTransactionData.getSenderPublicKey(), getPaymentData(), paymentTransactionData.getFee(), + paymentTransactionData.getSignature()); + } + + @Override + public void processReferencesAndFees() throws DataException { + // Wrap and delegate references processing to Payment class. Only update recipient's last reference if transferring QORA. + new Payment(this.repository).processReferencesAndFees(paymentTransactionData.getSenderPublicKey(), getPaymentData(), paymentTransactionData.getFee(), paymentTransactionData.getSignature(), false); } diff --git a/src/main/java/org/qora/transaction/ProxyForgingTransaction.java b/src/main/java/org/qora/transaction/ProxyForgingTransaction.java index 17ec5071..5d41f6c3 100644 --- a/src/main/java/org/qora/transaction/ProxyForgingTransaction.java +++ b/src/main/java/org/qora/transaction/ProxyForgingTransaction.java @@ -106,10 +106,6 @@ public class ProxyForgingTransaction extends Transaction { if (proxyForgingTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) return ValidationResult.NEGATIVE_FEE; - // Check reference - if (!Arrays.equals(creator.getLastReference(), proxyForgingTransactionData.getReference())) - return ValidationResult.INVALID_REFERENCE; - // Check creator has enough funds if (creator.getConfirmedBalance(Asset.QORA).compareTo(proxyForgingTransactionData.getFee()) < 0) return ValidationResult.NO_BALANCE; @@ -134,12 +130,11 @@ public class ProxyForgingTransaction extends Transaction { // Save proxy forging info proxyForgerData = new ProxyForgerData(forger.getPublicKey(), proxyForgingTransactionData.getRecipient(), proxyForgingTransactionData.getProxyPublicKey(), proxyForgingTransactionData.getShare()); this.repository.getAccountRepository().save(proxyForgerData); + } - // Update forger's balance - forger.setConfirmedBalance(Asset.QORA, forger.getConfirmedBalance(Asset.QORA).subtract(proxyForgingTransactionData.getFee())); - - // Update forger's reference - forger.setLastReference(proxyForgingTransactionData.getSignature()); + @Override + public void processReferencesAndFees() throws DataException { + super.processReferencesAndFees(); // If proxy recipient has no last-reference then use this transaction's signature as last-reference so they can spend their block rewards Account recipient = new Account(this.repository, proxyForgingTransactionData.getRecipient()); diff --git a/src/main/java/org/qora/transaction/RegisterNameTransaction.java b/src/main/java/org/qora/transaction/RegisterNameTransaction.java index eb7d065e..93bd8c42 100644 --- a/src/main/java/org/qora/transaction/RegisterNameTransaction.java +++ b/src/main/java/org/qora/transaction/RegisterNameTransaction.java @@ -1,7 +1,6 @@ package org.qora.transaction; import java.math.BigDecimal; -import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -78,10 +77,6 @@ public class RegisterNameTransaction extends Transaction { public ValidationResult isValid() throws DataException { Account registrant = getRegistrant(); - // If accounts are only allowed one registered name then check for this - if (BlockChain.getInstance().oneNamePerAccount() && !this.repository.getNameRepository().getNamesByOwner(registrant.getAddress()).isEmpty()) - return ValidationResult.MULTIPLE_NAMES_FORBIDDEN; - // Check owner address is valid if (!Crypto.isValidAddress(registerNameTransactionData.getOwner())) return ValidationResult.INVALID_ADDRESS; @@ -100,18 +95,10 @@ public class RegisterNameTransaction extends Transaction { if (!registerNameTransactionData.getName().equals(registerNameTransactionData.getName().toLowerCase())) return ValidationResult.NAME_NOT_LOWER_CASE; - // Check the name isn't already taken - if (this.repository.getNameRepository().nameExists(registerNameTransactionData.getName())) - return ValidationResult.NAME_ALREADY_REGISTERED; - // Check fee is positive if (registerNameTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) return ValidationResult.NEGATIVE_FEE; - // Check reference is correct - if (!Arrays.equals(registrant.getLastReference(), registerNameTransactionData.getReference())) - return ValidationResult.INVALID_REFERENCE; - // Check issuer has enough funds if (registrant.getConfirmedBalance(Asset.QORA).compareTo(registerNameTransactionData.getFee()) < 0) return ValidationResult.NO_BALANCE; @@ -119,6 +106,21 @@ public class RegisterNameTransaction extends Transaction { return ValidationResult.OK; } + @Override + public ValidationResult isProcessable() throws DataException { + // Check the name isn't already taken + if (this.repository.getNameRepository().nameExists(registerNameTransactionData.getName())) + return ValidationResult.NAME_ALREADY_REGISTERED; + + Account registrant = getRegistrant(); + + // If accounts are only allowed one registered name then check for this + if (BlockChain.getInstance().oneNamePerAccount() && !this.repository.getNameRepository().getNamesByOwner(registrant.getAddress()).isEmpty()) + return ValidationResult.MULTIPLE_NAMES_FORBIDDEN; + + return ValidationResult.OK; + } + @Override public void process() throws DataException { // Register Name @@ -126,13 +128,6 @@ public class RegisterNameTransaction extends Transaction { name.register(); // We would save updated transaction at this point, but it hasn't been modified - - // Update registrant's balance - Account registrant = getRegistrant(); - registrant.setConfirmedBalance(Asset.QORA, registrant.getConfirmedBalance(Asset.QORA).subtract(registerNameTransactionData.getFee())); - - // Update registrant's reference - registrant.setLastReference(registerNameTransactionData.getSignature()); } @Override diff --git a/src/main/java/org/qora/transaction/RemoveGroupAdminTransaction.java b/src/main/java/org/qora/transaction/RemoveGroupAdminTransaction.java index ec848352..5efa78e0 100644 --- a/src/main/java/org/qora/transaction/RemoveGroupAdminTransaction.java +++ b/src/main/java/org/qora/transaction/RemoveGroupAdminTransaction.java @@ -1,7 +1,6 @@ package org.qora.transaction; import java.math.BigDecimal; -import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -100,10 +99,6 @@ public class RemoveGroupAdminTransaction extends Transaction { if (removeGroupAdminTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) return ValidationResult.NEGATIVE_FEE; - // Check reference - if (!Arrays.equals(owner.getLastReference(), removeGroupAdminTransactionData.getReference())) - return ValidationResult.INVALID_REFERENCE; - // Check creator has enough funds if (owner.getConfirmedBalance(Asset.QORA).compareTo(removeGroupAdminTransactionData.getFee()) < 0) return ValidationResult.NO_BALANCE; @@ -119,13 +114,6 @@ public class RemoveGroupAdminTransaction extends Transaction { // Save this transaction with cached references to transactions that can help restore state this.repository.getTransactionRepository().save(removeGroupAdminTransactionData); - - // Update owner's balance - Account owner = getOwner(); - owner.setConfirmedBalance(Asset.QORA, owner.getConfirmedBalance(Asset.QORA).subtract(removeGroupAdminTransactionData.getFee())); - - // Update owner's reference - owner.setLastReference(removeGroupAdminTransactionData.getSignature()); } @Override diff --git a/src/main/java/org/qora/transaction/SellNameTransaction.java b/src/main/java/org/qora/transaction/SellNameTransaction.java index 43a21df7..ec54294f 100644 --- a/src/main/java/org/qora/transaction/SellNameTransaction.java +++ b/src/main/java/org/qora/transaction/SellNameTransaction.java @@ -2,7 +2,6 @@ package org.qora.transaction; import java.math.BigDecimal; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import org.qora.account.Account; @@ -105,10 +104,6 @@ public class SellNameTransaction extends Transaction { if (sellNameTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) return ValidationResult.NEGATIVE_FEE; - // Check reference is correct - if (!Arrays.equals(owner.getLastReference(), sellNameTransactionData.getReference())) - return ValidationResult.INVALID_REFERENCE; - // Check issuer has enough funds if (owner.getConfirmedBalance(Asset.QORA).compareTo(sellNameTransactionData.getFee()) < 0) return ValidationResult.NO_BALANCE; @@ -123,13 +118,6 @@ public class SellNameTransaction extends Transaction { name.sell(sellNameTransactionData); // We would save updated transaction at this point, but it hasn't been modified - - // Update owner's balance - Account owner = getOwner(); - owner.setConfirmedBalance(Asset.QORA, owner.getConfirmedBalance(Asset.QORA).subtract(sellNameTransactionData.getFee())); - - // Update owner's reference - owner.setLastReference(sellNameTransactionData.getSignature()); } @Override diff --git a/src/main/java/org/qora/transaction/SetGroupTransaction.java b/src/main/java/org/qora/transaction/SetGroupTransaction.java index ba89dd55..d0bdca81 100644 --- a/src/main/java/org/qora/transaction/SetGroupTransaction.java +++ b/src/main/java/org/qora/transaction/SetGroupTransaction.java @@ -1,7 +1,6 @@ package org.qora.transaction; import java.math.BigDecimal; -import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -74,10 +73,6 @@ public class SetGroupTransaction extends Transaction { if (setGroupTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) return ValidationResult.NEGATIVE_FEE; - // Check reference - if (!Arrays.equals(creator.getLastReference(), setGroupTransactionData.getReference())) - return ValidationResult.INVALID_REFERENCE; - // Check creator has enough funds if (creator.getConfirmedBalance(Asset.QORA).compareTo(setGroupTransactionData.getFee()) < 0) return ValidationResult.NO_BALANCE; @@ -100,12 +95,6 @@ public class SetGroupTransaction extends Transaction { // Set account's new default groupID creator.setDefaultGroupId(setGroupTransactionData.getDefaultGroupId()); - - // Update creator's balance - creator.setConfirmedBalance(Asset.QORA, creator.getConfirmedBalance(Asset.QORA).subtract(setGroupTransactionData.getFee())); - - // Update admin's reference - creator.setLastReference(setGroupTransactionData.getSignature()); } @Override diff --git a/src/main/java/org/qora/transaction/Transaction.java b/src/main/java/org/qora/transaction/Transaction.java index ac2024e1..e1bf6245 100644 --- a/src/main/java/org/qora/transaction/Transaction.java +++ b/src/main/java/org/qora/transaction/Transaction.java @@ -229,6 +229,7 @@ public abstract class Transaction { PUBLIC_KEY_UNKNOWN(78), INVALID_PUBLIC_KEY(79), AT_UNKNOWN(80), + AT_ALREADY_EXISTS(81), NOT_YET_RELEASED(1000); public final int value; @@ -831,6 +832,33 @@ public abstract class Transaction { */ public abstract ValidationResult isValid() throws DataException; + /** + * Returns whether transaction's reference is valid. + * + * @throws DataException + */ + public boolean hasValidReference() throws DataException { + Account creator = getCreator(); + + return Arrays.equals(transactionData.getReference(), creator.getLastReference()); + } + + /** + * Returns whether transaction can be processed. + *

+ * With group-approval, even if a transaction had valid values + * when submitted, by the time it is approved dependency might + * have changed. + *

+ * For example, with UPDATE_ASSET, the asset owner might have + * changed between submission and approval. + * + * @throws DataException + */ + public ValidationResult isProcessable() throws DataException { + return ValidationResult.OK; + }; + /** * Actually process a transaction, updating the blockchain. *

@@ -841,13 +869,14 @@ public abstract class Transaction { public abstract void process() throws DataException; /** - * Update creator's last reference, subtract transaction fee, etc. + * Update last references, subtract transaction fees, etc. * * @throws DataException */ - public void processCreatorUpdates() throws DataException { - // Update transaction creator's balance + public void processReferencesAndFees() throws DataException { Account creator = getCreator(); + + // Update transaction creator's balance creator.setConfirmedBalance(Asset.QORA, creator.getConfirmedBalance(Asset.QORA).subtract(transactionData.getFee())); // Update transaction creator's reference diff --git a/src/main/java/org/qora/transaction/TransferAssetTransaction.java b/src/main/java/org/qora/transaction/TransferAssetTransaction.java index 2612d510..d0434755 100644 --- a/src/main/java/org/qora/transaction/TransferAssetTransaction.java +++ b/src/main/java/org/qora/transaction/TransferAssetTransaction.java @@ -1,7 +1,6 @@ package org.qora.transaction; import java.math.BigDecimal; -import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -89,22 +88,29 @@ public class TransferAssetTransaction extends Transaction { if (this.transferAssetTransactionData.getTimestamp() < BlockChain.getInstance().getAssetsReleaseTimestamp()) return ValidationResult.NOT_YET_RELEASED; - // Check reference is correct - Account sender = getSender(); - - if (!Arrays.equals(sender.getLastReference(), transferAssetTransactionData.getReference())) - return ValidationResult.INVALID_REFERENCE; - // Wrap asset transfer as a payment and delegate final payment checks to Payment class return new Payment(this.repository).isValid(transferAssetTransactionData.getSenderPublicKey(), getPaymentData(), transferAssetTransactionData.getFee()); } + @Override + public ValidationResult isProcessable() throws DataException { + // Wrap asset transfer as a payment and delegate final processable checks to Payment class + return new Payment(this.repository).isProcessable(transferAssetTransactionData.getSenderPublicKey(), getPaymentData(), transferAssetTransactionData.getFee()); + } + @Override public void process() throws DataException { // We would save updated transaction at this point, but it hasn't been modified // Wrap asset transfer as a payment and delegate processing to Payment class. Only update recipient's last reference if transferring QORA. new Payment(this.repository).process(transferAssetTransactionData.getSenderPublicKey(), getPaymentData(), transferAssetTransactionData.getFee(), + transferAssetTransactionData.getSignature()); + } + + @Override + public void processReferencesAndFees() throws DataException { + // Wrap asset transfer as a payment and delegate processing to Payment class. Only update recipient's last reference if transferring QORA. + new Payment(this.repository).processReferencesAndFees(transferAssetTransactionData.getSenderPublicKey(), getPaymentData(), transferAssetTransactionData.getFee(), transferAssetTransactionData.getSignature(), false); } diff --git a/src/main/java/org/qora/transaction/UpdateAssetTransaction.java b/src/main/java/org/qora/transaction/UpdateAssetTransaction.java index b9fbe909..1a679432 100644 --- a/src/main/java/org/qora/transaction/UpdateAssetTransaction.java +++ b/src/main/java/org/qora/transaction/UpdateAssetTransaction.java @@ -1,7 +1,6 @@ package org.qora.transaction; import java.math.BigDecimal; -import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -85,11 +84,6 @@ public class UpdateAssetTransaction extends Transaction { if (assetData == null) return ValidationResult.ASSET_DOES_NOT_EXIST; - // Check transaction's public key matches asset's current owner - PublicKeyAccount currentOwner = getOwner(); - if (!assetData.getOwner().equals(currentOwner.getAddress())) - return ValidationResult.INVALID_ASSET_OWNER; - // Check new owner address is valid if (!Crypto.isValidAddress(updateAssetTransactionData.getNewOwner())) return ValidationResult.INVALID_ADDRESS; @@ -113,9 +107,7 @@ public class UpdateAssetTransaction extends Transaction { if (updateAssetTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) return ValidationResult.NEGATIVE_FEE; - // Check reference is correct - if (!Arrays.equals(currentOwner.getLastReference(), updateAssetTransactionData.getReference())) - return ValidationResult.INVALID_REFERENCE; + Account currentOwner = getOwner(); // Check current owner has enough funds if (currentOwner.getConfirmedBalance(Asset.QORA).compareTo(updateAssetTransactionData.getFee()) < 0) @@ -124,6 +116,18 @@ public class UpdateAssetTransaction extends Transaction { return ValidationResult.OK; } + @Override + public ValidationResult isProcessable() throws DataException { + // Check transaction's public key matches asset's current owner + Account currentOwner = getOwner(); + AssetData assetData = this.repository.getAssetRepository().fromAssetId(updateAssetTransactionData.getAssetId()); + + if (!assetData.getOwner().equals(currentOwner.getAddress())) + return ValidationResult.INVALID_ASSET_OWNER; + + return ValidationResult.OK; + } + @Override public void process() throws DataException { // Update Asset @@ -132,14 +136,6 @@ public class UpdateAssetTransaction extends Transaction { // Save this transaction, with updated "name reference" to previous transaction that updated name this.repository.getTransactionRepository().save(updateAssetTransactionData); - - // Update old owner's balance - Account owner = getOwner(); - owner.setConfirmedBalance(Asset.QORA, - owner.getConfirmedBalance(Asset.QORA).subtract(updateAssetTransactionData.getFee())); - - // Update owner's reference - owner.setLastReference(updateAssetTransactionData.getSignature()); } @Override diff --git a/src/main/java/org/qora/transaction/UpdateGroupTransaction.java b/src/main/java/org/qora/transaction/UpdateGroupTransaction.java index 9e56c327..0d076d72 100644 --- a/src/main/java/org/qora/transaction/UpdateGroupTransaction.java +++ b/src/main/java/org/qora/transaction/UpdateGroupTransaction.java @@ -1,7 +1,6 @@ package org.qora.transaction; import java.math.BigDecimal; -import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -101,6 +100,22 @@ public class UpdateGroupTransaction extends Transaction { Account owner = getOwner(); + // Check fee is positive + if (updateGroupTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) + return ValidationResult.NEGATIVE_FEE; + + // Check creator has enough funds + if (owner.getConfirmedBalance(Asset.QORA).compareTo(updateGroupTransactionData.getFee()) < 0) + return ValidationResult.NO_BALANCE; + + return ValidationResult.OK; + } + + @Override + public ValidationResult isProcessable() throws DataException { + GroupData groupData = this.repository.getGroupRepository().fromGroupId(updateGroupTransactionData.getGroupId()); + Account owner = getOwner(); + // Check transaction's public key matches group's current owner if (!owner.getAddress().equals(groupData.getOwner())) return ValidationResult.INVALID_GROUP_OWNER; @@ -111,17 +126,6 @@ public class UpdateGroupTransaction extends Transaction { if (this.repository.getGroupRepository().banExists(updateGroupTransactionData.getGroupId(), newOwner.getAddress())) return ValidationResult.BANNED_FROM_GROUP; - // Check fee is positive - if (updateGroupTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) - return ValidationResult.NEGATIVE_FEE; - - // Check reference is correct - if (!Arrays.equals(owner.getLastReference(), updateGroupTransactionData.getReference())) - return ValidationResult.INVALID_REFERENCE; - - // Check creator has enough funds - if (owner.getConfirmedBalance(Asset.QORA).compareTo(updateGroupTransactionData.getFee()) < 0) - return ValidationResult.NO_BALANCE; return ValidationResult.OK; } @@ -134,13 +138,6 @@ public class UpdateGroupTransaction extends Transaction { // Save this transaction, now with updated "group reference" to previous transaction that updated group this.repository.getTransactionRepository().save(updateGroupTransactionData); - - // Update owner's balance - Account owner = getOwner(); - owner.setConfirmedBalance(Asset.QORA, owner.getConfirmedBalance(Asset.QORA).subtract(updateGroupTransactionData.getFee())); - - // Update owner's reference - owner.setLastReference(updateGroupTransactionData.getSignature()); } @Override diff --git a/src/main/java/org/qora/transaction/UpdateNameTransaction.java b/src/main/java/org/qora/transaction/UpdateNameTransaction.java index 25b4de87..94da0bc3 100644 --- a/src/main/java/org/qora/transaction/UpdateNameTransaction.java +++ b/src/main/java/org/qora/transaction/UpdateNameTransaction.java @@ -1,7 +1,6 @@ package org.qora.transaction; import java.math.BigDecimal; -import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -104,23 +103,12 @@ public class UpdateNameTransaction extends Transaction { if (nameData.getCreationGroupId() != updateNameTransactionData.getTxGroupId()) return ValidationResult.TX_GROUP_ID_MISMATCH; - // Check name isn't currently for sale - if (nameData.getIsForSale()) - return ValidationResult.NAME_ALREADY_FOR_SALE; - - // Check transaction's public key matches name's current owner Account owner = getOwner(); - if (!owner.getAddress().equals(nameData.getOwner())) - return ValidationResult.INVALID_NAME_OWNER; // Check fee is positive if (updateNameTransactionData.getFee().compareTo(BigDecimal.ZERO) <= 0) return ValidationResult.NEGATIVE_FEE; - // Check reference is correct - if (!Arrays.equals(owner.getLastReference(), updateNameTransactionData.getReference())) - return ValidationResult.INVALID_REFERENCE; - // Check issuer has enough funds if (owner.getConfirmedBalance(Asset.QORA).compareTo(updateNameTransactionData.getFee()) < 0) return ValidationResult.NO_BALANCE; @@ -128,6 +116,23 @@ public class UpdateNameTransaction extends Transaction { return ValidationResult.OK; } + @Override + public ValidationResult isProcessable() throws DataException { + NameData nameData = this.repository.getNameRepository().fromName(updateNameTransactionData.getName()); + + // Check name isn't currently for sale + if (nameData.getIsForSale()) + return ValidationResult.NAME_ALREADY_FOR_SALE; + + Account owner = getOwner(); + + // Check transaction's public key matches name's current owner + if (!owner.getAddress().equals(nameData.getOwner())) + return ValidationResult.INVALID_NAME_OWNER; + + return ValidationResult.OK; + } + @Override public void process() throws DataException { // Update Name @@ -136,13 +141,6 @@ public class UpdateNameTransaction extends Transaction { // Save this transaction, now with updated "name reference" to previous transaction that updated name this.repository.getTransactionRepository().save(updateNameTransactionData); - - // Update owner's balance - Account owner = getOwner(); - owner.setConfirmedBalance(Asset.QORA, owner.getConfirmedBalance(Asset.QORA).subtract(updateNameTransactionData.getFee())); - - // Update owner's reference - owner.setLastReference(updateNameTransactionData.getSignature()); } @Override diff --git a/src/main/java/org/qora/transaction/VoteOnPollTransaction.java b/src/main/java/org/qora/transaction/VoteOnPollTransaction.java index 773ae40f..7d856c43 100644 --- a/src/main/java/org/qora/transaction/VoteOnPollTransaction.java +++ b/src/main/java/org/qora/transaction/VoteOnPollTransaction.java @@ -2,7 +2,6 @@ package org.qora.transaction; import java.math.BigDecimal; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import org.apache.logging.log4j.LogManager; @@ -110,9 +109,6 @@ public class VoteOnPollTransaction extends Transaction { // Check reference is correct Account voter = getVoter(); - if (!Arrays.equals(voter.getLastReference(), voteOnPollTransactionData.getReference())) - return ValidationResult.INVALID_REFERENCE; - // Check voter has enough funds if (voter.getConfirmedBalance(Asset.QORA).compareTo(voteOnPollTransactionData.getFee()) < 0) return ValidationResult.NO_BALANCE; @@ -122,12 +118,7 @@ public class VoteOnPollTransaction extends Transaction { @Override public void process() throws DataException { - // Update voter's balance Account voter = getVoter(); - voter.setConfirmedBalance(Asset.QORA, voter.getConfirmedBalance(Asset.QORA).subtract(voteOnPollTransactionData.getFee())); - - // Update vote's reference - voter.setLastReference(voteOnPollTransactionData.getSignature()); VotingRepository votingRepository = this.repository.getVotingRepository();