Merge 4766dae062e600b8eb97f25124de41eeb5646ecb into faee7c8f6a9fe85dcc4fb3c5ffacdde72668137f

This commit is contained in:
cwd.systems | 0KN 2025-03-19 13:36:19 +01:00 committed by GitHub
commit 8a6d102a89
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -18,241 +18,209 @@ import java.util.Map.Entry;
public class Payment { public class Payment {
// Properties // Properties
private Repository repository; private Repository repository;
// Constructors // Constructors
public Payment(Repository repository) {
this.repository = repository;
}
public Payment(Repository repository) { // isValid
this.repository = repository;
}
// Processing /** Are payments valid? */
public ValidationResult isValid(byte[] senderPublicKey, List<PaymentData> payments, long fee, boolean isZeroAmountValid) throws DataException {
AssetRepository assetRepository = this.repository.getAssetRepository();
// Check fee is positive or zero
if (fee < 0)
return ValidationResult.NEGATIVE_FEE;
// isValid // Total up payment amounts by assetId
Map<Long, Long> amountsByAssetId = new HashMap<>();
amountsByAssetId.put(Asset.QORT, fee); // Add transaction fee to the map
/** Are payments valid? */ // Grab sender info
public ValidationResult isValid(byte[] senderPublicKey, List<PaymentData> payments, long fee, boolean isZeroAmountValid) throws DataException { Account sender = new PublicKeyAccount(this.repository, senderPublicKey);
AssetRepository assetRepository = this.repository.getAssetRepository();
// Check fee is positive or zero // Check payments, and calculate amount total by assetId
// We have already checked that the fee is correct in the Transaction superclass for (PaymentData paymentData : payments) {
if (fee < 0) // Check amount is zero or positive
return ValidationResult.NEGATIVE_FEE; if (paymentData.getAmount() < 0)
return ValidationResult.NEGATIVE_AMOUNT;
// Total up payment amounts by assetId // Optional zero-amount check
Map<Long, Long> amountsByAssetId = new HashMap<>(); if (!isZeroAmountValid && paymentData.getAmount() <= 0)
// Add transaction fee to start with return ValidationResult.NEGATIVE_AMOUNT;
amountsByAssetId.put(Asset.QORT, fee);
// Grab sender info // Check recipient address is valid
Account sender = new PublicKeyAccount(this.repository, senderPublicKey); if (!Crypto.isValidAddress(paymentData.getRecipient()))
return ValidationResult.INVALID_ADDRESS;
// Check payments, and calculate amount total by assetId boolean recipientIsAT = Crypto.isValidAtAddress(paymentData.getRecipient());
for (PaymentData paymentData : payments) { ATData atData = null;
// Check amount is zero or positive
if (paymentData.getAmount() < 0)
return ValidationResult.NEGATIVE_AMOUNT;
// Optional zero-amount check // Do not allow payments to finished/dead/nonexistent ATs
if (!isZeroAmountValid && paymentData.getAmount() <= 0) if (recipientIsAT) {
return ValidationResult.NEGATIVE_AMOUNT; atData = this.repository.getATRepository().fromATAddress(paymentData.getRecipient());
// Check recipient address is valid if (atData == null)
if (!Crypto.isValidAddress(paymentData.getRecipient())) return ValidationResult.AT_UNKNOWN;
return ValidationResult.INVALID_ADDRESS;
boolean recipientIsAT = Crypto.isValidAtAddress(paymentData.getRecipient()); if (atData.getIsFinished())
ATData atData = null; return ValidationResult.AT_IS_FINISHED;
}
// Do not allow payments to finished/dead/nonexistent ATs AssetData assetData = assetRepository.fromAssetId(paymentData.getAssetId());
if (recipientIsAT) { // Check asset even exists
atData = this.repository.getATRepository().fromATAddress(paymentData.getRecipient()); if (assetData == null)
return ValidationResult.ASSET_DOES_NOT_EXIST;
if (atData == null) // Do not allow non-owner asset holders to use asset
return ValidationResult.AT_UNKNOWN; if (assetData.isUnspendable() && !assetData.getOwner().equals(sender.getAddress()))
return ValidationResult.ASSET_NOT_SPENDABLE;
if (atData != null && atData.getIsFinished()) // If we're sending to an AT then assetId must match AT's assetId
return ValidationResult.AT_IS_FINISHED; if (atData != null && atData.getAssetId() != paymentData.getAssetId())
} return ValidationResult.ASSET_DOES_NOT_MATCH_AT;
AssetData assetData = assetRepository.fromAssetId(paymentData.getAssetId()); // Check asset amount is integer if asset is not divisible
// Check asset even exists if (!assetData.isDivisible() && paymentData.getAmount() % Amounts.MULTIPLIER != 0)
if (assetData == null) return ValidationResult.INVALID_AMOUNT;
return ValidationResult.ASSET_DOES_NOT_EXIST;
// Do not allow non-owner asset holders to use asset // Set or add amount into amounts-by-asset map
if (assetData.isUnspendable() && !assetData.getOwner().equals(sender.getAddress())) amountsByAssetId.compute(paymentData.getAssetId(), (assetId, amount) -> amount == null ? paymentData.getAmount() : amount + paymentData.getAmount());
return ValidationResult.ASSET_NOT_SPENDABLE; }
// If we're sending to an AT then assetId must match AT's assetId // Check sender has enough of each asset
if (atData != null && atData.getAssetId() != paymentData.getAssetId()) for (Entry<Long, Long> pair : amountsByAssetId.entrySet())
return ValidationResult.ASSET_DOES_NOT_MATCH_AT; if (sender.getConfirmedBalance(pair.getKey()) < pair.getValue())
return ValidationResult.NO_BALANCE;
// Check asset amount is integer if asset is not divisible return ValidationResult.OK;
if (!assetData.isDivisible() && paymentData.getAmount() % Amounts.MULTIPLIER != 0) }
return ValidationResult.INVALID_AMOUNT;
// Set or add amount into amounts-by-asset map /** Are payments valid? */
amountsByAssetId.compute(paymentData.getAssetId(), (assetId, amount) -> amount == null ? paymentData.getAmount() : amount + paymentData.getAmount()); public ValidationResult isValid(byte[] senderPublicKey, List<PaymentData> payments, long fee) throws DataException {
} return isValid(senderPublicKey, payments, fee, false);
}
// Check sender has enough of each asset /** Is single payment valid? */
for (Entry<Long, Long> pair : amountsByAssetId.entrySet()) public ValidationResult isValid(byte[] senderPublicKey, PaymentData paymentData, long fee, boolean isZeroAmountValid) throws DataException {
if (sender.getConfirmedBalance(pair.getKey()) < pair.getValue()) return isValid(senderPublicKey, Collections.singletonList(paymentData), fee, isZeroAmountValid);
return ValidationResult.NO_BALANCE; }
return ValidationResult.OK; /** Is single payment valid? */
} public ValidationResult isValid(byte[] senderPublicKey, PaymentData paymentData, long fee) throws DataException {
return isValid(senderPublicKey, paymentData, fee, false);
}
/** Are payments valid? */ // process
public ValidationResult isValid(byte[] senderPublicKey, List<PaymentData> payments, long fee) throws DataException {
return isValid(senderPublicKey, payments, fee, false);
}
/** Is single payment valid? */ /** Multiple payment processing */
public ValidationResult isValid(byte[] senderPublicKey, PaymentData paymentData, long fee, boolean isZeroAmountValid) throws DataException { public void process(byte[] senderPublicKey, List<PaymentData> payments) throws DataException {
return isValid(senderPublicKey, Collections.singletonList(paymentData), fee, isZeroAmountValid); Account sender = new PublicKeyAccount(this.repository, senderPublicKey);
}
/** Is single payment valid? */ // Process all payments
public ValidationResult isValid(byte[] senderPublicKey, PaymentData paymentData, long fee) throws DataException { for (PaymentData paymentData : payments) {
return isValid(senderPublicKey, paymentData, fee, false); Account recipient = new Account(this.repository, paymentData.getRecipient());
}
// isProcessable long assetId = paymentData.getAssetId();
long amount = paymentData.getAmount();
/** Are multiple payments processable? */ // Update sender's balance due to amount
public ValidationResult isProcessable(byte[] senderPublicKey, List<PaymentData> payments, long fee, boolean isZeroAmountValid) throws DataException { sender.modifyAssetBalance(assetId, -amount);
// Essentially the same as isValid...
return isValid(senderPublicKey, payments, fee, isZeroAmountValid);
}
/** Are multiple payments processable? */ // Update recipient's balance
public ValidationResult isProcessable(byte[] senderPublicKey, List<PaymentData> payments, long fee) throws DataException { recipient.modifyAssetBalance(assetId, amount);
return isProcessable(senderPublicKey, payments, fee, false); }
} }
/** Is single payment processable? */ /** Single payment processing */
public ValidationResult isProcessable(byte[] senderPublicKey, PaymentData paymentData, long fee, boolean isZeroAmountValid) throws DataException { public void process(byte[] senderPublicKey, PaymentData paymentData) throws DataException {
return isProcessable(senderPublicKey, Collections.singletonList(paymentData), fee, isZeroAmountValid); process(senderPublicKey, Collections.singletonList(paymentData));
} }
/** Is single payment processable? */ // processReferenceAndFees
public ValidationResult isProcessable(byte[] senderPublicKey, PaymentData paymentData, long fee) throws DataException {
return isProcessable(senderPublicKey, paymentData, fee, false);
}
// process /** Multiple payment reference processing */
public void processReferencesAndFees(byte[] senderPublicKey, List<PaymentData> payments, long fee, byte[] signature, boolean alwaysInitializeRecipientReference)
throws DataException {
Account sender = new PublicKeyAccount(this.repository, senderPublicKey);
/** Multiple payment processing */ // Update sender's balance due to fee
public void process(byte[] senderPublicKey, List<PaymentData> payments) throws DataException { sender.modifyAssetBalance(Asset.QORT, -fee);
Account sender = new PublicKeyAccount(this.repository, senderPublicKey);
// Process all payments // Update sender's reference
for (PaymentData paymentData : payments) { sender.setLastReference(signature);
Account recipient = new Account(this.repository, paymentData.getRecipient());
long assetId = paymentData.getAssetId(); // Process all recipients
long amount = paymentData.getAmount(); for (PaymentData paymentData : payments) {
Account recipient = new Account(this.repository, paymentData.getRecipient());
// Update sender's balance due to amount long assetId = paymentData.getAssetId();
sender.modifyAssetBalance(assetId, - amount);
// Update recipient's balance // For QORT amounts only: if recipient has no reference yet, then this is their starting reference
recipient.modifyAssetBalance(assetId, amount); if ((alwaysInitializeRecipientReference || assetId == Asset.QORT) && recipient.getLastReference() == null)
} recipient.setLastReference(signature);
} }
}
/** Single payment processing */ /** Multiple payment reference processing */
public void process(byte[] senderPublicKey, PaymentData paymentData) throws DataException { public void processReferencesAndFees(byte[] senderPublicKey, PaymentData payment, long fee, byte[] signature, boolean alwaysInitializeRecipientReference)
process(senderPublicKey, Collections.singletonList(paymentData)); throws DataException {
} processReferencesAndFees(senderPublicKey, Collections.singletonList(payment), fee, signature, alwaysInitializeRecipientReference);
}
// processReferenceAndFees // orphan
/** Multiple payment reference processing */ public void orphan(byte[] senderPublicKey, List<PaymentData> payments) throws DataException {
public void processReferencesAndFees(byte[] senderPublicKey, List<PaymentData> payments, long fee, byte[] signature, boolean alwaysInitializeRecipientReference) Account sender = new PublicKeyAccount(this.repository, senderPublicKey);
throws DataException {
Account sender = new PublicKeyAccount(this.repository, senderPublicKey);
// Update sender's balance due to fee // Orphan all payments
sender.modifyAssetBalance(Asset.QORT, - fee); for (PaymentData paymentData : payments) {
Account recipient = new Account(this.repository, paymentData.getRecipient());
long assetId = paymentData.getAssetId();
long amount = paymentData.getAmount();
// Update sender's reference // Update sender's balance due to amount
sender.setLastReference(signature); sender.modifyAssetBalance(assetId, amount);
// Process all recipients // Update recipient's balance
for (PaymentData paymentData : payments) { recipient.modifyAssetBalance(assetId, -amount);
Account recipient = new Account(this.repository, paymentData.getRecipient()); }
}
long assetId = paymentData.getAssetId(); public void orphan(byte[] senderPublicKey, PaymentData paymentData) throws DataException {
orphan(senderPublicKey, Collections.singletonList(paymentData));
}
// For QORT amounts only: if recipient has no reference yet, then this is their starting reference // orphanReferencesAndFees
if ((alwaysInitializeRecipientReference || assetId == Asset.QORT) && recipient.getLastReference() == null)
recipient.setLastReference(signature);
}
}
/** Multiple payment reference processing */ public void orphanReferencesAndFees(byte[] senderPublicKey, List<PaymentData> payments, long fee, byte[] signature, byte[] reference,
public void processReferencesAndFees(byte[] senderPublicKey, PaymentData payment, long fee, byte[] signature, boolean alwaysInitializeRecipientReference) boolean alwaysUninitializeRecipientReference) throws DataException {
throws DataException { Account sender = new PublicKeyAccount(this.repository, senderPublicKey);
processReferencesAndFees(senderPublicKey, Collections.singletonList(payment), fee, signature, alwaysInitializeRecipientReference);
}
// orphan // Update sender's balance due to fee
sender.modifyAssetBalance(Asset.QORT, fee);
public void orphan(byte[] senderPublicKey, List<PaymentData> payments) throws DataException { // Update sender's reference
Account sender = new PublicKeyAccount(this.repository, senderPublicKey); sender.setLastReference(reference);
// Orphan all payments // Orphan all recipients
for (PaymentData paymentData : payments) { for (PaymentData paymentData : payments) {
Account recipient = new Account(this.repository, paymentData.getRecipient()); Account recipient = new Account(this.repository, paymentData.getRecipient());
long assetId = paymentData.getAssetId(); long assetId = paymentData.getAssetId();
long amount = paymentData.getAmount();
// Update sender's balance due to amount // For QORT amounts only: If recipient's last reference is this transaction's signature, then they can't have made any transactions of their own
sender.modifyAssetBalance(assetId, amount); // (which would have changed their last reference) thus this is their first reference so remove it.
if ((alwaysUninitializeRecipientReference || assetId == Asset.QORT) && Arrays.equals(recipient.getLastReference(), signature))
// Update recipient's balance recipient.setLastReference(null);
recipient.modifyAssetBalance(assetId, - amount); }
} }
}
public void orphan(byte[] senderPublicKey, PaymentData paymentData) throws DataException {
orphan(senderPublicKey, Collections.singletonList(paymentData));
}
// orphanReferencesAndFees
public void orphanReferencesAndFees(byte[] senderPublicKey, List<PaymentData> payments, long fee, byte[] signature, byte[] reference,
boolean alwaysUninitializeRecipientReference) throws DataException {
Account sender = new PublicKeyAccount(this.repository, senderPublicKey);
// Update sender's balance due to fee
sender.modifyAssetBalance(Asset.QORT, fee);
// Update sender's reference
sender.setLastReference(reference);
// Orphan all recipients
for (PaymentData paymentData : payments) {
Account recipient = new Account(this.repository, paymentData.getRecipient());
long assetId = paymentData.getAssetId();
/*
* For QORT amounts only: If recipient's last reference is this transaction's signature, then they can't have made any transactions of their own
* (which would have changed their last reference) thus this is their first reference so remove it.
*/
if ((alwaysUninitializeRecipientReference || assetId == Asset.QORT) && Arrays.equals(recipient.getLastReference(), signature))
recipient.setLastReference(null);
}
}
public void orphanReferencesAndFees(byte[] senderPublicKey, PaymentData paymentData, long fee, byte[] signature, byte[] reference,
boolean alwaysUninitializeRecipientReference) throws DataException {
orphanReferencesAndFees(senderPublicKey, Collections.singletonList(paymentData), fee, signature, reference, alwaysUninitializeRecipientReference);
}
public void orphanReferencesAndFees(byte[] senderPublicKey, PaymentData paymentData, long fee, byte[] signature, byte[] reference,
boolean alwaysUninitializeRecipientReference) throws DataException {
orphanReferencesAndFees(senderPublicKey, Collections.singletonList(paymentData), fee, signature, reference, alwaysUninitializeRecipientReference);
}
} }