forked from Qortal/qortal
Refactor to move arbitrary transaction building to its own class.
This commit is contained in:
parent
305e0f1772
commit
6c01955561
@ -27,6 +27,7 @@ import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.qortal.api.*;
|
||||
import org.qortal.api.resource.TransactionsResource.ConfirmationStatus;
|
||||
import org.qortal.arbitrary.ArbitraryDataTransactionBuilder;
|
||||
import org.qortal.block.BlockChain;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.data.PaymentData;
|
||||
@ -255,7 +256,10 @@ public class ArbitraryResource {
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.REPOSITORY_ISSUE})
|
||||
public String uploadFileAtPath(@PathParam("publickey") String creatorPublicKeyBase58, String path) {
|
||||
public String uploadFileAtPath(@PathParam("method") String methodString,
|
||||
@PathParam("publickey") String publicKey58,
|
||||
@PathParam("name") String name,
|
||||
String path) {
|
||||
Security.checkApiCallAllowed(request);
|
||||
|
||||
// It's too dangerous to allow user-supplied filenames in weaker security contexts
|
||||
@ -263,84 +267,16 @@ public class ArbitraryResource {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||
}
|
||||
|
||||
ArbitraryDataFile arbitraryDataFile = null;
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
try {
|
||||
ArbitraryDataTransactionBuilder transactionBuilder = new ArbitraryDataTransactionBuilder(
|
||||
publicKey58, Paths.get(path), name, Method.valueOf(methodString), Service.ARBITRARY_DATA
|
||||
);
|
||||
|
||||
if (creatorPublicKeyBase58 == null || path == null) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||
}
|
||||
byte[] creatorPublicKey = Base58.decode(creatorPublicKeyBase58);
|
||||
final String creatorAddress = Crypto.toAddress(creatorPublicKey);
|
||||
byte[] lastReference = repository.getAccountRepository().getLastReference(creatorAddress);
|
||||
if (lastReference == null) {
|
||||
// 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;
|
||||
byte[] secret = null;
|
||||
Method method = Method.PUT;
|
||||
Service service = Service.ARBITRARY_DATA;
|
||||
Compression compression = Compression.NONE;
|
||||
|
||||
ArbitraryDataWriter arbitraryDataWriter = new ArbitraryDataWriter(Paths.get(path), name, service, method, compression);
|
||||
try {
|
||||
arbitraryDataWriter.save();
|
||||
} catch (IOException | DataException | InterruptedException e) {
|
||||
LOGGER.info("Unable to create arbitrary data file: {}", e.getMessage());
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE);
|
||||
} catch (RuntimeException e) {
|
||||
LOGGER.info("Unable to create arbitrary data file: {}", e.getMessage());
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||
}
|
||||
|
||||
String digest58 = arbitraryDataFile.digest58();
|
||||
if (digest58 == null) {
|
||||
LOGGER.error("Unable to calculate digest");
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||
}
|
||||
|
||||
final BaseTransactionData baseTransactionData = new BaseTransactionData(NTP.getTime(), Group.NO_GROUP,
|
||||
lastReference, creatorPublicKey, BlockChain.getInstance().getUnitFee(), null);
|
||||
final int size = (int) arbitraryDataFile.size();
|
||||
final int version = 5;
|
||||
final int nonce = 0;
|
||||
final ArbitraryTransactionData.DataType dataType = ArbitraryTransactionData.DataType.DATA_HASH;
|
||||
final byte[] digest = arbitraryDataFile.digest();
|
||||
final byte[] chunkHashes = arbitraryDataFile.chunkHashes();
|
||||
final List<PaymentData> payments = new ArrayList<>();
|
||||
|
||||
ArbitraryTransactionData transactionData = new ArbitraryTransactionData(baseTransactionData,
|
||||
version, service, nonce, size, name, method,
|
||||
secret, compression, digest, dataType, chunkHashes, payments);
|
||||
|
||||
ArbitraryTransaction transaction = (ArbitraryTransaction) Transaction.fromData(repository, transactionData);
|
||||
transaction.computeNonce();
|
||||
|
||||
Transaction.ValidationResult result = transaction.isValidUnconfirmed();
|
||||
if (result != Transaction.ValidationResult.OK) {
|
||||
arbitraryDataFile.deleteAll();
|
||||
throw TransactionsResource.createTransactionInvalidException(request, result);
|
||||
}
|
||||
|
||||
byte[] bytes = ArbitraryTransactionTransformer.toBytes(transactionData);
|
||||
byte[] bytes = transactionBuilder.build();
|
||||
return Base58.encode(bytes);
|
||||
|
||||
} catch (DataException e) {
|
||||
arbitraryDataFile.deleteAll();
|
||||
LOGGER.error("Repository issue when uploading data", e);
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||
} catch (TransformationException e) {
|
||||
arbitraryDataFile.deleteAll();
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSFORMATION_ERROR, e);
|
||||
} catch (IllegalStateException e) {
|
||||
arbitraryDataFile.deleteAll();
|
||||
LOGGER.error("Invalid upload data", e);
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA, e);
|
||||
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_DATA, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,7 @@ import org.qortal.api.ApiError;
|
||||
import org.qortal.api.ApiExceptionFactory;
|
||||
import org.qortal.api.HTMLParser;
|
||||
import org.qortal.api.Security;
|
||||
import org.qortal.arbitrary.ArbitraryDataTransactionBuilder;
|
||||
import org.qortal.block.BlockChain;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.data.PaymentData;
|
||||
@ -91,92 +92,27 @@ public class WebsiteResource {
|
||||
)
|
||||
}
|
||||
)
|
||||
public String uploadWebsite(@PathParam("method") String methodString, @PathParam("publickey") String publicKey58, @PathParam("name") String name, String path) {
|
||||
public String uploadWebsite(@PathParam("method") String methodString,
|
||||
@PathParam("publickey") String publicKey58,
|
||||
@PathParam("name") String name,
|
||||
String path) {
|
||||
Security.checkApiCallAllowed(request);
|
||||
|
||||
// It's too dangerous to allow user-supplied filenames in weaker security contexts
|
||||
// It's too dangerous to allow user-supplied file paths in weaker security contexts
|
||||
if (Settings.getInstance().isApiRestricted()) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||
}
|
||||
|
||||
ArbitraryDataFile arbitraryDataFile = null;
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
try {
|
||||
ArbitraryDataTransactionBuilder transactionBuilder = new ArbitraryDataTransactionBuilder(
|
||||
publicKey58, Paths.get(path), name, Method.valueOf(methodString), Service.WEBSITE
|
||||
);
|
||||
|
||||
if (publicKey58 == null || path == null) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||
}
|
||||
byte[] creatorPublicKey = Base58.decode(publicKey58);
|
||||
final String creatorAddress = Crypto.toAddress(creatorPublicKey);
|
||||
byte[] lastReference = repository.getAccountRepository().getLastReference(creatorAddress);
|
||||
if (lastReference == null) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
ArbitraryTransactionData.Method method = ArbitraryTransactionData.Method.valueOf(methodString);
|
||||
ArbitraryTransactionData.Service service = ArbitraryTransactionData.Service.WEBSITE;
|
||||
ArbitraryTransactionData.Compression compression = ArbitraryTransactionData.Compression.ZIP;
|
||||
|
||||
ArbitraryDataWriter arbitraryDataWriter = new ArbitraryDataWriter(Paths.get(path), name, service, method, compression);
|
||||
try {
|
||||
arbitraryDataWriter.save();
|
||||
} catch (IOException | DataException | InterruptedException e) {
|
||||
LOGGER.info("Unable to create arbitrary data file: {}", e.getMessage());
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE);
|
||||
} catch (RuntimeException e) {
|
||||
LOGGER.info("Unable to create arbitrary data file: {}", e.getMessage());
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||
}
|
||||
|
||||
arbitraryDataFile = arbitraryDataWriter.getArbitraryDataFile();
|
||||
if (arbitraryDataFile == null) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||
}
|
||||
|
||||
String digest58 = arbitraryDataFile.digest58();
|
||||
if (digest58 == null) {
|
||||
LOGGER.error("Unable to calculate digest");
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||
}
|
||||
|
||||
final BaseTransactionData baseTransactionData = new BaseTransactionData(NTP.getTime(), Group.NO_GROUP,
|
||||
lastReference, creatorPublicKey, BlockChain.getInstance().getUnitFee(), null);
|
||||
final int size = (int) arbitraryDataFile.size();
|
||||
final int version = 5;
|
||||
final int nonce = 0;
|
||||
byte[] secret = arbitraryDataFile.getSecret();
|
||||
final ArbitraryTransactionData.DataType dataType = ArbitraryTransactionData.DataType.DATA_HASH;
|
||||
final byte[] digest = arbitraryDataFile.digest();
|
||||
final byte[] chunkHashes = arbitraryDataFile.chunkHashes();
|
||||
final List<PaymentData> payments = new ArrayList<>();
|
||||
|
||||
ArbitraryTransactionData transactionData = new ArbitraryTransactionData(baseTransactionData,
|
||||
version, service, nonce, size, name, method,
|
||||
secret, compression, digest, dataType, chunkHashes, payments);
|
||||
|
||||
ArbitraryTransaction transaction = (ArbitraryTransaction) Transaction.fromData(repository, transactionData);
|
||||
LOGGER.info("Computing nonce...");
|
||||
transaction.computeNonce();
|
||||
|
||||
Transaction.ValidationResult result = transaction.isValidUnconfirmed();
|
||||
if (result != Transaction.ValidationResult.OK) {
|
||||
arbitraryDataFile.deleteAll();
|
||||
throw TransactionsResource.createTransactionInvalidException(request, result);
|
||||
}
|
||||
|
||||
byte[] bytes = ArbitraryTransactionTransformer.toBytes(transactionData);
|
||||
byte[] bytes = transactionBuilder.build();
|
||||
return Base58.encode(bytes);
|
||||
|
||||
} catch (TransformationException e) {
|
||||
arbitraryDataFile.deleteAll();
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSFORMATION_ERROR, e);
|
||||
} catch (DataException e) {
|
||||
arbitraryDataFile.deleteAll();
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_DATA, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,121 @@
|
||||
package org.qortal.arbitrary;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.qortal.block.BlockChain;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.data.PaymentData;
|
||||
import org.qortal.data.transaction.ArbitraryTransactionData;
|
||||
import org.qortal.data.transaction.ArbitraryTransactionData.*;
|
||||
import org.qortal.data.transaction.BaseTransactionData;
|
||||
import org.qortal.group.Group;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
public class ArbitraryDataTransactionBuilder {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(ArbitraryDataTransactionBuilder.class);
|
||||
|
||||
private String publicKey58;
|
||||
private Path path;
|
||||
private String name;
|
||||
private Method method;
|
||||
private Service service;
|
||||
|
||||
public ArbitraryDataTransactionBuilder(String publicKey58, Path path, String name, Method method, Service service) {
|
||||
this.publicKey58 = publicKey58;
|
||||
this.path = path;
|
||||
this.name = name;
|
||||
this.method = method;
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
public byte[] build() throws DataException {
|
||||
ArbitraryDataFile arbitraryDataFile = null;
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
if (publicKey58 == null || path == null) {
|
||||
throw new DataException("Missing public key or path");
|
||||
}
|
||||
byte[] creatorPublicKey = Base58.decode(publicKey58);
|
||||
final String creatorAddress = Crypto.toAddress(creatorPublicKey);
|
||||
byte[] lastReference = repository.getAccountRepository().getLastReference(creatorAddress);
|
||||
if (lastReference == null) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
ArbitraryTransactionData.Compression compression = ArbitraryTransactionData.Compression.ZIP;
|
||||
|
||||
ArbitraryDataWriter arbitraryDataWriter = new ArbitraryDataWriter(path, name, service, method, compression);
|
||||
try {
|
||||
arbitraryDataWriter.save();
|
||||
} catch (IOException | DataException | InterruptedException | RuntimeException e) {
|
||||
LOGGER.info("Unable to create arbitrary data file: {}", e.getMessage());
|
||||
throw new DataException(String.format("Unable to create arbitrary data file: %s", e.getMessage()));
|
||||
}
|
||||
|
||||
arbitraryDataFile = arbitraryDataWriter.getArbitraryDataFile();
|
||||
if (arbitraryDataFile == null) {
|
||||
throw new DataException("Arbitrary data file is null");
|
||||
}
|
||||
|
||||
String digest58 = arbitraryDataFile.digest58();
|
||||
if (digest58 == null) {
|
||||
LOGGER.error("Unable to calculate file digest");
|
||||
throw new DataException("Unable to calculate file digest");
|
||||
}
|
||||
|
||||
final BaseTransactionData baseTransactionData = new BaseTransactionData(NTP.getTime(), Group.NO_GROUP,
|
||||
lastReference, creatorPublicKey, BlockChain.getInstance().getUnitFee(), null);
|
||||
final int size = (int) arbitraryDataFile.size();
|
||||
final int version = 5;
|
||||
final int nonce = 0;
|
||||
byte[] secret = arbitraryDataFile.getSecret();
|
||||
final ArbitraryTransactionData.DataType dataType = ArbitraryTransactionData.DataType.DATA_HASH;
|
||||
final byte[] digest = arbitraryDataFile.digest();
|
||||
final byte[] chunkHashes = arbitraryDataFile.chunkHashes();
|
||||
final List<PaymentData> payments = new ArrayList<>();
|
||||
|
||||
ArbitraryTransactionData transactionData = new ArbitraryTransactionData(baseTransactionData,
|
||||
version, service, nonce, size, name, method,
|
||||
secret, compression, digest, dataType, chunkHashes, payments);
|
||||
|
||||
ArbitraryTransaction transaction = (ArbitraryTransaction) Transaction.fromData(repository, transactionData);
|
||||
LOGGER.info("Computing nonce...");
|
||||
transaction.computeNonce();
|
||||
|
||||
Transaction.ValidationResult result = transaction.isValidUnconfirmed();
|
||||
if (result != Transaction.ValidationResult.OK) {
|
||||
arbitraryDataFile.deleteAll();
|
||||
throw new DataException(String.format("Arbitrary transaction invalid: %s", result));
|
||||
}
|
||||
|
||||
return ArbitraryTransactionTransformer.toBytes(transactionData);
|
||||
|
||||
} catch (TransformationException | DataException e) {
|
||||
arbitraryDataFile.deleteAll();
|
||||
throw new DataException(String.format("Unable to build ARBITRARY transaction: %s", e.getMessage()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user