From 47ff51ce4eba7a28a4fd8e4ae5680656e9055f31 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Fri, 20 Aug 2021 07:49:05 +0100 Subject: [PATCH] Use a random last reference on the very first transaction for an account This is needed because we want to allow brand new accounts to publish data without a fee. A similar approach to CrossChainResource.buildAtMessage(). We already require PoW on all arbitrary transactions, so no additional logic beyond this should be needed. --- .../api/resource/ArbitraryResource.java | 12 ++++++++--- .../qortal/api/resource/WebsiteResource.java | 12 ++++++++--- .../transaction/ArbitraryTransaction.java | 20 +++++++++++++++++++ 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/qortal/api/resource/ArbitraryResource.java b/src/main/java/org/qortal/api/resource/ArbitraryResource.java index 3ddc217c..b85d6405 100644 --- a/src/main/java/org/qortal/api/resource/ArbitraryResource.java +++ b/src/main/java/org/qortal/api/resource/ArbitraryResource.java @@ -15,6 +15,7 @@ import java.net.UnknownHostException; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; +import java.util.Random; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.*; @@ -53,6 +54,7 @@ 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.Transformer; import org.qortal.transform.transaction.ArbitraryTransactionTransformer; import org.qortal.utils.Base58; import org.qortal.utils.NTP; @@ -269,10 +271,14 @@ public class ArbitraryResource { } byte[] creatorPublicKey = Base58.decode(creatorPublicKeyBase58); final String creatorAddress = Crypto.toAddress(creatorPublicKey); - final byte[] lastReference = repository.getAccountRepository().getLastReference(creatorAddress); + byte[] lastReference = repository.getAccountRepository().getLastReference(creatorAddress); if (lastReference == null) { - LOGGER.info(String.format("Qortal account %s has no last reference", creatorAddress)); - throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); + // Use a random last reference on the very first transaction for an account + // Code copied from CrossChainResource.buildAtMessage() + // We already require PoW on all arbitrary transactions, so no additional logic is needed + Random random = new Random(); + lastReference = new byte[Transformer.SIGNATURE_LENGTH]; + random.nextBytes(lastReference); } String name = null; diff --git a/src/main/java/org/qortal/api/resource/WebsiteResource.java b/src/main/java/org/qortal/api/resource/WebsiteResource.java index 9aafe4da..559b9bd5 100644 --- a/src/main/java/org/qortal/api/resource/WebsiteResource.java +++ b/src/main/java/org/qortal/api/resource/WebsiteResource.java @@ -15,6 +15,7 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Random; import com.google.common.io.Resources; import io.swagger.v3.oas.annotations.Operation; @@ -48,6 +49,7 @@ import org.qortal.arbitrary.ArbitraryDataWriter; import org.qortal.transaction.ArbitraryTransaction; import org.qortal.transaction.Transaction; import org.qortal.transform.TransformationException; +import org.qortal.transform.Transformer; import org.qortal.transform.transaction.ArbitraryTransactionTransformer; import org.qortal.utils.Base58; import org.qortal.utils.NTP; @@ -104,10 +106,14 @@ public class WebsiteResource { } byte[] creatorPublicKey = Base58.decode(creatorPublicKeyBase58); final String creatorAddress = Crypto.toAddress(creatorPublicKey); - final byte[] lastReference = repository.getAccountRepository().getLastReference(creatorAddress); + byte[] lastReference = repository.getAccountRepository().getLastReference(creatorAddress); if (lastReference == null) { - LOGGER.info(String.format("Qortal account %s has no last reference", creatorAddress)); - throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); + // Use a random last reference on the very first transaction for an account + // Code copied from CrossChainResource.buildAtMessage() + // We already require PoW on all arbitrary transactions, so no additional logic is needed + Random random = new Random(); + lastReference = new byte[Transformer.SIGNATURE_LENGTH]; + random.nextBytes(lastReference); } String name = "CalDescentTest1"; // TODO: dynamic diff --git a/src/main/java/org/qortal/transaction/ArbitraryTransaction.java b/src/main/java/org/qortal/transaction/ArbitraryTransaction.java index 0f5bea60..aaa5bd48 100644 --- a/src/main/java/org/qortal/transaction/ArbitraryTransaction.java +++ b/src/main/java/org/qortal/transaction/ArbitraryTransaction.java @@ -74,6 +74,26 @@ public class ArbitraryTransaction extends Transaction { this.arbitraryTransactionData.setNonce(MemoryPoW.compute2(transactionBytes, POW_BUFFER_SIZE, difficulty)); } + @Override + public boolean hasValidReference() throws DataException { + // We shouldn't really get this far, but just in case: + if (this.arbitraryTransactionData.getReference() == null) { + return false; + } + + // If the account current doesn't have a last reference, and the fee is 0, we will allow any value. + // This ensures that the first transaction for an account will be valid whilst still validating + // the last reference from the second transaction onwards. By checking for a zero fee, we ensure + // standard last reference validation when fee > 0. + Account creator = getCreator(); + Long fee = this.arbitraryTransactionData.getFee(); + if (creator.getLastReference() == null && fee == 0) { + return true; + } + + return super.hasValidReference(); + } + @Override public ValidationResult isValid() throws DataException { // Check that some data - or a data hash - has been supplied