forked from Qortal/qortal
Merge branch 'PUBLICIZE-txn' into launch
This commit is contained in:
commit
9aabf93523
@ -28,6 +28,7 @@ import org.qortal.api.ApiError;
|
||||
import org.qortal.api.ApiErrors;
|
||||
import org.qortal.api.ApiException;
|
||||
import org.qortal.api.ApiExceptionFactory;
|
||||
import org.qortal.api.Security;
|
||||
import org.qortal.api.model.ApiOnlineAccount;
|
||||
import org.qortal.api.model.RewardShareKeyRequest;
|
||||
import org.qortal.asset.Asset;
|
||||
@ -36,19 +37,27 @@ import org.qortal.crypto.Crypto;
|
||||
import org.qortal.data.account.AccountData;
|
||||
import org.qortal.data.account.RewardShareData;
|
||||
import org.qortal.data.network.OnlineAccountData;
|
||||
import org.qortal.data.transaction.PublicizeTransactionData;
|
||||
import org.qortal.data.transaction.RewardShareTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.settings.Settings;
|
||||
import org.qortal.transaction.PublicizeTransaction;
|
||||
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.PublicizeTransactionTransformer;
|
||||
import org.qortal.transform.transaction.RewardShareTransactionTransformer;
|
||||
import org.qortal.transform.transaction.TransactionTransformer;
|
||||
import org.qortal.utils.Amounts;
|
||||
import org.qortal.utils.Base58;
|
||||
|
||||
import com.google.common.primitives.Bytes;
|
||||
|
||||
@Path("/addresses")
|
||||
@Tag(name = "Addresses")
|
||||
public class AddressesResource {
|
||||
@ -390,4 +399,119 @@ public class AddressesResource {
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/publicize")
|
||||
@Operation(
|
||||
summary = "Build raw, unsigned, PUBLICIZE transaction",
|
||||
requestBody = @RequestBody(
|
||||
required = true,
|
||||
content = @Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
schema = @Schema(
|
||||
implementation = PublicizeTransactionData.class
|
||||
)
|
||||
)
|
||||
),
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
description = "raw, unsigned, PUBLICIZE transaction encoded in Base58",
|
||||
content = @Content(
|
||||
mediaType = MediaType.TEXT_PLAIN,
|
||||
schema = @Schema(
|
||||
type = "string"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||
public String publicize(PublicizeTransactionData transactionData) {
|
||||
if (Settings.getInstance().isApiRestricted())
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
Transaction transaction = Transaction.fromData(repository, transactionData);
|
||||
|
||||
ValidationResult result = transaction.isValidUnconfirmed();
|
||||
if (result != ValidationResult.OK && result != ValidationResult.INCORRECT_NONCE)
|
||||
throw TransactionsResource.createTransactionInvalidException(request, result);
|
||||
|
||||
byte[] bytes = PublicizeTransactionTransformer.toBytes(transactionData);
|
||||
return Base58.encode(bytes);
|
||||
} catch (TransformationException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSFORMATION_ERROR, e);
|
||||
} catch (DataException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/publicize/compute")
|
||||
@Operation(
|
||||
summary = "Compute nonce for raw, unsigned PUBLICIZE transaction",
|
||||
requestBody = @RequestBody(
|
||||
required = true,
|
||||
content = @Content(
|
||||
mediaType = MediaType.TEXT_PLAIN,
|
||||
schema = @Schema(
|
||||
type = "string",
|
||||
description = "raw, unsigned PUBLICIZE transaction in base58 encoding",
|
||||
example = "raw transaction base58"
|
||||
)
|
||||
)
|
||||
),
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
description = "raw, unsigned, PUBLICIZE transaction encoded in Base58",
|
||||
content = @Content(
|
||||
mediaType = MediaType.TEXT_PLAIN,
|
||||
schema = @Schema(
|
||||
type = "string"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.TRANSACTION_INVALID, ApiError.INVALID_DATA, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE})
|
||||
public String computePublicize(String rawBytes58) {
|
||||
Security.checkApiCallAllowed(request);
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
byte[] rawBytes = Base58.decode(rawBytes58);
|
||||
// We're expecting unsigned transaction, so append empty signature prior to decoding
|
||||
rawBytes = Bytes.concat(rawBytes, new byte[TransactionTransformer.SIGNATURE_LENGTH]);
|
||||
|
||||
TransactionData transactionData = TransactionTransformer.fromBytes(rawBytes);
|
||||
if (transactionData == null)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||
|
||||
if (transactionData.getType() != TransactionType.PUBLICIZE)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||
|
||||
PublicizeTransaction publicizeTransaction = (PublicizeTransaction) Transaction.fromData(repository, transactionData);
|
||||
|
||||
// Quicker validity check first before we compute nonce
|
||||
ValidationResult result = publicizeTransaction.isValid();
|
||||
if (result != ValidationResult.OK && result != ValidationResult.INCORRECT_NONCE)
|
||||
throw TransactionsResource.createTransactionInvalidException(request, result);
|
||||
|
||||
publicizeTransaction.computeNonce();
|
||||
|
||||
// Re-check, but ignores signature
|
||||
result = publicizeTransaction.isValidUnconfirmed();
|
||||
if (result != ValidationResult.OK)
|
||||
throw TransactionsResource.createTransactionInvalidException(request, result);
|
||||
|
||||
// Strip zeroed signature
|
||||
transactionData.setSignature(null);
|
||||
|
||||
byte[] bytes = PublicizeTransactionTransformer.toBytes(transactionData);
|
||||
return Base58.encode(bytes);
|
||||
} catch (TransformationException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSFORMATION_ERROR, e);
|
||||
} catch (DataException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -363,6 +363,60 @@ public class TransactionsResource {
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/creator/{publickey}")
|
||||
@Operation(
|
||||
summary = "Find matching transactions created by account with given public key",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
description = "transactions",
|
||||
content = @Content(
|
||||
array = @ArraySchema(
|
||||
schema = @Schema(
|
||||
implementation = TransactionData.class
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({
|
||||
ApiError.INVALID_CRITERIA, ApiError.REPOSITORY_ISSUE
|
||||
})
|
||||
public List<TransactionData> findCreatorsTransactions(@PathParam("publickey") String publicKey58,
|
||||
@Parameter(
|
||||
description = "whether to include confirmed, unconfirmed or both",
|
||||
required = true
|
||||
) @QueryParam("confirmationStatus") ConfirmationStatus confirmationStatus, @Parameter(
|
||||
ref = "limit"
|
||||
) @QueryParam("limit") Integer limit, @Parameter(
|
||||
ref = "offset"
|
||||
) @QueryParam("offset") Integer offset, @Parameter(
|
||||
ref = "reverse"
|
||||
) @QueryParam("reverse") Boolean reverse) {
|
||||
// Decode public key
|
||||
byte[] publicKey;
|
||||
try {
|
||||
publicKey = Base58.decode(publicKey58);
|
||||
} catch (NumberFormatException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PUBLIC_KEY, e);
|
||||
}
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
List<byte[]> signatures = repository.getTransactionRepository().getSignaturesMatchingCriteria(null,
|
||||
publicKey, confirmationStatus, limit, offset, reverse);
|
||||
|
||||
// Expand signatures to transactions
|
||||
List<TransactionData> transactions = new ArrayList<>(signatures.size());
|
||||
for (byte[] signature : signatures)
|
||||
transactions.add(repository.getTransactionRepository().fromSignature(signature));
|
||||
|
||||
return transactions;
|
||||
} catch (DataException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/sign")
|
||||
@Operation(
|
||||
|
@ -0,0 +1,56 @@
|
||||
package org.qortal.data.transaction;
|
||||
|
||||
import javax.xml.bind.Unmarshaller;
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
|
||||
import org.qortal.transaction.Transaction.TransactionType;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.media.Schema.AccessMode;
|
||||
|
||||
// All properties to be converted to JSON via JAXB
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
@Schema(allOf = { TransactionData.class })
|
||||
public class PublicizeTransactionData extends TransactionData {
|
||||
|
||||
// Properties
|
||||
@Schema(description = "sender's public key", example = "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP")
|
||||
private byte[] senderPublicKey;
|
||||
|
||||
@Schema(accessMode = AccessMode.READ_ONLY)
|
||||
private int nonce;
|
||||
|
||||
// Constructors
|
||||
|
||||
// For JAXB
|
||||
protected PublicizeTransactionData() {
|
||||
super(TransactionType.PUBLICIZE);
|
||||
}
|
||||
|
||||
public void afterUnmarshal(Unmarshaller u, Object parent) {
|
||||
this.creatorPublicKey = this.senderPublicKey;
|
||||
}
|
||||
|
||||
public PublicizeTransactionData(BaseTransactionData baseTransactionData, int nonce) {
|
||||
super(TransactionType.PUBLICIZE, baseTransactionData);
|
||||
|
||||
this.senderPublicKey = baseTransactionData.creatorPublicKey;
|
||||
this.nonce = nonce;
|
||||
}
|
||||
|
||||
// Getters/Setters
|
||||
|
||||
public byte[] getSenderPublicKey() {
|
||||
return this.senderPublicKey;
|
||||
}
|
||||
|
||||
public int getNonce() {
|
||||
return this.nonce;
|
||||
}
|
||||
|
||||
public void setNonce(int nonce) {
|
||||
this.nonce = nonce;
|
||||
}
|
||||
|
||||
}
|
@ -72,6 +72,24 @@ public interface TransactionRepository {
|
||||
List<TransactionType> txTypes, Integer service, String address,
|
||||
ConfirmationStatus confirmationStatus, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||
|
||||
/**
|
||||
* Returns signatures for transactions that match search criteria.
|
||||
* <p>
|
||||
* Simpler version that only checks accepts one (optional) transaction type,
|
||||
* and one (optional) public key.
|
||||
*
|
||||
* @param txType
|
||||
* @param publicKey
|
||||
* @param confirmationStatus
|
||||
* @param limit
|
||||
* @param offset
|
||||
* @param reverse
|
||||
* @return
|
||||
* @throws DataException
|
||||
*/
|
||||
public List<byte[]> getSignaturesMatchingCriteria(TransactionType txType, byte[] publicKey,
|
||||
ConfirmationStatus confirmationStatus, Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||
|
||||
/**
|
||||
* Returns signature for latest auto-update transaction.
|
||||
* <p>
|
||||
|
@ -613,6 +613,11 @@ public class HSQLDBDatabaseUpdates {
|
||||
stmt.execute("CREATE INDEX ChatTransactionsRecipientIndex ON ChatTransactions (recipient, sender)");
|
||||
break;
|
||||
|
||||
case 19:
|
||||
// PUBLICIZE transactions
|
||||
stmt.execute("CREATE TABLE PublicizeTransactions (signature Signature, nonce INT NOT NULL, " + TRANSACTION_KEYS + ")");
|
||||
break;
|
||||
|
||||
default:
|
||||
// nothing to do
|
||||
return false;
|
||||
|
@ -0,0 +1,50 @@
|
||||
package org.qortal.repository.hsqldb.transaction;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import org.qortal.data.transaction.BaseTransactionData;
|
||||
import org.qortal.data.transaction.PublicizeTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.hsqldb.HSQLDBRepository;
|
||||
import org.qortal.repository.hsqldb.HSQLDBSaver;
|
||||
|
||||
public class HSQLDBPublicizeTransactionRepository extends HSQLDBTransactionRepository {
|
||||
|
||||
public HSQLDBPublicizeTransactionRepository(HSQLDBRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
TransactionData fromBase(BaseTransactionData baseTransactionData) throws DataException {
|
||||
String sql = "SELECT nonce FROM PublicizeTransactions WHERE signature = ?";
|
||||
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(sql, baseTransactionData.getSignature())) {
|
||||
if (resultSet == null)
|
||||
return null;
|
||||
|
||||
int nonce = resultSet.getInt(1);
|
||||
|
||||
return new PublicizeTransactionData(baseTransactionData, nonce);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to fetch publicize transaction from repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(TransactionData transactionData) throws DataException {
|
||||
PublicizeTransactionData publicizeTransactionData = (PublicizeTransactionData) transactionData;
|
||||
|
||||
HSQLDBSaver saveHelper = new HSQLDBSaver("PublicizeTransactions");
|
||||
|
||||
saveHelper.bind("signature", publicizeTransactionData.getSignature())
|
||||
.bind("nonce", publicizeTransactionData.getNonce());
|
||||
|
||||
try {
|
||||
saveHelper.execute(this.repository);
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to save publicize transaction into repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -499,7 +499,75 @@ public class HSQLDBTransactionRepository implements TransactionRepository {
|
||||
|
||||
HSQLDBRepository.limitOffsetSql(sql, limit, offset);
|
||||
|
||||
LOGGER.trace(String.format("Transaction search SQL: %s", sql));
|
||||
LOGGER.trace(() -> String.format("Transaction search SQL: %s", sql));
|
||||
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(sql.toString(), bindParams.toArray())) {
|
||||
if (resultSet == null)
|
||||
return signatures;
|
||||
|
||||
do {
|
||||
byte[] signature = resultSet.getBytes(1);
|
||||
|
||||
signatures.add(signature);
|
||||
} while (resultSet.next());
|
||||
|
||||
return signatures;
|
||||
} catch (SQLException e) {
|
||||
throw new DataException("Unable to fetch matching transaction signatures from repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
public List<byte[]> getSignaturesMatchingCriteria(TransactionType txType, byte[] publicKey,
|
||||
ConfirmationStatus confirmationStatus, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||
List<byte[]> signatures = new ArrayList<>();
|
||||
|
||||
StringBuilder sql = new StringBuilder(1024);
|
||||
sql.append("SELECT signature FROM Transactions ");
|
||||
|
||||
List<String> whereClauses = new ArrayList<>();
|
||||
List<Object> bindParams = new ArrayList<>();
|
||||
|
||||
if (txType != null) {
|
||||
whereClauses.add("type = ?");
|
||||
bindParams.add(txType.value);
|
||||
}
|
||||
|
||||
if (publicKey != null) {
|
||||
whereClauses.add("creator = ?");
|
||||
bindParams.add(publicKey);
|
||||
}
|
||||
|
||||
switch (confirmationStatus) {
|
||||
case BOTH:
|
||||
break;
|
||||
|
||||
case CONFIRMED:
|
||||
whereClauses.add("Transactions.block_height IS NOT NULL");
|
||||
break;
|
||||
|
||||
case UNCONFIRMED:
|
||||
whereClauses.add("Transactions.block_height IS NULL");
|
||||
break;
|
||||
}
|
||||
|
||||
if (!whereClauses.isEmpty()) {
|
||||
sql.append(" WHERE ");
|
||||
|
||||
final int whereClausesSize = whereClauses.size();
|
||||
for (int wci = 0; wci < whereClausesSize; ++wci) {
|
||||
if (wci != 0)
|
||||
sql.append(" AND ");
|
||||
|
||||
sql.append(whereClauses.get(wci));
|
||||
}
|
||||
}
|
||||
|
||||
sql.append(" ORDER BY Transactions.created_when");
|
||||
sql.append((reverse == null || !reverse) ? " ASC" : " DESC");
|
||||
|
||||
HSQLDBRepository.limitOffsetSql(sql, limit, offset);
|
||||
|
||||
LOGGER.trace(() -> String.format("Transaction search SQL: %s", sql));
|
||||
|
||||
try (ResultSet resultSet = this.repository.checkedExecute(sql.toString(), bindParams.toArray())) {
|
||||
if (resultSet == null)
|
||||
|
130
src/main/java/org/qortal/transaction/PublicizeTransaction.java
Normal file
130
src/main/java/org/qortal/transaction/PublicizeTransaction.java
Normal file
@ -0,0 +1,130 @@
|
||||
package org.qortal.transaction;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.qortal.account.Account;
|
||||
import org.qortal.api.resource.TransactionsResource.ConfirmationStatus;
|
||||
import org.qortal.crypto.MemoryPoW;
|
||||
import org.qortal.data.transaction.PublicizeTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.transform.TransformationException;
|
||||
import org.qortal.transform.transaction.ChatTransactionTransformer;
|
||||
import org.qortal.transform.transaction.PublicizeTransactionTransformer;
|
||||
import org.qortal.transform.transaction.TransactionTransformer;
|
||||
import org.qortal.utils.NTP;
|
||||
|
||||
public class PublicizeTransaction extends Transaction {
|
||||
|
||||
// Properties
|
||||
private PublicizeTransactionData publicizeTransactionData;
|
||||
|
||||
// Other useful constants
|
||||
|
||||
/** If time difference between transaction and now is greater than this then we don't verify proof-of-work. */
|
||||
public static final long HISTORIC_THRESHOLD = 2 * 7 * 24 * 60 * 60 * 1000L;
|
||||
public static final int POW_BUFFER_SIZE = 8 * 1024 * 1024; // bytes
|
||||
public static final int POW_DIFFICULTY = 18; // leading zero bits
|
||||
|
||||
// Constructors
|
||||
|
||||
public PublicizeTransaction(Repository repository, TransactionData transactionData) {
|
||||
super(repository, transactionData);
|
||||
|
||||
this.publicizeTransactionData = (PublicizeTransactionData) this.transactionData;
|
||||
}
|
||||
|
||||
// More information
|
||||
|
||||
@Override
|
||||
public List<String> getRecipientAddresses() throws DataException {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// Navigation
|
||||
|
||||
public Account getSender() {
|
||||
return this.getCreator();
|
||||
}
|
||||
|
||||
// Processing
|
||||
|
||||
public void computeNonce() {
|
||||
byte[] transactionBytes;
|
||||
|
||||
try {
|
||||
transactionBytes = TransactionTransformer.toBytesForSigning(this.transactionData);
|
||||
} catch (TransformationException e) {
|
||||
throw new RuntimeException("Unable to transform transaction to byte array for verification", e);
|
||||
}
|
||||
|
||||
// Clear nonce from transactionBytes
|
||||
PublicizeTransactionTransformer.clearNonce(transactionBytes);
|
||||
|
||||
// Calculate nonce
|
||||
this.publicizeTransactionData.setNonce(MemoryPoW.compute2(transactionBytes, POW_BUFFER_SIZE, POW_DIFFICULTY));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValidationResult isFeeValid() throws DataException {
|
||||
if (this.transactionData.getFee() < 0)
|
||||
return ValidationResult.NEGATIVE_FEE;
|
||||
|
||||
return ValidationResult.OK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValidationResult isValid() throws DataException {
|
||||
// There can be only one
|
||||
List<byte[]> signatures = this.repository.getTransactionRepository().getSignaturesMatchingCriteria(
|
||||
TransactionType.PUBLICIZE,
|
||||
this.transactionData.getCreatorPublicKey(),
|
||||
ConfirmationStatus.CONFIRMED,
|
||||
1, null, null);
|
||||
|
||||
if (!signatures.isEmpty())
|
||||
return ValidationResult.TRANSACTION_ALREADY_EXISTS;
|
||||
|
||||
// We only need to check recent transactions due to PoW verification overhead
|
||||
if (NTP.getTime() - this.transactionData.getTimestamp() < HISTORIC_THRESHOLD)
|
||||
if (!verifyNonce())
|
||||
return ValidationResult.INCORRECT_NONCE;
|
||||
|
||||
return ValidationResult.OK;
|
||||
}
|
||||
|
||||
private boolean verifyNonce() {
|
||||
byte[] transactionBytes;
|
||||
|
||||
try {
|
||||
transactionBytes = TransactionTransformer.toBytesForSigning(this.transactionData);
|
||||
} catch (TransformationException e) {
|
||||
throw new RuntimeException("Unable to transform transaction to byte array for verification", e);
|
||||
}
|
||||
|
||||
int nonce = this.publicizeTransactionData.getNonce();
|
||||
|
||||
// Clear nonce from transactionBytes
|
||||
ChatTransactionTransformer.clearNonce(transactionBytes);
|
||||
|
||||
// Check nonce
|
||||
return MemoryPoW.verify2(transactionBytes, POW_BUFFER_SIZE, POW_DIFFICULTY, nonce);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process() throws DataException {
|
||||
// Save this transaction
|
||||
this.repository.getTransactionRepository().save(this.transactionData);
|
||||
|
||||
// Ensure public key & address are saved
|
||||
this.getSender().ensureAccount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void orphan() throws DataException {
|
||||
/* Don't actually need to do anything */
|
||||
}
|
||||
|
||||
}
|
@ -62,7 +62,7 @@ public abstract class Transaction {
|
||||
DEPLOY_AT(16, true),
|
||||
MESSAGE(17, true),
|
||||
CHAT(18, false),
|
||||
SUPERNODE(19, false),
|
||||
PUBLICIZE(19, false),
|
||||
AIRDROP(20, false),
|
||||
AT(21, false),
|
||||
CREATE_GROUP(22, true),
|
||||
@ -242,6 +242,7 @@ public abstract class Transaction {
|
||||
SELF_SHARE_EXISTS(91),
|
||||
ACCOUNT_ALREADY_EXISTS(92),
|
||||
INVALID_GROUP_BLOCK_DELAY(93),
|
||||
INCORRECT_NONCE(94),
|
||||
CHAT(999),
|
||||
NOT_YET_RELEASED(1000);
|
||||
|
||||
|
@ -0,0 +1,94 @@
|
||||
package org.qortal.transform.transaction;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.qortal.data.transaction.BaseTransactionData;
|
||||
import org.qortal.data.transaction.PublicizeTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.transaction.Transaction.TransactionType;
|
||||
import org.qortal.transform.TransformationException;
|
||||
import org.qortal.utils.Serialization;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.google.common.primitives.Longs;
|
||||
|
||||
public class PublicizeTransactionTransformer extends TransactionTransformer {
|
||||
|
||||
// Property lengths
|
||||
private static final int NONCE_LENGTH = INT_LENGTH;
|
||||
|
||||
private static final int EXTRAS_LENGTH = NONCE_LENGTH;
|
||||
|
||||
protected static final TransactionLayout layout;
|
||||
|
||||
static {
|
||||
layout = new TransactionLayout();
|
||||
layout.add("txType: " + TransactionType.CHAT.valueString, TransformationType.INT);
|
||||
layout.add("timestamp", TransformationType.TIMESTAMP);
|
||||
layout.add("transaction's groupID", TransformationType.INT);
|
||||
layout.add("reference", TransformationType.SIGNATURE);
|
||||
layout.add("sender's public key", TransformationType.PUBLIC_KEY);
|
||||
layout.add("proof-of-work nonce", TransformationType.INT);
|
||||
layout.add("fee", TransformationType.AMOUNT);
|
||||
layout.add("signature", TransformationType.SIGNATURE);
|
||||
}
|
||||
|
||||
public static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
|
||||
long timestamp = byteBuffer.getLong();
|
||||
|
||||
int txGroupId = byteBuffer.getInt();
|
||||
|
||||
byte[] reference = new byte[REFERENCE_LENGTH];
|
||||
byteBuffer.get(reference);
|
||||
|
||||
byte[] senderPublicKey = Serialization.deserializePublicKey(byteBuffer);
|
||||
|
||||
int nonce = byteBuffer.getInt();
|
||||
|
||||
long fee = byteBuffer.getLong();
|
||||
|
||||
byte[] signature = new byte[SIGNATURE_LENGTH];
|
||||
byteBuffer.get(signature);
|
||||
|
||||
BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, senderPublicKey, fee, signature);
|
||||
|
||||
return new PublicizeTransactionData(baseTransactionData, nonce);
|
||||
}
|
||||
|
||||
public static int getDataLength(TransactionData transactionData) {
|
||||
return getBaseLength(transactionData) + EXTRAS_LENGTH;
|
||||
}
|
||||
|
||||
public static byte[] toBytes(TransactionData transactionData) throws TransformationException {
|
||||
try {
|
||||
PublicizeTransactionData publicizeTransactionData = (PublicizeTransactionData) transactionData;
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
transformCommonBytes(transactionData, bytes);
|
||||
|
||||
bytes.write(Ints.toByteArray(publicizeTransactionData.getNonce()));
|
||||
|
||||
bytes.write(Longs.toByteArray(publicizeTransactionData.getFee()));
|
||||
|
||||
if (publicizeTransactionData.getSignature() != null)
|
||||
bytes.write(publicizeTransactionData.getSignature());
|
||||
|
||||
return bytes.toByteArray();
|
||||
} catch (IOException | ClassCastException e) {
|
||||
throw new TransformationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void clearNonce(byte[] transactionBytes) {
|
||||
int nonceIndex = TYPE_LENGTH + TIMESTAMP_LENGTH + GROUPID_LENGTH + REFERENCE_LENGTH + PUBLIC_KEY_LENGTH;
|
||||
|
||||
transactionBytes[nonceIndex++] = (byte) 0;
|
||||
transactionBytes[nonceIndex++] = (byte) 0;
|
||||
transactionBytes[nonceIndex++] = (byte) 0;
|
||||
transactionBytes[nonceIndex++] = (byte) 0;
|
||||
}
|
||||
|
||||
}
|
@ -48,7 +48,7 @@ public class SerializationTests extends Common {
|
||||
case ACCOUNT_FLAGS:
|
||||
case AT:
|
||||
case CHAT:
|
||||
case SUPERNODE:
|
||||
case PUBLICIZE:
|
||||
case AIRDROP:
|
||||
case ENABLE_FORGING:
|
||||
continue;
|
||||
|
Loading…
Reference in New Issue
Block a user