mirror of
https://github.com/Qortal/qortal.git
synced 2025-04-19 01:25:54 +00:00
Some payments don't always initialize the recipient's last reference, depending on whether the asset is QORA or not and what type of transaction is being processed. Calls to Payment.process/orphan now take boolean to indicate whether recipient's last reference should always be initialized regardless of asset. ("initialized" here means setting an initial last reference for an account if the current value is null/empty/missing) When matching orders to produce trades, it isn't enough to only sort orders with best price first. Orders with the same price also need to be further sorted by earliest order first. (This was implicitly done in Qora v1). Added additional ORDER BY sub-clause to achieve this and improved the corresponding table index too. Converted a lot of logging from simplistic System.out/err.println to Apache log4j2. Added several "trace"-level logging statements to aid debugging which can be activated given appropriate configuration in log4j2.properties. With voting, detection of previous votes was broken during orphan as previousOptionIndex was set to 0 instead of null when fetching a VoteOnPollTransaction from the HSQLDB repository. This was due to lack of null-checking - now fixed. Corresponding changes made in IssueAsset and Message transaction HSQLDB repository classes. v1feeder now syncs up to block 99055. Block 99056 contains DeployAT. Orphaning back to block 1 and then resync works without issue too.
373 lines
12 KiB
Java
373 lines
12 KiB
Java
package transform.transaction;
|
|
|
|
import java.nio.BufferUnderflowException;
|
|
import java.nio.ByteBuffer;
|
|
import java.util.Arrays;
|
|
|
|
import org.apache.logging.log4j.LogManager;
|
|
import org.apache.logging.log4j.Logger;
|
|
import org.json.simple.JSONObject;
|
|
|
|
import com.google.common.hash.HashCode;
|
|
|
|
import data.transaction.TransactionData;
|
|
import qora.account.PrivateKeyAccount;
|
|
import qora.transaction.Transaction;
|
|
import qora.transaction.Transaction.TransactionType;
|
|
import transform.TransformationException;
|
|
import transform.Transformer;
|
|
import utils.Base58;
|
|
|
|
public class TransactionTransformer extends Transformer {
|
|
|
|
private static final Logger LOGGER = LogManager.getLogger(TransactionTransformer.class);
|
|
|
|
protected static final int TYPE_LENGTH = INT_LENGTH;
|
|
protected static final int REFERENCE_LENGTH = SIGNATURE_LENGTH;
|
|
protected static final int FEE_LENGTH = BIG_DECIMAL_LENGTH;
|
|
protected static final int BASE_TYPELESS_LENGTH = TIMESTAMP_LENGTH + REFERENCE_LENGTH + FEE_LENGTH + SIGNATURE_LENGTH;
|
|
|
|
public static TransactionData fromBytes(byte[] bytes) throws TransformationException {
|
|
if (bytes == null)
|
|
return null;
|
|
|
|
if (bytes.length < TYPE_LENGTH)
|
|
throw new TransformationException("Byte data too short to determine transaction type");
|
|
|
|
LOGGER.trace("tx hex: " + HashCode.fromBytes(bytes).toString());
|
|
|
|
ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
|
|
|
|
TransactionType type = TransactionType.valueOf(byteBuffer.getInt());
|
|
if (type == null)
|
|
return null;
|
|
|
|
try {
|
|
switch (type) {
|
|
case GENESIS:
|
|
return GenesisTransactionTransformer.fromByteBuffer(byteBuffer);
|
|
|
|
case PAYMENT:
|
|
return PaymentTransactionTransformer.fromByteBuffer(byteBuffer);
|
|
|
|
case REGISTER_NAME:
|
|
return RegisterNameTransactionTransformer.fromByteBuffer(byteBuffer);
|
|
|
|
case UPDATE_NAME:
|
|
return UpdateNameTransactionTransformer.fromByteBuffer(byteBuffer);
|
|
|
|
case SELL_NAME:
|
|
return SellNameTransactionTransformer.fromByteBuffer(byteBuffer);
|
|
|
|
case CANCEL_SELL_NAME:
|
|
return CancelSellNameTransactionTransformer.fromByteBuffer(byteBuffer);
|
|
|
|
case BUY_NAME:
|
|
return BuyNameTransactionTransformer.fromByteBuffer(byteBuffer);
|
|
|
|
case CREATE_POLL:
|
|
return CreatePollTransactionTransformer.fromByteBuffer(byteBuffer);
|
|
|
|
case VOTE_ON_POLL:
|
|
return VoteOnPollTransactionTransformer.fromByteBuffer(byteBuffer);
|
|
|
|
case ARBITRARY:
|
|
return ArbitraryTransactionTransformer.fromByteBuffer(byteBuffer);
|
|
|
|
case ISSUE_ASSET:
|
|
return IssueAssetTransactionTransformer.fromByteBuffer(byteBuffer);
|
|
|
|
case TRANSFER_ASSET:
|
|
return TransferAssetTransactionTransformer.fromByteBuffer(byteBuffer);
|
|
|
|
case CREATE_ASSET_ORDER:
|
|
return CreateOrderTransactionTransformer.fromByteBuffer(byteBuffer);
|
|
|
|
case CANCEL_ASSET_ORDER:
|
|
return CancelOrderTransactionTransformer.fromByteBuffer(byteBuffer);
|
|
|
|
case MULTIPAYMENT:
|
|
return MultiPaymentTransactionTransformer.fromByteBuffer(byteBuffer);
|
|
|
|
case MESSAGE:
|
|
return MessageTransactionTransformer.fromByteBuffer(byteBuffer);
|
|
|
|
default:
|
|
throw new TransformationException("Unsupported transaction type [" + type.value + "] during conversion from bytes");
|
|
}
|
|
} catch (BufferUnderflowException e) {
|
|
throw new TransformationException("Byte data too short for transaction type [" + type.value + "]");
|
|
}
|
|
}
|
|
|
|
public static int getDataLength(TransactionData transactionData) throws TransformationException {
|
|
switch (transactionData.getType()) {
|
|
case GENESIS:
|
|
return GenesisTransactionTransformer.getDataLength(transactionData);
|
|
|
|
case PAYMENT:
|
|
return PaymentTransactionTransformer.getDataLength(transactionData);
|
|
|
|
case REGISTER_NAME:
|
|
return RegisterNameTransactionTransformer.getDataLength(transactionData);
|
|
|
|
case UPDATE_NAME:
|
|
return UpdateNameTransactionTransformer.getDataLength(transactionData);
|
|
|
|
case SELL_NAME:
|
|
return SellNameTransactionTransformer.getDataLength(transactionData);
|
|
|
|
case CANCEL_SELL_NAME:
|
|
return CancelSellNameTransactionTransformer.getDataLength(transactionData);
|
|
|
|
case BUY_NAME:
|
|
return BuyNameTransactionTransformer.getDataLength(transactionData);
|
|
|
|
case CREATE_POLL:
|
|
return CreatePollTransactionTransformer.getDataLength(transactionData);
|
|
|
|
case VOTE_ON_POLL:
|
|
return VoteOnPollTransactionTransformer.getDataLength(transactionData);
|
|
|
|
case ARBITRARY:
|
|
return ArbitraryTransactionTransformer.getDataLength(transactionData);
|
|
|
|
case ISSUE_ASSET:
|
|
return IssueAssetTransactionTransformer.getDataLength(transactionData);
|
|
|
|
case TRANSFER_ASSET:
|
|
return TransferAssetTransactionTransformer.getDataLength(transactionData);
|
|
|
|
case CREATE_ASSET_ORDER:
|
|
return CreateOrderTransactionTransformer.getDataLength(transactionData);
|
|
|
|
case CANCEL_ASSET_ORDER:
|
|
return CancelOrderTransactionTransformer.getDataLength(transactionData);
|
|
|
|
case MULTIPAYMENT:
|
|
return MultiPaymentTransactionTransformer.getDataLength(transactionData);
|
|
|
|
case MESSAGE:
|
|
return MessageTransactionTransformer.getDataLength(transactionData);
|
|
|
|
default:
|
|
throw new TransformationException("Unsupported transaction type [" + transactionData.getType().value + "] when requesting byte length");
|
|
}
|
|
}
|
|
|
|
public static byte[] toBytes(TransactionData transactionData) throws TransformationException {
|
|
switch (transactionData.getType()) {
|
|
case GENESIS:
|
|
return GenesisTransactionTransformer.toBytes(transactionData);
|
|
|
|
case PAYMENT:
|
|
return PaymentTransactionTransformer.toBytes(transactionData);
|
|
|
|
case REGISTER_NAME:
|
|
return RegisterNameTransactionTransformer.toBytes(transactionData);
|
|
|
|
case UPDATE_NAME:
|
|
return UpdateNameTransactionTransformer.toBytes(transactionData);
|
|
|
|
case SELL_NAME:
|
|
return SellNameTransactionTransformer.toBytes(transactionData);
|
|
|
|
case CANCEL_SELL_NAME:
|
|
return CancelSellNameTransactionTransformer.toBytes(transactionData);
|
|
|
|
case BUY_NAME:
|
|
return BuyNameTransactionTransformer.toBytes(transactionData);
|
|
|
|
case CREATE_POLL:
|
|
return CreatePollTransactionTransformer.toBytes(transactionData);
|
|
|
|
case VOTE_ON_POLL:
|
|
return VoteOnPollTransactionTransformer.toBytes(transactionData);
|
|
|
|
case ARBITRARY:
|
|
return ArbitraryTransactionTransformer.toBytes(transactionData);
|
|
|
|
case ISSUE_ASSET:
|
|
return IssueAssetTransactionTransformer.toBytes(transactionData);
|
|
|
|
case TRANSFER_ASSET:
|
|
return TransferAssetTransactionTransformer.toBytes(transactionData);
|
|
|
|
case CREATE_ASSET_ORDER:
|
|
return CreateOrderTransactionTransformer.toBytes(transactionData);
|
|
|
|
case CANCEL_ASSET_ORDER:
|
|
return CancelOrderTransactionTransformer.toBytes(transactionData);
|
|
|
|
case MULTIPAYMENT:
|
|
return MultiPaymentTransactionTransformer.toBytes(transactionData);
|
|
|
|
case MESSAGE:
|
|
return MessageTransactionTransformer.toBytes(transactionData);
|
|
|
|
default:
|
|
throw new TransformationException("Unsupported transaction type [" + transactionData.getType().value + "] during conversion to bytes");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Serialize transaction as byte[], stripping off trailing signature ready for signing/verification.
|
|
* <p>
|
|
* Used by signature-related methods such as {@link Transaction#sign(PrivateKeyAccount)} and {@link Transaction#isSignatureValid()}
|
|
*
|
|
* @param transactionData
|
|
* @return byte[] of transaction, without trailing signature
|
|
* @throws TransformationException
|
|
*/
|
|
public static byte[] toBytesForSigning(TransactionData transactionData) throws TransformationException {
|
|
switch (transactionData.getType()) {
|
|
case GENESIS:
|
|
return GenesisTransactionTransformer.toBytesForSigningImpl(transactionData);
|
|
|
|
case PAYMENT:
|
|
return PaymentTransactionTransformer.toBytesForSigningImpl(transactionData);
|
|
|
|
case REGISTER_NAME:
|
|
return RegisterNameTransactionTransformer.toBytesForSigningImpl(transactionData);
|
|
|
|
case UPDATE_NAME:
|
|
return UpdateNameTransactionTransformer.toBytesForSigningImpl(transactionData);
|
|
|
|
case SELL_NAME:
|
|
return SellNameTransactionTransformer.toBytesForSigningImpl(transactionData);
|
|
|
|
case CANCEL_SELL_NAME:
|
|
return CancelSellNameTransactionTransformer.toBytesForSigningImpl(transactionData);
|
|
|
|
case BUY_NAME:
|
|
return BuyNameTransactionTransformer.toBytesForSigningImpl(transactionData);
|
|
|
|
case CREATE_POLL:
|
|
return CreatePollTransactionTransformer.toBytesForSigningImpl(transactionData);
|
|
|
|
case VOTE_ON_POLL:
|
|
return VoteOnPollTransactionTransformer.toBytesForSigningImpl(transactionData);
|
|
|
|
case ARBITRARY:
|
|
return ArbitraryTransactionTransformer.toBytesForSigningImpl(transactionData);
|
|
|
|
case ISSUE_ASSET:
|
|
return IssueAssetTransactionTransformer.toBytesForSigningImpl(transactionData);
|
|
|
|
case TRANSFER_ASSET:
|
|
return TransferAssetTransactionTransformer.toBytesForSigningImpl(transactionData);
|
|
|
|
case CREATE_ASSET_ORDER:
|
|
return CreateOrderTransactionTransformer.toBytesForSigningImpl(transactionData);
|
|
|
|
case CANCEL_ASSET_ORDER:
|
|
return CancelOrderTransactionTransformer.toBytesForSigningImpl(transactionData);
|
|
|
|
case MULTIPAYMENT:
|
|
return MultiPaymentTransactionTransformer.toBytesForSigningImpl(transactionData);
|
|
|
|
case MESSAGE:
|
|
return MessageTransactionTransformer.toBytesForSigningImpl(transactionData);
|
|
|
|
default:
|
|
throw new TransformationException(
|
|
"Unsupported transaction type [" + transactionData.getType().value + "] during conversion to bytes for signing");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generic serialization of transaction as byte[], stripping off trailing signature ready for signing/verification.
|
|
*
|
|
* @param transactionData
|
|
* @return byte[] of transaction, without trailing signature
|
|
* @throws TransformationException
|
|
*/
|
|
protected static byte[] toBytesForSigningImpl(TransactionData transactionData) throws TransformationException {
|
|
try {
|
|
byte[] bytes = TransactionTransformer.toBytes(transactionData);
|
|
|
|
if (transactionData.getSignature() == null)
|
|
return bytes;
|
|
|
|
return Arrays.copyOf(bytes, bytes.length - Transformer.SIGNATURE_LENGTH);
|
|
} catch (TransformationException e) {
|
|
throw new RuntimeException("Unable to transform transaction to byte array for signing", e);
|
|
}
|
|
}
|
|
|
|
public static JSONObject toJSON(TransactionData transactionData) throws TransformationException {
|
|
switch (transactionData.getType()) {
|
|
case GENESIS:
|
|
return GenesisTransactionTransformer.toJSON(transactionData);
|
|
|
|
case PAYMENT:
|
|
return PaymentTransactionTransformer.toJSON(transactionData);
|
|
|
|
case REGISTER_NAME:
|
|
return RegisterNameTransactionTransformer.toJSON(transactionData);
|
|
|
|
case UPDATE_NAME:
|
|
return UpdateNameTransactionTransformer.toJSON(transactionData);
|
|
|
|
case SELL_NAME:
|
|
return SellNameTransactionTransformer.toJSON(transactionData);
|
|
|
|
case CANCEL_SELL_NAME:
|
|
return CancelSellNameTransactionTransformer.toJSON(transactionData);
|
|
|
|
case BUY_NAME:
|
|
return BuyNameTransactionTransformer.toJSON(transactionData);
|
|
|
|
case CREATE_POLL:
|
|
return CreatePollTransactionTransformer.toJSON(transactionData);
|
|
|
|
case VOTE_ON_POLL:
|
|
return VoteOnPollTransactionTransformer.toJSON(transactionData);
|
|
|
|
case ARBITRARY:
|
|
return ArbitraryTransactionTransformer.toJSON(transactionData);
|
|
|
|
case ISSUE_ASSET:
|
|
return IssueAssetTransactionTransformer.toJSON(transactionData);
|
|
|
|
case TRANSFER_ASSET:
|
|
return TransferAssetTransactionTransformer.toJSON(transactionData);
|
|
|
|
case CREATE_ASSET_ORDER:
|
|
return CreateOrderTransactionTransformer.toJSON(transactionData);
|
|
|
|
case CANCEL_ASSET_ORDER:
|
|
return CancelOrderTransactionTransformer.toJSON(transactionData);
|
|
|
|
case MULTIPAYMENT:
|
|
return MultiPaymentTransactionTransformer.toJSON(transactionData);
|
|
|
|
case MESSAGE:
|
|
return MessageTransactionTransformer.toJSON(transactionData);
|
|
|
|
default:
|
|
throw new TransformationException("Unsupported transaction type [" + transactionData.getType().value + "] during conversion to JSON");
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
static JSONObject getBaseJSON(TransactionData transaction) {
|
|
JSONObject json = new JSONObject();
|
|
|
|
json.put("type", transaction.getType().value);
|
|
json.put("fee", transaction.getFee().toPlainString());
|
|
json.put("timestamp", transaction.getTimestamp());
|
|
json.put("signature", Base58.encode(transaction.getSignature()));
|
|
|
|
byte[] reference = transaction.getReference();
|
|
if (reference != null)
|
|
json.put("reference", Base58.encode(reference));
|
|
|
|
// XXX Can't do this as it requires database access:
|
|
// json.put("confirmations", RepositoryManager.getTransactionRepository.getConfirmations(transaction));
|
|
|
|
return json;
|
|
}
|
|
|
|
}
|