mirror of
https://github.com/Qortal/qortal.git
synced 2025-03-30 17:05:53 +00:00
HSQLDB v2.4.0 had some issue with non-padded, case-insensitive string comparisons. This is fixed in svn r5836-ish of HSQLDB but yet to be pushed out to new HSQLDB release. So this commit includes hsqldb-r5836.jar and modified pom.xml/.classpath for now. No need for duplicate, hidden creatorPublicKey in CancelOrderTransactionData, CreateOrderTransactionData and CreatePollTransactionData. Various changes to use more try-with-resources, especially with JDBC objects like Connection, Statement, PreparedStatement, ResultSet. Added loads of missing @Override annotations. Fixed bug in Asset exchange order matching where the matching logic loop would incorrectly adjust temporary amount fulfilled by the "want" asset amount (in matchedAmount) instead of the "have" asset amount (in tradePrice). Disabled check for duplicate asset name in IssueAssetTransactions for old v1 transactions. In HSQLDB repository we now use ResultSet.getTimestamp(index, UTC-calendar) to make sure we only store/fetch UTC timestamps. The UTC-calendar is made using static final TimeZone called HSQLDBRepository.UTC. To keep asset IDs in line with v1, Assets.asset_id values are generated on-the-fly in HSQLDB using a "before insert" trigger on Assets table. Corresponding code calling HSQLDBRepository.callIdentity() replaced with SELECT statement instead. Moved most of the HSQLDB connection properties from the connection URL to explicit code in HSQLDBRepositoryFactory. Fixed incorrect 'amount' lengths in PaymentTransformer, as used by MultiPayment and Arbitrary transaction types. Added support for mangled arbitrary transaction bytes when generating/verifying a v1 transaction signature. In v1 Arbitrary transactions, bytes-for-signing are lost prior to final payment (but only if there are any payments). Added corresponding code for multi-payment transactions in the same vein.
142 lines
5.0 KiB
Java
142 lines
5.0 KiB
Java
package transform.transaction;
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.IOException;
|
|
import java.math.BigDecimal;
|
|
import java.nio.ByteBuffer;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.List;
|
|
|
|
import org.json.simple.JSONArray;
|
|
import org.json.simple.JSONObject;
|
|
|
|
import com.google.common.hash.HashCode;
|
|
import com.google.common.primitives.Ints;
|
|
import com.google.common.primitives.Longs;
|
|
|
|
import data.transaction.TransactionData;
|
|
import qora.account.PublicKeyAccount;
|
|
import qora.block.BlockChain;
|
|
import data.PaymentData;
|
|
import data.transaction.MultiPaymentTransactionData;
|
|
import transform.PaymentTransformer;
|
|
import transform.TransformationException;
|
|
import utils.Serialization;
|
|
|
|
public class MultiPaymentTransactionTransformer extends TransactionTransformer {
|
|
|
|
// Property lengths
|
|
private static final int SENDER_LENGTH = PUBLIC_KEY_LENGTH;
|
|
private static final int PAYMENTS_COUNT_LENGTH = INT_LENGTH;
|
|
|
|
private static final int TYPELESS_LENGTH = BASE_TYPELESS_LENGTH + SENDER_LENGTH + PAYMENTS_COUNT_LENGTH;
|
|
|
|
static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException {
|
|
long timestamp = byteBuffer.getLong();
|
|
|
|
byte[] reference = new byte[REFERENCE_LENGTH];
|
|
byteBuffer.get(reference);
|
|
|
|
byte[] senderPublicKey = Serialization.deserializePublicKey(byteBuffer);
|
|
|
|
int paymentsCount = byteBuffer.getInt();
|
|
|
|
List<PaymentData> payments = new ArrayList<PaymentData>();
|
|
for (int i = 0; i < paymentsCount; ++i)
|
|
payments.add(PaymentTransformer.fromByteBuffer(byteBuffer));
|
|
|
|
BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer);
|
|
|
|
byte[] signature = new byte[SIGNATURE_LENGTH];
|
|
byteBuffer.get(signature);
|
|
|
|
return new MultiPaymentTransactionData(senderPublicKey, payments, fee, timestamp, reference, signature);
|
|
}
|
|
|
|
public static int getDataLength(TransactionData transactionData) throws TransformationException {
|
|
MultiPaymentTransactionData multiPaymentTransactionData = (MultiPaymentTransactionData) transactionData;
|
|
|
|
return TYPE_LENGTH + TYPELESS_LENGTH + multiPaymentTransactionData.getPayments().size() * PaymentTransformer.getDataLength();
|
|
}
|
|
|
|
public static byte[] toBytes(TransactionData transactionData) throws TransformationException {
|
|
try {
|
|
MultiPaymentTransactionData multiPaymentTransactionData = (MultiPaymentTransactionData) transactionData;
|
|
|
|
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
|
|
|
bytes.write(Ints.toByteArray(multiPaymentTransactionData.getType().value));
|
|
bytes.write(Longs.toByteArray(multiPaymentTransactionData.getTimestamp()));
|
|
bytes.write(multiPaymentTransactionData.getReference());
|
|
|
|
bytes.write(multiPaymentTransactionData.getSenderPublicKey());
|
|
|
|
List<PaymentData> payments = multiPaymentTransactionData.getPayments();
|
|
bytes.write(Ints.toByteArray(payments.size()));
|
|
|
|
for (PaymentData paymentData : payments)
|
|
bytes.write(PaymentTransformer.toBytes(paymentData));
|
|
|
|
Serialization.serializeBigDecimal(bytes, multiPaymentTransactionData.getFee());
|
|
|
|
if (multiPaymentTransactionData.getSignature() != null)
|
|
bytes.write(multiPaymentTransactionData.getSignature());
|
|
|
|
return bytes.toByteArray();
|
|
} catch (IOException | ClassCastException e) {
|
|
throw new TransformationException(e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* In Qora v1, the bytes used for verification are really mangled so we need to test for v1-ness and adjust the bytes accordingly.
|
|
*
|
|
* @param transactionData
|
|
* @return byte[]
|
|
* @throws TransformationException
|
|
*/
|
|
public static byte[] toBytesForSigningImpl(TransactionData transactionData) throws TransformationException {
|
|
byte[] bytes = TransactionTransformer.toBytesForSigningImpl(transactionData);
|
|
|
|
if (transactionData.getTimestamp() >= BlockChain.getIssueAssetV2Timestamp())
|
|
return bytes;
|
|
|
|
// Special v1 version
|
|
|
|
// In v1, a coding error means that all bytes prior to final payment entry are lost!
|
|
// So we're left with: final payment entry and fee. Signature has already been stripped
|
|
int v1Length = PaymentTransformer.getDataLength() + FEE_LENGTH;
|
|
int v1Start = bytes.length - v1Length;
|
|
|
|
return Arrays.copyOfRange(bytes, v1Start, bytes.length);
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
public static JSONObject toJSON(TransactionData transactionData) throws TransformationException {
|
|
JSONObject json = TransactionTransformer.getBaseJSON(transactionData);
|
|
|
|
try {
|
|
MultiPaymentTransactionData multiPaymentTransactionData = (MultiPaymentTransactionData) transactionData;
|
|
|
|
byte[] senderPublicKey = multiPaymentTransactionData.getSenderPublicKey();
|
|
|
|
json.put("sender", PublicKeyAccount.getAddress(senderPublicKey));
|
|
json.put("senderPublicKey", HashCode.fromBytes(senderPublicKey).toString());
|
|
|
|
List<PaymentData> payments = multiPaymentTransactionData.getPayments();
|
|
JSONArray paymentsJson = new JSONArray();
|
|
|
|
for (PaymentData paymentData : payments)
|
|
paymentsJson.add(PaymentTransformer.toJSON(paymentData));
|
|
|
|
json.put("payments", paymentsJson);
|
|
} catch (ClassCastException e) {
|
|
throw new TransformationException(e);
|
|
}
|
|
|
|
return json;
|
|
}
|
|
|
|
}
|