forked from Qortal-Forker/qortal
		
	For Balance Recorder, reward recordings only, that is the default.
This commit is contained in:
		@@ -283,7 +283,7 @@ public class AssetsResource {
 | 
			
		||||
		Optional<HSQLDBBalanceRecorder> recorder = HSQLDBBalanceRecorder.getInstance();
 | 
			
		||||
 | 
			
		||||
		if( recorder.isPresent()) {
 | 
			
		||||
			Optional<BlockHeightRangeAddressAmounts> addressAmounts = recorder.get().getAddressAmounts(new BlockHeightRange(begin, end));
 | 
			
		||||
			Optional<BlockHeightRangeAddressAmounts> addressAmounts = recorder.get().getAddressAmounts(new BlockHeightRange(begin, end, false));
 | 
			
		||||
 | 
			
		||||
			if( addressAmounts.isPresent() ) {
 | 
			
		||||
				return addressAmounts.get().getAmounts().stream()
 | 
			
		||||
 
 | 
			
		||||
@@ -12,12 +12,15 @@ public class BlockHeightRange {
 | 
			
		||||
 | 
			
		||||
    private int end;
 | 
			
		||||
 | 
			
		||||
    private boolean isRewardDistribution;
 | 
			
		||||
 | 
			
		||||
    public BlockHeightRange() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public BlockHeightRange(int begin, int end) {
 | 
			
		||||
    public BlockHeightRange(int begin, int end, boolean isRewardDistribution) {
 | 
			
		||||
        this.begin = begin;
 | 
			
		||||
        this.end = end;
 | 
			
		||||
        this.isRewardDistribution = isRewardDistribution;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public int getBegin() {
 | 
			
		||||
@@ -28,6 +31,10 @@ public class BlockHeightRange {
 | 
			
		||||
        return end;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean isRewardDistribution() {
 | 
			
		||||
        return isRewardDistribution;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean equals(Object o) {
 | 
			
		||||
        if (this == o) return true;
 | 
			
		||||
@@ -46,6 +53,7 @@ public class BlockHeightRange {
 | 
			
		||||
        return "BlockHeightRange{" +
 | 
			
		||||
                "begin=" + begin +
 | 
			
		||||
                ", end=" + end +
 | 
			
		||||
                ", isRewardDistribution=" + isRewardDistribution +
 | 
			
		||||
                '}';
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ package org.qortal.repository.hsqldb;
 | 
			
		||||
import org.apache.logging.log4j.LogManager;
 | 
			
		||||
import org.apache.logging.log4j.Logger;
 | 
			
		||||
import org.qortal.api.SearchMode;
 | 
			
		||||
import org.qortal.api.resource.TransactionsResource;
 | 
			
		||||
import org.qortal.arbitrary.misc.Category;
 | 
			
		||||
import org.qortal.arbitrary.misc.Service;
 | 
			
		||||
import org.qortal.controller.Controller;
 | 
			
		||||
@@ -14,7 +15,10 @@ import org.qortal.data.arbitrary.ArbitraryResourceCache;
 | 
			
		||||
import org.qortal.data.arbitrary.ArbitraryResourceData;
 | 
			
		||||
import org.qortal.data.arbitrary.ArbitraryResourceMetadata;
 | 
			
		||||
import org.qortal.data.arbitrary.ArbitraryResourceStatus;
 | 
			
		||||
import org.qortal.data.transaction.TransactionData;
 | 
			
		||||
import org.qortal.repository.DataException;
 | 
			
		||||
import org.qortal.repository.Repository;
 | 
			
		||||
import org.qortal.repository.RepositoryManager;
 | 
			
		||||
import org.qortal.settings.Settings;
 | 
			
		||||
import org.qortal.utils.BalanceRecorderUtils;
 | 
			
		||||
 | 
			
		||||
@@ -434,17 +438,44 @@ public class HSQLDBCacheUtils {
 | 
			
		||||
                    // if there is a prior height
 | 
			
		||||
                    if(priorHeight.isPresent()) {
 | 
			
		||||
 | 
			
		||||
                        BlockHeightRange blockHeightRange = new BlockHeightRange(priorHeight.get(), currentHeight);
 | 
			
		||||
                        boolean isRewardDistribution = BalanceRecorderUtils.isRewardDistributionRange(priorHeight.get(), currentHeight);
 | 
			
		||||
 | 
			
		||||
                        // if this range has a reward recording block or if other blocks are enabled for recording
 | 
			
		||||
                        if( isRewardDistribution || !Settings.getInstance().isRewardRecordingOnly() ) {
 | 
			
		||||
                            produceBalanceDynamics(currentHeight, priorHeight, isRewardDistribution, balancesByHeight, balanceDynamics, capacity);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    else {
 | 
			
		||||
                        LOGGER.warn("Expecting prior height and nothing was discovered, current height = " + currentHeight);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                // else this should be the first recording
 | 
			
		||||
                else {
 | 
			
		||||
                    LOGGER.info("first balance recording completed");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // wait 5 minutes
 | 
			
		||||
        timer.scheduleAtFixedRate(task, 300_000, frequency * 60_000);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void produceBalanceDynamics(int currentHeight, Optional<Integer> priorHeight, boolean isRewardDistribution, ConcurrentHashMap<Integer, List<AccountBalanceData>> balancesByHeight, CopyOnWriteArrayList<BlockHeightRangeAddressAmounts> balanceDynamics, int capacity) {
 | 
			
		||||
        BlockHeightRange blockHeightRange = new BlockHeightRange(priorHeight.get(), currentHeight, isRewardDistribution);
 | 
			
		||||
 | 
			
		||||
        LOGGER.debug("building dynamics for block heights: range = " + blockHeightRange);
 | 
			
		||||
 | 
			
		||||
        List<AccountBalanceData> currentBalances = balancesByHeight.get(currentHeight);
 | 
			
		||||
 | 
			
		||||
        ArrayList<TransactionData> transactions = getTransactionDataForBlocks(blockHeightRange);
 | 
			
		||||
 | 
			
		||||
        LOGGER.info("transactions counted for balance adjustments: count = " + transactions.size());
 | 
			
		||||
        List<AddressAmountData> currentDynamics
 | 
			
		||||
            = BalanceRecorderUtils.buildBalanceDynamics(
 | 
			
		||||
                currentBalances,
 | 
			
		||||
                balancesByHeight.get(priorHeight.get()),
 | 
			
		||||
                                Settings.getInstance().getMinimumBalanceRecording());
 | 
			
		||||
                Settings.getInstance().getMinimumBalanceRecording(),
 | 
			
		||||
                transactions);
 | 
			
		||||
 | 
			
		||||
        LOGGER.debug("dynamics built: count = " + currentDynamics.size());
 | 
			
		||||
 | 
			
		||||
@@ -467,19 +498,29 @@ public class HSQLDBCacheUtils {
 | 
			
		||||
            LOGGER.debug("removing oldest dynamics: range " + oldestDynamics.getRange());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
                    else {
 | 
			
		||||
                        LOGGER.warn("Expecting prior height and nothing was discovered, current height = " + currentHeight);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                // else this should be the first recording
 | 
			
		||||
                else {
 | 
			
		||||
                    LOGGER.info("first balance recording completed");
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // wait 5 minutes
 | 
			
		||||
        timer.scheduleAtFixedRate(task, 300_000, frequency * 60_000);
 | 
			
		||||
    private static ArrayList<TransactionData> getTransactionDataForBlocks(BlockHeightRange blockHeightRange) {
 | 
			
		||||
        ArrayList<TransactionData> transactions;
 | 
			
		||||
 | 
			
		||||
        try (final Repository repository = RepositoryManager.getRepository()) {
 | 
			
		||||
            List<byte[]> signatures
 | 
			
		||||
                = repository.getTransactionRepository().getSignaturesMatchingCriteria(
 | 
			
		||||
                    blockHeightRange.getBegin() + 1, blockHeightRange.getEnd() - blockHeightRange.getBegin(),
 | 
			
		||||
                    null, null,null, null, null,
 | 
			
		||||
                    TransactionsResource.ConfirmationStatus.CONFIRMED,
 | 
			
		||||
                    null, null, null);
 | 
			
		||||
 | 
			
		||||
            transactions = new ArrayList<>(signatures.size());
 | 
			
		||||
            for (byte[] signature : signatures) {
 | 
			
		||||
                transactions.add(repository.getTransactionRepository().fromSignature(signature));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            LOGGER.debug(String.format("Found %s transactions for " + blockHeightRange, transactions.size()));
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            transactions = new ArrayList<>(0);
 | 
			
		||||
            LOGGER.warn("Problems getting transactions for balance recording: " + e.getMessage());
 | 
			
		||||
        }
 | 
			
		||||
        return transactions;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static int recordCurrentBalances(ConcurrentHashMap<Integer, List<AccountBalanceData>> balancesByHeight) {
 | 
			
		||||
 
 | 
			
		||||
@@ -494,6 +494,13 @@ public class Settings {
 | 
			
		||||
	 */
 | 
			
		||||
	private int balanceRecorderRollbackAllowance = 100;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Is Reward Recording Only
 | 
			
		||||
	 *
 | 
			
		||||
	 * Set true to only retain the recordings that cover reward distributions, otherwise set false.
 | 
			
		||||
	 */
 | 
			
		||||
    private boolean rewardRecordingOnly = true;
 | 
			
		||||
 | 
			
		||||
    // Domain mapping
 | 
			
		||||
	public static class ThreadLimit {
 | 
			
		||||
		private String messageType;
 | 
			
		||||
@@ -1311,4 +1318,8 @@ public class Settings {
 | 
			
		||||
	public int getBalanceRecorderRollbackAllowance() {
 | 
			
		||||
		return balanceRecorderRollbackAllowance;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public boolean isRewardRecordingOnly() {
 | 
			
		||||
		return rewardRecordingOnly;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,26 @@
 | 
			
		||||
package org.qortal.utils;
 | 
			
		||||
 | 
			
		||||
import org.qortal.block.Block;
 | 
			
		||||
import org.qortal.crypto.Crypto;
 | 
			
		||||
import org.qortal.data.PaymentData;
 | 
			
		||||
import org.qortal.data.account.AccountBalanceData;
 | 
			
		||||
import org.qortal.data.account.AddressAmountData;
 | 
			
		||||
import org.qortal.data.account.BlockHeightRange;
 | 
			
		||||
import org.qortal.data.account.BlockHeightRangeAddressAmounts;
 | 
			
		||||
import org.qortal.data.transaction.ATTransactionData;
 | 
			
		||||
import org.qortal.data.transaction.BaseTransactionData;
 | 
			
		||||
import org.qortal.data.transaction.BuyNameTransactionData;
 | 
			
		||||
import org.qortal.data.transaction.CreateAssetOrderTransactionData;
 | 
			
		||||
import org.qortal.data.transaction.DeployAtTransactionData;
 | 
			
		||||
import org.qortal.data.transaction.MultiPaymentTransactionData;
 | 
			
		||||
import org.qortal.data.transaction.PaymentTransactionData;
 | 
			
		||||
import org.qortal.data.transaction.TransactionData;
 | 
			
		||||
import org.qortal.data.transaction.TransferAssetTransactionData;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Comparator;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
import java.util.concurrent.ConcurrentHashMap;
 | 
			
		||||
import java.util.concurrent.CopyOnWriteArrayList;
 | 
			
		||||
@@ -67,22 +80,190 @@ public class BalanceRecorderUtils {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static List<AddressAmountData> buildBalanceDynamics(final List<AccountBalanceData> balances, final List<AccountBalanceData> priorBalances, long minimum) {
 | 
			
		||||
    public static List<AddressAmountData> buildBalanceDynamics(
 | 
			
		||||
            final List<AccountBalanceData> balances,
 | 
			
		||||
            final List<AccountBalanceData> priorBalances,
 | 
			
		||||
            long minimum,
 | 
			
		||||
            List<TransactionData> transactions) {
 | 
			
		||||
 | 
			
		||||
        List<AddressAmountData> addressAmounts = new ArrayList<>(balances.size());
 | 
			
		||||
        Map<String, Long> amountsByAddress = new HashMap<>(transactions.size());
 | 
			
		||||
 | 
			
		||||
        // prior balance
 | 
			
		||||
        addressAmounts.addAll(
 | 
			
		||||
            balances.stream()
 | 
			
		||||
        for( TransactionData transactionData : transactions ) {
 | 
			
		||||
 | 
			
		||||
            mapBalanceModificationsForTransaction(amountsByAddress, transactionData);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        List<AddressAmountData> addressAmounts
 | 
			
		||||
            = balances.stream()
 | 
			
		||||
                .map(balance -> buildBalanceDynamicsForAccount(priorBalances, balance))
 | 
			
		||||
                .map( data -> adjustAddressAmount(amountsByAddress.getOrDefault(data.getAddress(), 0L), data))
 | 
			
		||||
                .filter(ADDRESS_AMOUNT_DATA_NOT_ZERO)
 | 
			
		||||
                .filter(data -> data.getAmount() >= minimum)
 | 
			
		||||
                .collect(Collectors.toList())
 | 
			
		||||
        );
 | 
			
		||||
                .collect(Collectors.toList());
 | 
			
		||||
 | 
			
		||||
        return addressAmounts;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static AddressAmountData adjustAddressAmount(long adjustment, AddressAmountData data) {
 | 
			
		||||
 | 
			
		||||
        return new AddressAmountData(data.getAddress(), data.getAmount() - adjustment);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void mapBalanceModificationsForTransaction(Map<String, Long> amountsByAddress, TransactionData transactionData) {
 | 
			
		||||
        String creatorAddress;
 | 
			
		||||
 | 
			
		||||
        // AT Transaction
 | 
			
		||||
        if( transactionData instanceof ATTransactionData) {
 | 
			
		||||
            creatorAddress = mapBalanceModificationsForAtTransaction(amountsByAddress, (ATTransactionData) transactionData);
 | 
			
		||||
        }
 | 
			
		||||
        // Buy Name Transaction
 | 
			
		||||
        else if( transactionData instanceof BuyNameTransactionData) {
 | 
			
		||||
            creatorAddress = mapBalanceModificationsForBuyNameTransaction(amountsByAddress, (BuyNameTransactionData) transactionData);
 | 
			
		||||
        }
 | 
			
		||||
        // Create Asset Order Transaction
 | 
			
		||||
        else if( transactionData instanceof CreateAssetOrderTransactionData) {
 | 
			
		||||
            //TODO I'm not sure how to handle this one. This hasn't been used at this point in the blockchain.
 | 
			
		||||
 | 
			
		||||
            creatorAddress = Crypto.toAddress(transactionData.getCreatorPublicKey());
 | 
			
		||||
        }
 | 
			
		||||
        // Deploy AT Transaction
 | 
			
		||||
        else if( transactionData instanceof DeployAtTransactionData ) {
 | 
			
		||||
            creatorAddress = mapBalanceModificationsForDeployAtTransaction(amountsByAddress, (DeployAtTransactionData) transactionData);
 | 
			
		||||
        }
 | 
			
		||||
        // Multi Payment Transaction
 | 
			
		||||
        else if( transactionData instanceof MultiPaymentTransactionData) {
 | 
			
		||||
            creatorAddress = mapBalanceModificationsForMultiPaymentTransaction(amountsByAddress, (MultiPaymentTransactionData) transactionData);
 | 
			
		||||
        }
 | 
			
		||||
        // Payment Transaction
 | 
			
		||||
        else if( transactionData instanceof  PaymentTransactionData ) {
 | 
			
		||||
            creatorAddress = mapBalanceModicationsForPaymentTransaction(amountsByAddress, (PaymentTransactionData) transactionData);
 | 
			
		||||
        }
 | 
			
		||||
        // Transfer Asset Transaction
 | 
			
		||||
        else if( transactionData instanceof TransferAssetTransactionData) {
 | 
			
		||||
            creatorAddress = mapBalanceModificationsForTransferAssetTransaction(amountsByAddress, (TransferAssetTransactionData) transactionData);
 | 
			
		||||
        }
 | 
			
		||||
        // Other Transactions
 | 
			
		||||
        else {
 | 
			
		||||
            creatorAddress = Crypto.toAddress(transactionData.getCreatorPublicKey());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // all transactions modify the balance for fees
 | 
			
		||||
        mapBalanceModifications(amountsByAddress, transactionData.getFee(), creatorAddress, Optional.empty());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static String mapBalanceModificationsForTransferAssetTransaction(Map<String, Long> amountsByAddress, TransferAssetTransactionData transferAssetData) {
 | 
			
		||||
        String creatorAddress = Crypto.toAddress(transferAssetData.getSenderPublicKey());
 | 
			
		||||
 | 
			
		||||
        if( transferAssetData.getAssetId() == 0) {
 | 
			
		||||
            mapBalanceModifications(
 | 
			
		||||
                    amountsByAddress,
 | 
			
		||||
                    transferAssetData.getAmount(),
 | 
			
		||||
                    creatorAddress,
 | 
			
		||||
                    Optional.of(transferAssetData.getRecipient())
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        return creatorAddress;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static String mapBalanceModicationsForPaymentTransaction(Map<String, Long> amountsByAddress, PaymentTransactionData paymentData) {
 | 
			
		||||
        String creatorAddress = Crypto.toAddress(paymentData.getCreatorPublicKey());
 | 
			
		||||
 | 
			
		||||
        mapBalanceModifications(amountsByAddress,
 | 
			
		||||
            paymentData.getAmount(),
 | 
			
		||||
            creatorAddress,
 | 
			
		||||
            Optional.of(paymentData.getRecipient())
 | 
			
		||||
        );
 | 
			
		||||
        return creatorAddress;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static String mapBalanceModificationsForMultiPaymentTransaction(Map<String, Long> amountsByAddress, MultiPaymentTransactionData multiPaymentData) {
 | 
			
		||||
        String creatorAddress = Crypto.toAddress(multiPaymentData.getCreatorPublicKey());
 | 
			
		||||
 | 
			
		||||
        for(PaymentData payment : multiPaymentData.getPayments() ) {
 | 
			
		||||
            mapBalanceModificationsForTransaction(
 | 
			
		||||
                amountsByAddress,
 | 
			
		||||
                getPaymentTransactionData(multiPaymentData, payment)
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        return creatorAddress;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static String mapBalanceModificationsForDeployAtTransaction(Map<String, Long> amountsByAddress, DeployAtTransactionData transactionData) {
 | 
			
		||||
        String creatorAddress;
 | 
			
		||||
        DeployAtTransactionData deployAtData = transactionData;
 | 
			
		||||
 | 
			
		||||
        creatorAddress = Crypto.toAddress(deployAtData.getCreatorPublicKey());
 | 
			
		||||
 | 
			
		||||
        if( deployAtData.getAssetId() == 0 ) {
 | 
			
		||||
            mapBalanceModifications(
 | 
			
		||||
                    amountsByAddress,
 | 
			
		||||
                    deployAtData.getAmount(),
 | 
			
		||||
                    creatorAddress,
 | 
			
		||||
                    Optional.of(deployAtData.getAtAddress())
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        return creatorAddress;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static String mapBalanceModificationsForBuyNameTransaction(Map<String, Long> amountsByAddress, BuyNameTransactionData transactionData) {
 | 
			
		||||
        String creatorAddress;
 | 
			
		||||
        BuyNameTransactionData buyNameData = transactionData;
 | 
			
		||||
 | 
			
		||||
        creatorAddress = Crypto.toAddress(buyNameData.getCreatorPublicKey());
 | 
			
		||||
 | 
			
		||||
        mapBalanceModifications(
 | 
			
		||||
                amountsByAddress,
 | 
			
		||||
            buyNameData.getAmount(),
 | 
			
		||||
            creatorAddress,
 | 
			
		||||
            Optional.of(buyNameData.getSeller())
 | 
			
		||||
        );
 | 
			
		||||
        return creatorAddress;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static String mapBalanceModificationsForAtTransaction(Map<String, Long> amountsByAddress, ATTransactionData transactionData) {
 | 
			
		||||
        String creatorAddress;
 | 
			
		||||
        ATTransactionData atData = transactionData;
 | 
			
		||||
        creatorAddress = atData.getATAddress();
 | 
			
		||||
 | 
			
		||||
        if( atData.getAssetId() != null && atData.getAssetId() == 0) {
 | 
			
		||||
            mapBalanceModifications(
 | 
			
		||||
                    amountsByAddress,
 | 
			
		||||
                    atData.getAmount(),
 | 
			
		||||
                    creatorAddress,
 | 
			
		||||
                    Optional.of(atData.getRecipient())
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        return creatorAddress;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static PaymentTransactionData getPaymentTransactionData(MultiPaymentTransactionData multiPaymentData, PaymentData payment) {
 | 
			
		||||
        return new PaymentTransactionData(
 | 
			
		||||
                new BaseTransactionData(
 | 
			
		||||
                        multiPaymentData.getTimestamp(),
 | 
			
		||||
                        multiPaymentData.getTxGroupId(),
 | 
			
		||||
                        multiPaymentData.getReference(),
 | 
			
		||||
                        multiPaymentData.getCreatorPublicKey(),
 | 
			
		||||
                        0L,
 | 
			
		||||
                        multiPaymentData.getSignature()
 | 
			
		||||
                ),
 | 
			
		||||
                payment.getRecipient(),
 | 
			
		||||
                payment.getAmount()
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void mapBalanceModifications(Map<String, Long> amountsByAddress, Long amount, String sender, Optional<String> recipient) {
 | 
			
		||||
        amountsByAddress.put(
 | 
			
		||||
                sender,
 | 
			
		||||
            amountsByAddress.getOrDefault(sender, 0L) - amount
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        if( recipient.isPresent() )
 | 
			
		||||
            amountsByAddress.put(
 | 
			
		||||
                recipient.get(),
 | 
			
		||||
                amountsByAddress.getOrDefault(recipient.get(), 0L) + amount
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void removeRecordingsAboveHeight(int currentHeight, ConcurrentHashMap<Integer, List<AccountBalanceData>> balancesByHeight) {
 | 
			
		||||
        balancesByHeight.entrySet().stream()
 | 
			
		||||
            .filter(heightWithBalances -> heightWithBalances.getKey() > currentHeight)
 | 
			
		||||
@@ -116,4 +297,23 @@ public class BalanceRecorderUtils {
 | 
			
		||||
                .sorted(Comparator.reverseOrder()).findFirst();
 | 
			
		||||
        return priorHeight;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Is Reward Distribution Range?
 | 
			
		||||
     *
 | 
			
		||||
     * @param start start height, exclusive
 | 
			
		||||
     * @param end end height, inclusive
 | 
			
		||||
     *
 | 
			
		||||
     * @return true there is a reward distribution block within this block range
 | 
			
		||||
     */
 | 
			
		||||
    public static boolean isRewardDistributionRange(int start, int end) {
 | 
			
		||||
 | 
			
		||||
        // iterate through the block height until a reward distribution block or the end of the range
 | 
			
		||||
        for( int i = start + 1; i <= end; i++) {
 | 
			
		||||
            if( Block.isRewardDistributionBlock(i) ) return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // no reward distribution blocks found within range
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,13 +2,27 @@ package org.qortal.test.utils;
 | 
			
		||||
 | 
			
		||||
import org.junit.Assert;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
import org.qortal.asset.Asset;
 | 
			
		||||
import org.qortal.block.Block;
 | 
			
		||||
import org.qortal.crypto.Crypto;
 | 
			
		||||
import org.qortal.data.PaymentData;
 | 
			
		||||
import org.qortal.data.account.AccountBalanceData;
 | 
			
		||||
import org.qortal.data.account.AddressAmountData;
 | 
			
		||||
import org.qortal.data.account.BlockHeightRange;
 | 
			
		||||
import org.qortal.data.account.BlockHeightRangeAddressAmounts;
 | 
			
		||||
import org.qortal.data.transaction.ATTransactionData;
 | 
			
		||||
import org.qortal.data.transaction.BaseTransactionData;
 | 
			
		||||
import org.qortal.data.transaction.BuyNameTransactionData;
 | 
			
		||||
import org.qortal.data.transaction.DeployAtTransactionData;
 | 
			
		||||
import org.qortal.data.transaction.MultiPaymentTransactionData;
 | 
			
		||||
import org.qortal.data.transaction.PaymentTransactionData;
 | 
			
		||||
import org.qortal.data.transaction.RegisterNameTransactionData;
 | 
			
		||||
import org.qortal.data.transaction.TransactionData;
 | 
			
		||||
import org.qortal.data.transaction.TransferAssetTransactionData;
 | 
			
		||||
import org.qortal.utils.BalanceRecorderUtils;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
@@ -18,6 +32,10 @@ import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
public class BalanceRecorderUtilsTests {
 | 
			
		||||
 | 
			
		||||
    public static final String RECIPIENT_ADDRESS = "recipient";
 | 
			
		||||
    public static final String AT_ADDRESS = "atAddress";
 | 
			
		||||
    public static final String OTHER = "Other";
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testNotZeroForZero() {
 | 
			
		||||
        boolean test = BalanceRecorderUtils.ADDRESS_AMOUNT_DATA_NOT_ZERO.test( new AddressAmountData("", 0));
 | 
			
		||||
@@ -42,8 +60,8 @@ public class BalanceRecorderUtilsTests {
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testAddressAmountComparatorReverseOrder() {
 | 
			
		||||
 | 
			
		||||
        BlockHeightRangeAddressAmounts addressAmounts1 = new BlockHeightRangeAddressAmounts(new BlockHeightRange(2, 3), new ArrayList<>(0));
 | 
			
		||||
        BlockHeightRangeAddressAmounts addressAmounts2 = new BlockHeightRangeAddressAmounts(new BlockHeightRange(1, 2), new ArrayList<>(0));
 | 
			
		||||
        BlockHeightRangeAddressAmounts addressAmounts1 = new BlockHeightRangeAddressAmounts(new BlockHeightRange(2, 3, false), new ArrayList<>(0));
 | 
			
		||||
        BlockHeightRangeAddressAmounts addressAmounts2 = new BlockHeightRangeAddressAmounts(new BlockHeightRange(1, 2, false), new ArrayList<>(0));
 | 
			
		||||
 | 
			
		||||
        int compare = BalanceRecorderUtils.BLOCK_HEIGHT_RANGE_ADDRESS_AMOUNTS_COMPARATOR.compare(addressAmounts1, addressAmounts2);
 | 
			
		||||
 | 
			
		||||
@@ -53,8 +71,8 @@ public class BalanceRecorderUtilsTests {
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testAddressAmountComparatorForwardOrder() {
 | 
			
		||||
 | 
			
		||||
        BlockHeightRangeAddressAmounts addressAmounts1 = new BlockHeightRangeAddressAmounts(new BlockHeightRange(1, 2), new ArrayList<>(0));
 | 
			
		||||
        BlockHeightRangeAddressAmounts addressAmounts2 = new BlockHeightRangeAddressAmounts(new BlockHeightRange(2, 3), new ArrayList<>(0));
 | 
			
		||||
        BlockHeightRangeAddressAmounts addressAmounts1 = new BlockHeightRangeAddressAmounts(new BlockHeightRange(1, 2, false), new ArrayList<>(0));
 | 
			
		||||
        BlockHeightRangeAddressAmounts addressAmounts2 = new BlockHeightRangeAddressAmounts(new BlockHeightRange(2, 3, false), new ArrayList<>(0));
 | 
			
		||||
 | 
			
		||||
        int compare = BalanceRecorderUtils.BLOCK_HEIGHT_RANGE_ADDRESS_AMOUNTS_COMPARATOR.compare(addressAmounts1, addressAmounts2);
 | 
			
		||||
 | 
			
		||||
@@ -124,7 +142,7 @@ public class BalanceRecorderUtilsTests {
 | 
			
		||||
        List<AccountBalanceData> priorBalances = new ArrayList<>(1);
 | 
			
		||||
        priorBalances.add(new AccountBalanceData(address, 0, 1));
 | 
			
		||||
 | 
			
		||||
        List<AddressAmountData> dynamics = BalanceRecorderUtils.buildBalanceDynamics(balances, priorBalances, 0);
 | 
			
		||||
        List<AddressAmountData> dynamics = BalanceRecorderUtils.buildBalanceDynamics(balances, priorBalances, 0, new ArrayList<>(0));
 | 
			
		||||
 | 
			
		||||
        Assert.assertNotNull(dynamics);
 | 
			
		||||
        Assert.assertEquals(1, dynamics.size());
 | 
			
		||||
@@ -145,7 +163,7 @@ public class BalanceRecorderUtilsTests {
 | 
			
		||||
 | 
			
		||||
        List<AccountBalanceData> priorBalances = new ArrayList<>(0);
 | 
			
		||||
 | 
			
		||||
        List<AddressAmountData> dynamics = BalanceRecorderUtils.buildBalanceDynamics(balances, priorBalances, 0);
 | 
			
		||||
        List<AddressAmountData> dynamics = BalanceRecorderUtils.buildBalanceDynamics(balances, priorBalances, 0, new ArrayList<>(0));
 | 
			
		||||
 | 
			
		||||
        Assert.assertNotNull(dynamics);
 | 
			
		||||
        Assert.assertEquals(1, dynamics.size());
 | 
			
		||||
@@ -156,6 +174,55 @@ public class BalanceRecorderUtilsTests {
 | 
			
		||||
        Assert.assertEquals(2, addressAmountData.getAmount());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testBuildBalanceDynamicOneAccountAdjustment() {
 | 
			
		||||
        List<AccountBalanceData> balances = new ArrayList<>(1);
 | 
			
		||||
        balances.add(new AccountBalanceData(RECIPIENT_ADDRESS, 0, 20));
 | 
			
		||||
 | 
			
		||||
        List<AccountBalanceData> priorBalances = new ArrayList<>(0);
 | 
			
		||||
        priorBalances.add(new AccountBalanceData(RECIPIENT_ADDRESS, 0, 12));
 | 
			
		||||
 | 
			
		||||
        List<TransactionData> transactions = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
        final long amount = 5L;
 | 
			
		||||
        final long fee = 1L;
 | 
			
		||||
 | 
			
		||||
        boolean exceptionThrown = false;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            byte[] creatorPublicKey = TestUtils.generatePublicKey();
 | 
			
		||||
 | 
			
		||||
            PaymentTransactionData paymentData
 | 
			
		||||
                = new PaymentTransactionData(
 | 
			
		||||
                    new BaseTransactionData(0L, 0, null, creatorPublicKey, fee, null),
 | 
			
		||||
                    RECIPIENT_ADDRESS,
 | 
			
		||||
                    amount
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            transactions.add(paymentData);
 | 
			
		||||
 | 
			
		||||
            List<AddressAmountData> dynamics
 | 
			
		||||
                = BalanceRecorderUtils.buildBalanceDynamics(
 | 
			
		||||
                    balances,
 | 
			
		||||
                    priorBalances,
 | 
			
		||||
                    0,
 | 
			
		||||
                    transactions
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            Assert.assertNotNull(dynamics);
 | 
			
		||||
            Assert.assertEquals(1, dynamics.size());
 | 
			
		||||
 | 
			
		||||
            AddressAmountData addressAmountData = dynamics.get(0);
 | 
			
		||||
            Assert.assertNotNull(addressAmountData);
 | 
			
		||||
            Assert.assertEquals(RECIPIENT_ADDRESS, addressAmountData.getAddress());
 | 
			
		||||
            Assert.assertEquals(3, addressAmountData.getAmount());
 | 
			
		||||
        } catch( Exception e ) {
 | 
			
		||||
            exceptionThrown = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Assert.assertFalse(exceptionThrown);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testBuildBalanceDynamicsTwoAccountsNegativeValues() {
 | 
			
		||||
 | 
			
		||||
@@ -170,7 +237,7 @@ public class BalanceRecorderUtilsTests {
 | 
			
		||||
        priorBalances.add(new AccountBalanceData(address2, 0, 200));
 | 
			
		||||
        priorBalances.add(new AccountBalanceData(address1, 0, 5000));
 | 
			
		||||
 | 
			
		||||
        List<AddressAmountData> dynamics = BalanceRecorderUtils.buildBalanceDynamics(balances, priorBalances, -100L);
 | 
			
		||||
        List<AddressAmountData> dynamics = BalanceRecorderUtils.buildBalanceDynamics(balances, priorBalances, -100L, new ArrayList<>(0));
 | 
			
		||||
 | 
			
		||||
        Assert.assertNotNull(dynamics);
 | 
			
		||||
        Assert.assertEquals(2, dynamics.size());
 | 
			
		||||
@@ -304,10 +371,10 @@ public class BalanceRecorderUtilsTests {
 | 
			
		||||
 | 
			
		||||
        CopyOnWriteArrayList<BlockHeightRangeAddressAmounts> dynamics = new CopyOnWriteArrayList<>();
 | 
			
		||||
 | 
			
		||||
        BlockHeightRange range1 = new BlockHeightRange(10, 20);
 | 
			
		||||
        BlockHeightRange range1 = new BlockHeightRange(10, 20, false);
 | 
			
		||||
        dynamics.add(new BlockHeightRangeAddressAmounts(range1, new ArrayList<>()));
 | 
			
		||||
 | 
			
		||||
        BlockHeightRange range2 = new BlockHeightRange(1, 4);
 | 
			
		||||
        BlockHeightRange range2 = new BlockHeightRange(1, 4, false);
 | 
			
		||||
        dynamics.add(new BlockHeightRangeAddressAmounts(range2, new ArrayList<>()));
 | 
			
		||||
 | 
			
		||||
        Assert.assertEquals(2, dynamics.size());
 | 
			
		||||
@@ -323,13 +390,13 @@ public class BalanceRecorderUtilsTests {
 | 
			
		||||
 | 
			
		||||
        CopyOnWriteArrayList<BlockHeightRangeAddressAmounts> dynamics = new CopyOnWriteArrayList<>();
 | 
			
		||||
 | 
			
		||||
        BlockHeightRange range1 = new BlockHeightRange(1,5);
 | 
			
		||||
        BlockHeightRange range1 = new BlockHeightRange(1,5, false);
 | 
			
		||||
        dynamics.add(new BlockHeightRangeAddressAmounts(range1, new ArrayList<>()));
 | 
			
		||||
 | 
			
		||||
        BlockHeightRange range2 = new BlockHeightRange(6, 11);
 | 
			
		||||
        BlockHeightRange range2 = new BlockHeightRange(6, 11, false);
 | 
			
		||||
        dynamics.add((new BlockHeightRangeAddressAmounts(range2, new ArrayList<>())));
 | 
			
		||||
 | 
			
		||||
        BlockHeightRange range3 = new BlockHeightRange(22, 16);
 | 
			
		||||
        BlockHeightRange range3 = new BlockHeightRange(22, 16, false);
 | 
			
		||||
        dynamics.add(new BlockHeightRangeAddressAmounts(range3, new ArrayList<>()));
 | 
			
		||||
 | 
			
		||||
        Assert.assertEquals(3, dynamics.size());
 | 
			
		||||
@@ -344,18 +411,353 @@ public class BalanceRecorderUtilsTests {
 | 
			
		||||
    public void testRemoveOldestDynamicsTwice() {
 | 
			
		||||
        CopyOnWriteArrayList<BlockHeightRangeAddressAmounts> dynamics = new CopyOnWriteArrayList<>();
 | 
			
		||||
 | 
			
		||||
        dynamics.add(new BlockHeightRangeAddressAmounts(new BlockHeightRange(1, 5), new ArrayList<>()));
 | 
			
		||||
        dynamics.add(new BlockHeightRangeAddressAmounts(new BlockHeightRange(5, 9), new ArrayList<>()));
 | 
			
		||||
        dynamics.add(new BlockHeightRangeAddressAmounts(new BlockHeightRange(1, 5, false), new ArrayList<>()));
 | 
			
		||||
        dynamics.add(new BlockHeightRangeAddressAmounts(new BlockHeightRange(5, 9, false), new ArrayList<>()));
 | 
			
		||||
 | 
			
		||||
        Assert.assertEquals(2, dynamics.size());
 | 
			
		||||
 | 
			
		||||
        BalanceRecorderUtils.removeOldestDynamics(dynamics);
 | 
			
		||||
 | 
			
		||||
        Assert.assertEquals(1, dynamics.size());
 | 
			
		||||
        Assert.assertTrue(dynamics.get(0).getRange().equals(new BlockHeightRange(5, 9)));
 | 
			
		||||
        Assert.assertTrue(dynamics.get(0).getRange().equals(new BlockHeightRange(5, 9, false)));
 | 
			
		||||
 | 
			
		||||
        BalanceRecorderUtils.removeOldestDynamics(dynamics);
 | 
			
		||||
 | 
			
		||||
        Assert.assertEquals(0, dynamics.size());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testMapBalanceModificationsForPaymentTransaction() {
 | 
			
		||||
 | 
			
		||||
        boolean exceptionThrown = false;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            final long amount = 1L;
 | 
			
		||||
            final long fee = 1L;
 | 
			
		||||
 | 
			
		||||
            byte[] creatorPublicKey = TestUtils.generatePublicKey();
 | 
			
		||||
 | 
			
		||||
            PaymentTransactionData paymentData
 | 
			
		||||
                = new PaymentTransactionData(
 | 
			
		||||
                    new BaseTransactionData(0L, 0, null, creatorPublicKey, fee, null),
 | 
			
		||||
                    RECIPIENT_ADDRESS,
 | 
			
		||||
                    amount
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            // map balance modifications for addresses in the transaction
 | 
			
		||||
            Map<String, Long> amountsByAddress = new HashMap<>();
 | 
			
		||||
            BalanceRecorderUtils.mapBalanceModicationsForPaymentTransaction(amountsByAddress, paymentData);
 | 
			
		||||
 | 
			
		||||
            // this will not add the fee, that is done in a different place
 | 
			
		||||
            assertAmountsByAddress(amountsByAddress, amount, creatorPublicKey, RECIPIENT_ADDRESS);
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            exceptionThrown = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Assert.assertFalse(exceptionThrown);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testMapBalanceModificationsForAssetOrderTransaction() {
 | 
			
		||||
 | 
			
		||||
        boolean exceptionThrown = false;
 | 
			
		||||
 | 
			
		||||
        try{
 | 
			
		||||
            final long amount = 1L;
 | 
			
		||||
            final long fee = 1L;
 | 
			
		||||
 | 
			
		||||
            byte[] creatorPublicKey = TestUtils.generatePublicKey();
 | 
			
		||||
 | 
			
		||||
            TransferAssetTransactionData transferAssetData
 | 
			
		||||
             = new TransferAssetTransactionData(
 | 
			
		||||
                     new BaseTransactionData(0L, 0, null, creatorPublicKey, fee, null),
 | 
			
		||||
                    RECIPIENT_ADDRESS,
 | 
			
		||||
                    amount,
 | 
			
		||||
                    0
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            // map balance modifications for addresses in the transaction
 | 
			
		||||
            Map<String, Long> amountsByAddress = new HashMap<>();
 | 
			
		||||
            BalanceRecorderUtils.mapBalanceModificationsForTransferAssetTransaction(amountsByAddress, transferAssetData);
 | 
			
		||||
 | 
			
		||||
            assertAmountsByAddress(amountsByAddress, amount, creatorPublicKey, RECIPIENT_ADDRESS);
 | 
			
		||||
        } catch( Exception e) {
 | 
			
		||||
            exceptionThrown = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Assert.assertFalse(exceptionThrown);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testMapBalanceModificationsForATTransactionMessageType() {
 | 
			
		||||
 | 
			
		||||
        boolean exceptionThrown = false;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
 | 
			
		||||
            final long fee = 1L;
 | 
			
		||||
 | 
			
		||||
            byte[] creatorPublicKey = TestUtils.generatePublicKey();
 | 
			
		||||
            Map<String, Long> amountsByAddress = new HashMap<>();
 | 
			
		||||
 | 
			
		||||
            ATTransactionData atTransactionData = new ATTransactionData(new BaseTransactionData(0L, 0, null, creatorPublicKey, fee, null),
 | 
			
		||||
                    AT_ADDRESS,
 | 
			
		||||
                    RECIPIENT_ADDRESS,
 | 
			
		||||
                    new byte[0]);
 | 
			
		||||
            BalanceRecorderUtils.mapBalanceModificationsForAtTransaction( amountsByAddress, atTransactionData);
 | 
			
		||||
 | 
			
		||||
            // no balance changes for AT message
 | 
			
		||||
            Assert.assertTrue(amountsByAddress.size() == 0);
 | 
			
		||||
        } catch( Exception e) {
 | 
			
		||||
            exceptionThrown = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Assert.assertFalse(exceptionThrown);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testMapBalanceModificationsForATTransactionPaymentType() {
 | 
			
		||||
 | 
			
		||||
        boolean exceptionThrown = false;
 | 
			
		||||
 | 
			
		||||
        try{
 | 
			
		||||
            final long amount = 1L;
 | 
			
		||||
            final long fee = 1L;
 | 
			
		||||
 | 
			
		||||
            byte[] creatorPublicKey = TestUtils.generatePublicKey();
 | 
			
		||||
 | 
			
		||||
            Map<String, Long> amountsByAddress = new HashMap<>();
 | 
			
		||||
 | 
			
		||||
            ATTransactionData atTransactionData
 | 
			
		||||
                = new ATTransactionData(
 | 
			
		||||
                    new BaseTransactionData(0L, 0, null, creatorPublicKey, fee, null),
 | 
			
		||||
                    AT_ADDRESS,
 | 
			
		||||
                    RECIPIENT_ADDRESS,
 | 
			
		||||
                    amount,
 | 
			
		||||
                    0
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
            BalanceRecorderUtils.mapBalanceModificationsForAtTransaction( amountsByAddress, atTransactionData);
 | 
			
		||||
 | 
			
		||||
            assertAmountByAddress(amountsByAddress, amount, RECIPIENT_ADDRESS);
 | 
			
		||||
 | 
			
		||||
            assertAmountByAddress(amountsByAddress, -amount, AT_ADDRESS);
 | 
			
		||||
        } catch( Exception e) {
 | 
			
		||||
            exceptionThrown = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Assert.assertFalse(exceptionThrown);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testMapBalanceModificationsForBuyNameTransaction() {
 | 
			
		||||
 | 
			
		||||
        boolean exceptionThrown = false;
 | 
			
		||||
 | 
			
		||||
        try{
 | 
			
		||||
            final long amount = 100L;
 | 
			
		||||
            final long fee = 1L;
 | 
			
		||||
 | 
			
		||||
            byte[] creatorPublicKey = TestUtils.generatePublicKey();
 | 
			
		||||
            Map<String, Long> amountsByAddress = new HashMap<>();
 | 
			
		||||
 | 
			
		||||
            BuyNameTransactionData buyNameData
 | 
			
		||||
                = new BuyNameTransactionData(
 | 
			
		||||
                    new BaseTransactionData(0L, 0, null, creatorPublicKey, fee, null),
 | 
			
		||||
                    "null",
 | 
			
		||||
                    amount,
 | 
			
		||||
                    RECIPIENT_ADDRESS
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            BalanceRecorderUtils.mapBalanceModificationsForBuyNameTransaction(amountsByAddress, buyNameData);
 | 
			
		||||
 | 
			
		||||
            assertAmountsByAddress(amountsByAddress, amount, creatorPublicKey, RECIPIENT_ADDRESS);
 | 
			
		||||
        } catch( Exception e) {
 | 
			
		||||
            exceptionThrown = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Assert.assertFalse(exceptionThrown);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testMapBalanceModificationsForMultiPaymentTransaction() {
 | 
			
		||||
 | 
			
		||||
        boolean exceptionThrown = false;
 | 
			
		||||
 | 
			
		||||
        try{
 | 
			
		||||
            final long amount = 100L;
 | 
			
		||||
            final long fee = 1L;
 | 
			
		||||
 | 
			
		||||
            byte[] creatorPublicKey = TestUtils.generatePublicKey();
 | 
			
		||||
            Map<String, Long> amountsByAddress = new HashMap<>();
 | 
			
		||||
 | 
			
		||||
            List<PaymentData> payments = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
            payments.add(new PaymentData(RECIPIENT_ADDRESS, 0, amount));
 | 
			
		||||
 | 
			
		||||
            MultiPaymentTransactionData multiPayment
 | 
			
		||||
                    = new MultiPaymentTransactionData(new BaseTransactionData(0L, 0, null, creatorPublicKey, fee, null),
 | 
			
		||||
                    payments);
 | 
			
		||||
            BalanceRecorderUtils.mapBalanceModificationsForMultiPaymentTransaction(amountsByAddress,multiPayment);
 | 
			
		||||
            assertAmountsByAddress(amountsByAddress, amount, creatorPublicKey, RECIPIENT_ADDRESS);
 | 
			
		||||
        } catch( Exception e ) {
 | 
			
		||||
            exceptionThrown = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Assert.assertFalse(exceptionThrown);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testMapBalanceModificationsForMultiPaymentTransaction2PaymentsOneAddress() {
 | 
			
		||||
 | 
			
		||||
        boolean exceptionThrown = false;
 | 
			
		||||
 | 
			
		||||
        try{
 | 
			
		||||
            final long amount = 100L;
 | 
			
		||||
            final long fee = 1L;
 | 
			
		||||
 | 
			
		||||
            byte[] creatorPublicKey = TestUtils.generatePublicKey();
 | 
			
		||||
            Map<String, Long> amountsByAddress = new HashMap<>();
 | 
			
		||||
 | 
			
		||||
            List<PaymentData> payments = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
            payments.add(new PaymentData(RECIPIENT_ADDRESS, 0, amount));
 | 
			
		||||
            payments.add(new PaymentData(RECIPIENT_ADDRESS, 0, amount));
 | 
			
		||||
 | 
			
		||||
            MultiPaymentTransactionData multiPayment
 | 
			
		||||
                    = new MultiPaymentTransactionData(new BaseTransactionData(0L, 0, null, creatorPublicKey, fee, null),
 | 
			
		||||
                    payments);
 | 
			
		||||
            BalanceRecorderUtils.mapBalanceModificationsForMultiPaymentTransaction(amountsByAddress,multiPayment);
 | 
			
		||||
            assertAmountsByAddress(amountsByAddress, 2*amount, creatorPublicKey, RECIPIENT_ADDRESS);
 | 
			
		||||
        } catch( Exception e ) {
 | 
			
		||||
            exceptionThrown = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Assert.assertFalse(exceptionThrown);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testMapBalanceModificationsForMultiPaymentTransaction2PaymentsTwoAddresses() {
 | 
			
		||||
 | 
			
		||||
        boolean exceptionThrown = false;
 | 
			
		||||
 | 
			
		||||
        try{
 | 
			
		||||
            final long amount = 100L;
 | 
			
		||||
            final long fee = 1L;
 | 
			
		||||
 | 
			
		||||
            byte[] creatorPublicKey = TestUtils.generatePublicKey();
 | 
			
		||||
            Map<String, Long> amountsByAddress = new HashMap<>();
 | 
			
		||||
 | 
			
		||||
            List<PaymentData> payments = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
            payments.add(new PaymentData(RECIPIENT_ADDRESS, 0, amount));
 | 
			
		||||
            payments.add(new PaymentData(OTHER, 0, amount));
 | 
			
		||||
 | 
			
		||||
            MultiPaymentTransactionData multiPayment
 | 
			
		||||
                    = new MultiPaymentTransactionData(new BaseTransactionData(0L, 0, null, creatorPublicKey, fee, null),
 | 
			
		||||
                    payments);
 | 
			
		||||
            BalanceRecorderUtils.mapBalanceModificationsForMultiPaymentTransaction(amountsByAddress,multiPayment);
 | 
			
		||||
            assertAmountByAddress(amountsByAddress, amount, RECIPIENT_ADDRESS);
 | 
			
		||||
            assertAmountByAddress(amountsByAddress, amount, OTHER);
 | 
			
		||||
 | 
			
		||||
            String creatorAddress = Crypto.toAddress(creatorPublicKey);
 | 
			
		||||
 | 
			
		||||
            assertAmountByAddress(amountsByAddress, 2*-amount, creatorAddress);
 | 
			
		||||
        } catch( Exception e ) {
 | 
			
		||||
            exceptionThrown = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Assert.assertFalse(exceptionThrown);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testMapBalanceModificationsForDeployAtTransaction() {
 | 
			
		||||
 | 
			
		||||
        boolean exceptionThrown = false;
 | 
			
		||||
 | 
			
		||||
        try{
 | 
			
		||||
            final long amount = 3L;
 | 
			
		||||
            final long fee = 1L;
 | 
			
		||||
 | 
			
		||||
            byte[] creatorPublicKey = TestUtils.generatePublicKey();
 | 
			
		||||
            Map<String, Long> amountsByAddress = new HashMap<>();
 | 
			
		||||
 | 
			
		||||
            DeployAtTransactionData deployAt
 | 
			
		||||
                = new DeployAtTransactionData(
 | 
			
		||||
                    new BaseTransactionData(0L, 0, null, creatorPublicKey, fee, null),
 | 
			
		||||
                    AT_ADDRESS, "name", "description", "type", "tags", new byte[0], amount, Asset.QORT
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
            BalanceRecorderUtils.mapBalanceModificationsForDeployAtTransaction(amountsByAddress,deployAt);
 | 
			
		||||
            assertAmountsByAddress(amountsByAddress, amount, creatorPublicKey, AT_ADDRESS);
 | 
			
		||||
        } catch( Exception e) {
 | 
			
		||||
            exceptionThrown = true;
 | 
			
		||||
            e.printStackTrace();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Assert.assertFalse(exceptionThrown);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testMapBalanceModificationsForTransaction() {
 | 
			
		||||
 | 
			
		||||
        boolean exceptionThrown = false;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            final long fee = 2;
 | 
			
		||||
 | 
			
		||||
            byte[] creatorPublicKey = TestUtils.generatePublicKey();
 | 
			
		||||
            Map<String, Long> amountsByAddress = new HashMap<>();
 | 
			
		||||
 | 
			
		||||
            BalanceRecorderUtils.mapBalanceModificationsForTransaction(
 | 
			
		||||
                    amountsByAddress,
 | 
			
		||||
                    new RegisterNameTransactionData(
 | 
			
		||||
                            new BaseTransactionData(0L, 0, null, creatorPublicKey, fee, null),
 | 
			
		||||
                            "aaa", "data", "aaa")
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            String creatorAddress = Crypto.toAddress(creatorPublicKey);
 | 
			
		||||
 | 
			
		||||
            assertAmountByAddress(amountsByAddress, -fee, creatorAddress);
 | 
			
		||||
        } catch(Exception e) {
 | 
			
		||||
            exceptionThrown = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Assert.assertFalse(exceptionThrown);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testBlockHeightRangeEqualityTrue() {
 | 
			
		||||
 | 
			
		||||
        BlockHeightRange range1 = new BlockHeightRange(2, 4, false);
 | 
			
		||||
        BlockHeightRange range2 = new BlockHeightRange(2, 4, true);
 | 
			
		||||
 | 
			
		||||
        Assert.assertTrue(range1.equals(range2));
 | 
			
		||||
        Assert.assertEquals(range1, range2);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testBloHeightRangeEqualityFalse() {
 | 
			
		||||
 | 
			
		||||
        BlockHeightRange range1 = new BlockHeightRange(2, 3, true);
 | 
			
		||||
        BlockHeightRange range2 = new BlockHeightRange(2, 4, true);
 | 
			
		||||
 | 
			
		||||
        Assert.assertFalse(range1.equals(range2));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void assertAmountsByAddress(Map<String, Long> amountsByAddress, long amount, byte[] creatorPublicKey, String recipientAddress) {
 | 
			
		||||
        assertAmountByAddress(amountsByAddress, amount, recipientAddress);
 | 
			
		||||
 | 
			
		||||
        String creatorAddress = Crypto.toAddress(creatorPublicKey);
 | 
			
		||||
 | 
			
		||||
        assertAmountByAddress(amountsByAddress, -amount, creatorAddress);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void assertAmountByAddress(Map<String, Long> amountsByAddress, long amount, String address) {
 | 
			
		||||
        Long amountForAddress = amountsByAddress.get(address);
 | 
			
		||||
 | 
			
		||||
        Assert.assertTrue(amountsByAddress.containsKey(address));
 | 
			
		||||
        Assert.assertNotNull(amountForAddress);
 | 
			
		||||
        Assert.assertEquals(amount, amountForAddress.longValue());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										48
									
								
								src/test/java/org/qortal/test/utils/TestUtils.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/test/java/org/qortal/test/utils/TestUtils.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
			
		||||
package org.qortal.test.utils;
 | 
			
		||||
 | 
			
		||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
 | 
			
		||||
 | 
			
		||||
import java.security.KeyPair;
 | 
			
		||||
import java.security.KeyPairGenerator;
 | 
			
		||||
import java.security.MessageDigest;
 | 
			
		||||
import java.security.PublicKey;
 | 
			
		||||
import java.security.Security;
 | 
			
		||||
 | 
			
		||||
public class TestUtils {
 | 
			
		||||
    public static byte[] generatePublicKey() throws Exception {
 | 
			
		||||
        // Add the Bouncy Castle provider
 | 
			
		||||
        Security.addProvider(new BouncyCastleProvider());
 | 
			
		||||
 | 
			
		||||
        // Generate a key pair
 | 
			
		||||
        KeyPair keyPair = generateKeyPair();
 | 
			
		||||
 | 
			
		||||
        // Get the public key
 | 
			
		||||
        PublicKey publicKey = keyPair.getPublic();
 | 
			
		||||
 | 
			
		||||
        // Get the public key as a byte array
 | 
			
		||||
        byte[] publicKeyBytes = publicKey.getEncoded();
 | 
			
		||||
 | 
			
		||||
        // Generate a RIPEMD160 message digest from the public key
 | 
			
		||||
        byte[] ripeMd160Digest = generateRipeMd160Digest(publicKeyBytes);
 | 
			
		||||
 | 
			
		||||
        return ripeMd160Digest;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static KeyPair generateKeyPair() throws Exception {
 | 
			
		||||
        // Generate a key pair using the RSA algorithm
 | 
			
		||||
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
 | 
			
		||||
        keyGen.initialize(2048); // Key size (bits)
 | 
			
		||||
        return keyGen.generateKeyPair();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static byte[] generateRipeMd160Digest(byte[] input) throws Exception {
 | 
			
		||||
        // Create a RIPEMD160 message digest instance
 | 
			
		||||
        MessageDigest ripeMd160 = MessageDigest.getInstance("RIPEMD160", new BouncyCastleProvider());
 | 
			
		||||
 | 
			
		||||
        // Update the message digest with the input bytes
 | 
			
		||||
        ripeMd160.update(input);
 | 
			
		||||
 | 
			
		||||
        // Get the message digest bytes
 | 
			
		||||
        return ripeMd160.digest();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user