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.apache.logging.log4j.Logger;
|
||||||
import org.qortal.api.*;
|
import org.qortal.api.*;
|
||||||
import org.qortal.api.resource.TransactionsResource.ConfirmationStatus;
|
import org.qortal.api.resource.TransactionsResource.ConfirmationStatus;
|
||||||
|
import org.qortal.arbitrary.ArbitraryDataTransactionBuilder;
|
||||||
import org.qortal.block.BlockChain;
|
import org.qortal.block.BlockChain;
|
||||||
import org.qortal.crypto.Crypto;
|
import org.qortal.crypto.Crypto;
|
||||||
import org.qortal.data.PaymentData;
|
import org.qortal.data.PaymentData;
|
||||||
@ -255,7 +256,10 @@ public class ArbitraryResource {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiErrors({ApiError.REPOSITORY_ISSUE})
|
@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);
|
Security.checkApiCallAllowed(request);
|
||||||
|
|
||||||
// It's too dangerous to allow user-supplied filenames in weaker security contexts
|
// 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);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
}
|
}
|
||||||
|
|
||||||
ArbitraryDataFile arbitraryDataFile = null;
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
|
|
||||||
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 {
|
try {
|
||||||
arbitraryDataWriter.save();
|
ArbitraryDataTransactionBuilder transactionBuilder = new ArbitraryDataTransactionBuilder(
|
||||||
} catch (IOException | DataException | InterruptedException e) {
|
publicKey58, Paths.get(path), name, Method.valueOf(methodString), Service.ARBITRARY_DATA
|
||||||
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();
|
byte[] bytes = transactionBuilder.build();
|
||||||
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);
|
|
||||||
return Base58.encode(bytes);
|
return Base58.encode(bytes);
|
||||||
|
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
arbitraryDataFile.deleteAll();
|
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_DATA, e.getMessage());
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@ import org.qortal.api.ApiError;
|
|||||||
import org.qortal.api.ApiExceptionFactory;
|
import org.qortal.api.ApiExceptionFactory;
|
||||||
import org.qortal.api.HTMLParser;
|
import org.qortal.api.HTMLParser;
|
||||||
import org.qortal.api.Security;
|
import org.qortal.api.Security;
|
||||||
|
import org.qortal.arbitrary.ArbitraryDataTransactionBuilder;
|
||||||
import org.qortal.block.BlockChain;
|
import org.qortal.block.BlockChain;
|
||||||
import org.qortal.crypto.Crypto;
|
import org.qortal.crypto.Crypto;
|
||||||
import org.qortal.data.PaymentData;
|
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);
|
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()) {
|
if (Settings.getInstance().isApiRestricted()) {
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
|
||||||
}
|
}
|
||||||
|
|
||||||
ArbitraryDataFile arbitraryDataFile = null;
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
||||||
|
|
||||||
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 {
|
try {
|
||||||
arbitraryDataWriter.save();
|
ArbitraryDataTransactionBuilder transactionBuilder = new ArbitraryDataTransactionBuilder(
|
||||||
} catch (IOException | DataException | InterruptedException e) {
|
publicKey58, Paths.get(path), name, Method.valueOf(methodString), Service.WEBSITE
|
||||||
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();
|
byte[] bytes = transactionBuilder.build();
|
||||||
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);
|
|
||||||
return Base58.encode(bytes);
|
return Base58.encode(bytes);
|
||||||
|
|
||||||
} catch (TransformationException e) {
|
|
||||||
arbitraryDataFile.deleteAll();
|
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSFORMATION_ERROR, e);
|
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
arbitraryDataFile.deleteAll();
|
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_DATA, e.getMessage());
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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