diff --git a/src/main/java/org/qortal/api/resource/CrossChainResource.java b/src/main/java/org/qortal/api/resource/CrossChainResource.java index 28d78226..bb60c372 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainResource.java @@ -815,9 +815,10 @@ public class CrossChainResource { Long fee = null; long amount = 0L; + Long assetId = null; // no assetId as amount is zero BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, senderPublicKey, fee, null); - TransactionData messageTransactionData = new MessageTransactionData(baseTransactionData, 4, atAddress, Asset.QORT, amount, messageData, false, false); + TransactionData messageTransactionData = new MessageTransactionData(baseTransactionData, 4, atAddress, amount, assetId, messageData, false, false); MessageTransaction messageTransaction = new MessageTransaction(repository, messageTransactionData); diff --git a/src/main/java/org/qortal/data/transaction/MessageTransactionData.java b/src/main/java/org/qortal/data/transaction/MessageTransactionData.java index 0c9b29f3..649687f6 100644 --- a/src/main/java/org/qortal/data/transaction/MessageTransactionData.java +++ b/src/main/java/org/qortal/data/transaction/MessageTransactionData.java @@ -5,7 +5,6 @@ import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; -import org.qortal.asset.Asset; import org.qortal.transaction.Transaction.TransactionType; import io.swagger.v3.oas.annotations.media.Schema; @@ -16,14 +15,24 @@ import io.swagger.v3.oas.annotations.media.Schema; public class MessageTransactionData extends TransactionData { // Properties + private byte[] senderPublicKey; + private int version; + + // Not always present private String recipient; - private Long assetId; + @XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class) private long amount; + + // Not present if amount is zero + private Long assetId; + private byte[] data; + private boolean isText; + private boolean isEncrypted; // Constructors @@ -38,19 +47,14 @@ public class MessageTransactionData extends TransactionData { } public MessageTransactionData(BaseTransactionData baseTransactionData, - int version, String recipient, Long assetId, long amount, byte[] data, boolean isText, boolean isEncrypted) { + int version, String recipient, long amount, Long assetId, byte[] data, boolean isText, boolean isEncrypted) { super(TransactionType.MESSAGE, baseTransactionData); this.senderPublicKey = baseTransactionData.creatorPublicKey; this.version = version; this.recipient = recipient; - - if (assetId != null) - this.assetId = assetId; - else - this.assetId = Asset.QORT; - this.amount = amount; + this.assetId = assetId; this.data = data; this.isText = isText; this.isEncrypted = isEncrypted; @@ -70,23 +74,23 @@ public class MessageTransactionData extends TransactionData { return this.recipient; } - public Long getAssetId() { - return this.assetId; - } - public long getAmount() { return this.amount; } + public Long getAssetId() { + return this.assetId; + } + public byte[] getData() { return this.data; } - public boolean getIsText() { + public boolean isText() { return this.isText; } - public boolean getIsEncrypted() { + public boolean isEncrypted() { return this.isEncrypted; } diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java index 730740cd..09d38187 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBDatabaseUpdates.java @@ -269,8 +269,8 @@ public class HSQLDBDatabaseUpdates { case 6: // Message Transactions stmt.execute("CREATE TABLE MessageTransactions (signature Signature, version TINYINT NOT NULL, " - + "sender QortalPublicKey NOT NULL, recipient QortalAddress NOT NULL, " - + "is_text BOOLEAN NOT NULL, is_encrypted BOOLEAN NOT NULL, amount QortalAmount NOT NULL, asset_id AssetID NOT NULL, data MessageData NOT NULL, " + + "sender QortalPublicKey NOT NULL, recipient QortalAddress, amount QortalAmount NOT NULL, asset_id AssetID, " + + "is_text BOOLEAN NOT NULL, is_encrypted BOOLEAN NOT NULL, data MessageData NOT NULL, " + TRANSACTION_KEYS + ")"); break; diff --git a/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBMessageTransactionRepository.java b/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBMessageTransactionRepository.java index 1ef2a707..c33dc032 100644 --- a/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBMessageTransactionRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBMessageTransactionRepository.java @@ -36,7 +36,7 @@ public class HSQLDBMessageTransactionRepository extends HSQLDBTransactionReposit byte[] data = resultSet.getBytes(7); - return new MessageTransactionData(baseTransactionData, version, recipient, assetId, amount, data, isText, isEncrypted); + return new MessageTransactionData(baseTransactionData, version, recipient, amount, assetId, data, isText, isEncrypted); } catch (SQLException e) { throw new DataException("Unable to fetch message transaction from repository", e); } @@ -50,7 +50,7 @@ public class HSQLDBMessageTransactionRepository extends HSQLDBTransactionReposit saveHelper.bind("signature", messageTransactionData.getSignature()).bind("version", messageTransactionData.getVersion()) .bind("sender", messageTransactionData.getSenderPublicKey()).bind("recipient", messageTransactionData.getRecipient()) - .bind("is_text", messageTransactionData.getIsText()).bind("is_encrypted", messageTransactionData.getIsEncrypted()) + .bind("is_text", messageTransactionData.isText()).bind("is_encrypted", messageTransactionData.isEncrypted()) .bind("amount", messageTransactionData.getAmount()).bind("asset_id", messageTransactionData.getAssetId()) .bind("data", messageTransactionData.getData()); diff --git a/src/main/java/org/qortal/transaction/MessageTransaction.java b/src/main/java/org/qortal/transaction/MessageTransaction.java index 463f47a2..2d70dce8 100644 --- a/src/main/java/org/qortal/transaction/MessageTransaction.java +++ b/src/main/java/org/qortal/transaction/MessageTransaction.java @@ -4,6 +4,7 @@ import java.util.Collections; import java.util.List; import org.qortal.account.Account; +import org.qortal.asset.Asset; import org.qortal.data.PaymentData; import org.qortal.data.transaction.MessageTransactionData; import org.qortal.data.transaction.TransactionData; @@ -13,14 +14,17 @@ import org.qortal.repository.Repository; public class MessageTransaction extends Transaction { + // Useful constants + + public static final int MAX_DATA_SIZE = 4000; + // Properties private MessageTransactionData messageTransactionData; + + /** Cached, lazy-instantiated payment data. Use {@link #getPaymentData()} instead! */ private PaymentData paymentData = null; - // Other useful constants - public static final int MAX_DATA_SIZE = 4000; - private static final boolean isZeroAmountValid = true; // Constructors @@ -34,6 +38,9 @@ public class MessageTransaction extends Transaction { @Override public List getRecipientAddresses() throws DataException { + if (this.messageTransactionData.getRecipient() == null) + return Collections.emptyList(); + return Collections.singletonList(this.messageTransactionData.getRecipient()); } @@ -62,42 +69,80 @@ public class MessageTransaction extends Transaction { if (this.messageTransactionData.getData().length < 1 || this.messageTransactionData.getData().length > MAX_DATA_SIZE) return ValidationResult.INVALID_DATA_LENGTH; + // If message has no recipient then it cannot have a payment + if (this.messageTransactionData.getRecipient() == null && this.messageTransactionData.getAmount() != 0) + return ValidationResult.INVALID_AMOUNT; + + // If message has no payment then we only need to do a simple balance check for fee + if (this.messageTransactionData.getAmount() == 0) { + if (getSender().getConfirmedBalance(Asset.QORT) < this.messageTransactionData.getFee()) + return ValidationResult.NO_BALANCE; + + return ValidationResult.OK; + } + // Wrap and delegate final payment checks to Payment class - return new Payment(this.repository).isValid(this.messageTransactionData.getSenderPublicKey(), getPaymentData(), this.messageTransactionData.getFee(), - isZeroAmountValid); + return new Payment(this.repository).isValid(this.messageTransactionData.getSenderPublicKey(), getPaymentData(), + this.messageTransactionData.getFee(), true); } @Override public ValidationResult isProcessable() throws DataException { + // If we have no amount then we can always process + if (this.messageTransactionData.getAmount() == 0L) + return ValidationResult.OK; + // Wrap and delegate final processable checks to Payment class - return new Payment(this.repository).isProcessable(this.messageTransactionData.getSenderPublicKey(), getPaymentData(), this.messageTransactionData.getFee(), - isZeroAmountValid); + return new Payment(this.repository).isProcessable(this.messageTransactionData.getSenderPublicKey(), + getPaymentData(), this.messageTransactionData.getFee(), true); } @Override public void process() throws DataException { + // If we have no amount then there's nothing to do + if (this.messageTransactionData.getAmount() == 0L) + return; + // Wrap and delegate payment processing to Payment class. new Payment(this.repository).process(this.messageTransactionData.getSenderPublicKey(), getPaymentData()); } @Override public void processReferencesAndFees() throws DataException { + // If we have no amount then we only need to process sender's reference and fees + if (this.messageTransactionData.getAmount() == 0L) { + super.processReferencesAndFees(); + return; + } + // Wrap and delegate references processing to Payment class. Only update recipient's last reference if transferring QORT. - new Payment(this.repository).processReferencesAndFees(this.messageTransactionData.getSenderPublicKey(), getPaymentData(), this.messageTransactionData.getFee(), - this.messageTransactionData.getSignature(), false); + new Payment(this.repository).processReferencesAndFees(this.messageTransactionData.getSenderPublicKey(), + getPaymentData(), this.messageTransactionData.getFee(), this.messageTransactionData.getSignature(), + false); } @Override public void orphan() throws DataException { + // If we have no amount then there's nothing to do + if (this.messageTransactionData.getAmount() == 0L) + return; + // Wrap and delegate payment processing to Payment class. new Payment(this.repository).orphan(this.messageTransactionData.getSenderPublicKey(), getPaymentData()); } @Override public void orphanReferencesAndFees() throws DataException { + // If we have no amount then we only need to orphan sender's reference and fees + if (this.messageTransactionData.getAmount() == 0L) { + super.orphanReferencesAndFees(); + return; + } + // Wrap and delegate references processing to Payment class. Only revert recipient's last reference if transferring QORT. - new Payment(this.repository).orphanReferencesAndFees(this.messageTransactionData.getSenderPublicKey(), getPaymentData(), this.messageTransactionData.getFee(), - this.messageTransactionData.getSignature(), this.messageTransactionData.getReference(), false); + new Payment(this.repository).orphanReferencesAndFees(this.messageTransactionData.getSenderPublicKey(), + getPaymentData(), this.messageTransactionData.getFee(), this.messageTransactionData.getSignature(), + this.messageTransactionData.getReference(), false); } } diff --git a/src/main/java/org/qortal/transform/transaction/MessageTransactionTransformer.java b/src/main/java/org/qortal/transform/transaction/MessageTransactionTransformer.java index 7a008521..b3c41476 100644 --- a/src/main/java/org/qortal/transform/transaction/MessageTransactionTransformer.java +++ b/src/main/java/org/qortal/transform/transaction/MessageTransactionTransformer.java @@ -19,12 +19,13 @@ import com.google.common.primitives.Longs; public class MessageTransactionTransformer extends TransactionTransformer { // Property lengths + private static final int HAS_RECIPIENT_LENGTH = BOOLEAN_LENGTH; private static final int RECIPIENT_LENGTH = ADDRESS_LENGTH; private static final int DATA_SIZE_LENGTH = INT_LENGTH; private static final int IS_TEXT_LENGTH = BOOLEAN_LENGTH; private static final int IS_ENCRYPTED_LENGTH = BOOLEAN_LENGTH; - private static final int EXTRAS_LENGTH = RECIPIENT_LENGTH + ASSET_ID_LENGTH + AMOUNT_LENGTH + DATA_SIZE_LENGTH + IS_ENCRYPTED_LENGTH + IS_TEXT_LENGTH; + private static final int EXTRAS_LENGTH = HAS_RECIPIENT_LENGTH + AMOUNT_LENGTH + DATA_SIZE_LENGTH + IS_ENCRYPTED_LENGTH + IS_TEXT_LENGTH; protected static final TransactionLayout layout; @@ -35,9 +36,10 @@ public class MessageTransactionTransformer extends TransactionTransformer { layout.add("transaction's groupID", TransformationType.INT); layout.add("reference", TransformationType.SIGNATURE); layout.add("sender's public key", TransformationType.PUBLIC_KEY); - layout.add("recipient", TransformationType.ADDRESS); - layout.add("asset ID of payment", TransformationType.LONG); + layout.add("has recipient?", TransformationType.BOOLEAN); + layout.add("? recipient", TransformationType.ADDRESS); layout.add("payment (can be zero)", TransformationType.AMOUNT); + layout.add("asset ID of payment (if payment not zero)", TransformationType.LONG); layout.add("message length", TransformationType.INT); layout.add("message", TransformationType.DATA); layout.add("is message encrypted?", TransformationType.BOOLEAN); @@ -58,12 +60,13 @@ public class MessageTransactionTransformer extends TransactionTransformer { byte[] senderPublicKey = Serialization.deserializePublicKey(byteBuffer); - String recipient = Serialization.deserializeAddress(byteBuffer); - - long assetId = byteBuffer.getLong(); + boolean hasRecipient = byteBuffer.get() != 0; + String recipient = hasRecipient ? Serialization.deserializeAddress(byteBuffer) : null; long amount = byteBuffer.getLong(); + Long assetId = amount != 0 ? byteBuffer.getLong() : null; + int dataSize = byteBuffer.getInt(); // Don't allow invalid dataSize here to avoid run-time issues if (dataSize > MessageTransaction.MAX_DATA_SIZE) @@ -83,13 +86,21 @@ public class MessageTransactionTransformer extends TransactionTransformer { BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, senderPublicKey, fee, signature); - return new MessageTransactionData(baseTransactionData, version, recipient, assetId, amount, data, isText, isEncrypted); + return new MessageTransactionData(baseTransactionData, version, recipient, amount, assetId, data, isText, isEncrypted); } public static int getDataLength(TransactionData transactionData) throws TransformationException { MessageTransactionData messageTransactionData = (MessageTransactionData) transactionData; - return getBaseLength(transactionData) + EXTRAS_LENGTH + messageTransactionData.getData().length; + int dataLength = getBaseLength(transactionData) + EXTRAS_LENGTH + messageTransactionData.getData().length; + + if (messageTransactionData.getRecipient() != null) + dataLength += RECIPIENT_LENGTH; + + if (messageTransactionData.getAmount() != 0) + dataLength += ASSET_ID_LENGTH; + + return dataLength; } public static byte[] toBytes(TransactionData transactionData) throws TransformationException { @@ -100,19 +111,25 @@ public class MessageTransactionTransformer extends TransactionTransformer { transformCommonBytes(transactionData, bytes); - Serialization.serializeAddress(bytes, messageTransactionData.getRecipient()); - - bytes.write(Longs.toByteArray(messageTransactionData.getAssetId())); + if (messageTransactionData.getRecipient() != null) { + bytes.write((byte) 1); + Serialization.serializeAddress(bytes, messageTransactionData.getRecipient()); + } else { + bytes.write((byte) 0); + } bytes.write(Longs.toByteArray(messageTransactionData.getAmount())); + if (messageTransactionData.getAmount() != 0) + bytes.write(Longs.toByteArray(messageTransactionData.getAssetId())); + bytes.write(Ints.toByteArray(messageTransactionData.getData().length)); bytes.write(messageTransactionData.getData()); - bytes.write((byte) (messageTransactionData.getIsEncrypted() ? 1 : 0)); + bytes.write((byte) (messageTransactionData.isEncrypted() ? 1 : 0)); - bytes.write((byte) (messageTransactionData.getIsText() ? 1 : 0)); + bytes.write((byte) (messageTransactionData.isText() ? 1 : 0)); bytes.write(Longs.toByteArray(messageTransactionData.getFee())); diff --git a/src/test/java/org/qortal/test/MessageTests.java b/src/test/java/org/qortal/test/MessageTests.java new file mode 100644 index 00000000..c9c05050 --- /dev/null +++ b/src/test/java/org/qortal/test/MessageTests.java @@ -0,0 +1,154 @@ +package org.qortal.test; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.qortal.asset.Asset; +import org.qortal.data.transaction.MessageTransactionData; +import org.qortal.data.transaction.TransactionData; +import org.qortal.group.Group; +import org.qortal.group.Group.ApprovalThreshold; +import org.qortal.repository.DataException; +import org.qortal.repository.Repository; +import org.qortal.repository.RepositoryManager; +import org.qortal.test.common.BlockUtils; +import org.qortal.test.common.Common; +import org.qortal.test.common.GroupUtils; +import org.qortal.test.common.TestAccount; +import org.qortal.test.common.TransactionUtils; +import org.qortal.test.common.transaction.TestTransaction; +import org.qortal.transaction.MessageTransaction; +import org.qortal.transaction.Transaction; +import org.qortal.transaction.Transaction.TransactionType; +import org.qortal.transaction.Transaction.ValidationResult; +import org.qortal.transform.TransformationException; +import org.qortal.transform.transaction.MessageTransactionTransformer; +import org.qortal.transform.transaction.TransactionTransformer; + +import static org.junit.Assert.*; + +public class MessageTests extends Common { + + private static final int version = 3; + private static final String recipient = Common.getTestAccount(null, "bob").getAddress(); + + + @Before + public void beforeTest() throws DataException { + Common.useDefaultSettings(); + } + + @After + public void afterTest() throws DataException { + Common.orphanCheck(); + } + + @Test + public void validityTests() throws DataException { + // with recipient, with amount + assertTrue(isValid(Group.NO_GROUP, recipient, 123L, Asset.QORT)); + + // with recipient, no amount + assertTrue(isValid(Group.NO_GROUP, recipient, 0L, null)); + + // no recipient (message to group), no amount + assertTrue(isValid(Group.NO_GROUP, null, 0L, null)); + + // can't have amount if no recipient! + assertFalse(isValid(Group.NO_GROUP, null, 123L, Asset.QORT)); + + // Alice is part of group 1 + assertTrue(isValid(1, null, 0L, null)); + + int newGroupId; + try (final Repository repository = RepositoryManager.getRepository()) { + newGroupId = GroupUtils.createGroup(repository, "chloe", "non-alice-group", false, ApprovalThreshold.ONE, 10, 1440); + } + + // Alice is not part of new group + assertFalse(isValid(newGroupId, null, 0L, null)); + } + + @Test + public void withRecipentNoAmount() throws DataException { + testMessage(Group.NO_GROUP, recipient, 0L, null); + } + + @Test + public void withRecipentWithAmount() throws DataException { + testMessage(Group.NO_GROUP, recipient, 123L, Asset.QORT); + } + + @Test + public void noRecipentNoAmount() throws DataException { + testMessage(Group.NO_GROUP, null, 0L, null); + } + + @Test + public void noRecipentNoAmountWithGroup() throws DataException { + testMessage(1, null, 0L, null); + } + + @Test + public void serializationTests() throws DataException, TransformationException { + // with recipient, with amount + testSerialization(recipient, 123L, Asset.QORT); + + // with recipient, no amount + testSerialization(recipient, 0L, null); + + // no recipient (message to group), no amount + testSerialization(null, 0L, null); + } + + private boolean isValid(int txGroupId, String recipient, long amount, Long assetId) throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + TestAccount alice = Common.getTestAccount(repository, "alice"); + + MessageTransactionData transactionData = new MessageTransactionData(TestTransaction.generateBase(alice, txGroupId), + version, recipient, amount, assetId, new byte[1], false, false); + + Transaction transaction = new MessageTransaction(repository, transactionData); + + return transaction.isValidUnconfirmed() == ValidationResult.OK; + } + } + + private void testMessage(int txGroupId, String recipient, long amount, Long assetId) throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + TestAccount alice = Common.getTestAccount(repository, "alice"); + + MessageTransactionData transactionData = new MessageTransactionData(TestTransaction.generateBase(alice, txGroupId), + version, recipient, amount, assetId, new byte[1], false, false); + + TransactionUtils.signAndMint(repository, transactionData, alice); + + BlockUtils.orphanLastBlock(repository); + } + } + + private void testSerialization(String recipient, long amount, Long assetId) throws DataException, TransformationException { + try (final Repository repository = RepositoryManager.getRepository()) { + TestAccount alice = Common.getTestAccount(repository, "alice"); + + MessageTransactionData expectedTransactionData = new MessageTransactionData(TestTransaction.generateBase(alice), + version, recipient, amount, assetId, new byte[1], false, false); + + Transaction transaction = new MessageTransaction(repository, expectedTransactionData); + transaction.sign(alice); + + MessageTransactionTransformer.getDataLength(expectedTransactionData); + byte[] transactionBytes = MessageTransactionTransformer.toBytes(expectedTransactionData); + + TransactionData transactionData = TransactionTransformer.fromBytes(transactionBytes); + assertEquals(TransactionType.MESSAGE, transactionData.getType()); + + MessageTransactionData actualTransactionData = (MessageTransactionData) transactionData; + + assertEquals(expectedTransactionData.getRecipient(), actualTransactionData.getRecipient()); + assertEquals(expectedTransactionData.getAmount(), actualTransactionData.getAmount()); + assertEquals(expectedTransactionData.getAssetId(), actualTransactionData.getAssetId()); + } + } + +} diff --git a/src/test/java/org/qortal/test/btcacct/AtTests.java b/src/test/java/org/qortal/test/btcacct/AtTests.java index 1d4a6e0f..74771d62 100644 --- a/src/test/java/org/qortal/test/btcacct/AtTests.java +++ b/src/test/java/org/qortal/test/btcacct/AtTests.java @@ -474,9 +474,10 @@ public class AtTests extends Common { Long fee = null; long amount = 0; + Long assetId = null; // because amount is zero BaseTransactionData baseTransactionData = new BaseTransactionData(txTimestamp, Group.NO_GROUP, lastReference, sender.getPublicKey(), fee, null); - TransactionData messageTransactionData = new MessageTransactionData(baseTransactionData, 4, recipient, Asset.QORT, amount, data, false, false); + TransactionData messageTransactionData = new MessageTransactionData(baseTransactionData, 4, recipient, amount, assetId, data, false, false); MessageTransaction messageTransaction = new MessageTransaction(repository, messageTransactionData); diff --git a/src/test/java/org/qortal/test/common/transaction/MessageTestTransaction.java b/src/test/java/org/qortal/test/common/transaction/MessageTestTransaction.java index 7725bf3c..4bb4455e 100644 --- a/src/test/java/org/qortal/test/common/transaction/MessageTestTransaction.java +++ b/src/test/java/org/qortal/test/common/transaction/MessageTestTransaction.java @@ -19,7 +19,7 @@ public class MessageTestTransaction extends TestTransaction { final boolean isText = true; final boolean isEncrypted = false; - return new MessageTransactionData(generateBase(account), version, recipient, assetId, amount, data, isText, isEncrypted); + return new MessageTransactionData(generateBase(account), version, recipient, amount, assetId, data, isText, isEncrypted); } } diff --git a/src/test/java/org/qortal/test/common/transaction/TestTransaction.java b/src/test/java/org/qortal/test/common/transaction/TestTransaction.java index 00aa92de..11fdf58e 100644 --- a/src/test/java/org/qortal/test/common/transaction/TestTransaction.java +++ b/src/test/java/org/qortal/test/common/transaction/TestTransaction.java @@ -12,8 +12,12 @@ public abstract class TestTransaction { protected static final Random random = new Random(); + public static BaseTransactionData generateBase(PrivateKeyAccount account, int txGroupId) throws DataException { + return new BaseTransactionData(System.currentTimeMillis(), txGroupId, account.getLastReference(), account.getPublicKey(), BlockChain.getInstance().getUnitFee(), null); + } + public static BaseTransactionData generateBase(PrivateKeyAccount account) throws DataException { - return new BaseTransactionData(System.currentTimeMillis(), Group.NO_GROUP, account.getLastReference(), account.getPublicKey(), BlockChain.getInstance().getUnitFee(), null); + return generateBase(account, Group.NO_GROUP); } }