From d05359dfa9d38f51038d8f48bdd8fe3261241557 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 7 Feb 2024 16:52:12 -0600 Subject: [PATCH 1/3] Add debug logging when invalid timestamp is encountered --- src/main/java/org/qortal/block/Block.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/qortal/block/Block.java b/src/main/java/org/qortal/block/Block.java index 22e6e9f1..baa56179 100644 --- a/src/main/java/org/qortal/block/Block.java +++ b/src/main/java/org/qortal/block/Block.java @@ -1061,8 +1061,10 @@ public class Block { return ValidationResult.MINTER_NOT_ACCEPTED; long expectedTimestamp = calcTimestamp(parentBlockData, this.blockData.getMinterPublicKey(), minterLevel); - if (this.blockData.getTimestamp() != expectedTimestamp) - return ValidationResult.TIMESTAMP_INCORRECT; + if (this.blockData.getTimestamp() != expectedTimestamp) { + LOGGER.debug(String.format("timestamp mismatch! block had %s but we expected %s", this.blockData.getTimestamp(), expectedTimestamp)); + return ValidationResult.TIMESTAMP_INCORRECT; + } return ValidationResult.OK; } From 2d27901f9f3f842c999da3ce4d393106a1cc673c Mon Sep 17 00:00:00 2001 From: AlphaX-Projects <77661270+AlphaX-Projects@users.noreply.github.com> Date: Sun, 18 Feb 2024 18:12:43 +0100 Subject: [PATCH 2/3] Adding new algos --- .../qortal/account/SelfSponsorshipAlgoV2.java | 249 +++ .../qortal/account/SelfSponsorshipAlgoV3.java | 370 ++++ src/main/java/org/qortal/block/Block.java | 22 +- .../java/org/qortal/block/BlockChain.java | 20 + .../block/SelfSponsorshipAlgoV2Block.java | 143 ++ .../block/SelfSponsorshipAlgoV3Block.java | 136 ++ .../org/qortal/crosschain/PirateChain.java | 7 +- .../repository/TransactionRepository.java | 9 + .../HSQLDBTransactionRepository.java | 27 + .../java/org/qortal/settings/Settings.java | 2 +- .../transaction/RegisterNameTransaction.java | 8 + .../transaction/RewardShareTransaction.java | 16 + .../org/qortal/transaction/Transaction.java | 2 + .../transaction/TransferAssetTransaction.java | 9 + src/main/resources/blockchain.json | 7 +- .../i18n/TransactionValidity_de.properties | 2 + .../i18n/TransactionValidity_en.properties | 2 + .../i18n/TransactionValidity_es.properties | 2 + .../i18n/TransactionValidity_fi.properties | 2 + .../i18n/TransactionValidity_fr.properties | 2 + .../i18n/TransactionValidity_he.properties | 2 + .../i18n/TransactionValidity_hu.properties | 2 + .../i18n/TransactionValidity_it.properties | 2 + .../i18n/TransactionValidity_jp.properties | 2 + .../i18n/TransactionValidity_ko.properties | 2 + .../i18n/TransactionValidity_nl.properties | 2 + .../i18n/TransactionValidity_pl.properties | 2 + .../i18n/TransactionValidity_ro.properties | 2 + .../i18n/TransactionValidity_ru.properties | 2 + .../i18n/TransactionValidity_sv.properties | 2 + .../i18n/TransactionValidity_zh_CN.properties | 2 + .../i18n/TransactionValidity_zh_TW.properties | 2 + .../test/SelfSponsorshipAlgoV2Tests.java | 342 ++++ .../test/SelfSponsorshipAlgoV3Tests.java | 1578 +++++++++++++++++ .../test-chain-v2-block-timestamps.json | 8 +- .../test-chain-v2-disable-reference.json | 8 +- .../test-chain-v2-founder-rewards.json | 8 +- .../test-chain-v2-leftover-reward.json | 8 +- src/test/resources/test-chain-v2-minting.json | 8 +- .../test-chain-v2-qora-holder-extremes.json | 8 +- .../test-chain-v2-qora-holder-reduction.json | 8 +- .../resources/test-chain-v2-qora-holder.json | 8 +- .../test-chain-v2-reward-levels.json | 8 +- .../test-chain-v2-reward-scaling.json | 8 +- .../test-chain-v2-reward-shares.json | 8 +- ...est-chain-v2-self-sponsorship-algo-v1.json | 8 +- ...est-chain-v2-self-sponsorship-algo-v2.json | 132 ++ ...est-chain-v2-self-sponsorship-algo-v3.json | 132 ++ src/test/resources/test-chain-v2.json | 8 +- ...-settings-v2-self-sponsorship-algo-v2.json | 20 + ...-settings-v2-self-sponsorship-algo-v3.json | 20 + 51 files changed, 3343 insertions(+), 46 deletions(-) create mode 100644 src/main/java/org/qortal/account/SelfSponsorshipAlgoV2.java create mode 100644 src/main/java/org/qortal/account/SelfSponsorshipAlgoV3.java create mode 100644 src/main/java/org/qortal/block/SelfSponsorshipAlgoV2Block.java create mode 100644 src/main/java/org/qortal/block/SelfSponsorshipAlgoV3Block.java create mode 100644 src/test/java/org/qortal/test/SelfSponsorshipAlgoV2Tests.java create mode 100644 src/test/java/org/qortal/test/SelfSponsorshipAlgoV3Tests.java create mode 100644 src/test/resources/test-chain-v2-self-sponsorship-algo-v2.json create mode 100644 src/test/resources/test-chain-v2-self-sponsorship-algo-v3.json create mode 100644 src/test/resources/test-settings-v2-self-sponsorship-algo-v2.json create mode 100644 src/test/resources/test-settings-v2-self-sponsorship-algo-v3.json diff --git a/src/main/java/org/qortal/account/SelfSponsorshipAlgoV2.java b/src/main/java/org/qortal/account/SelfSponsorshipAlgoV2.java new file mode 100644 index 00000000..9052257c --- /dev/null +++ b/src/main/java/org/qortal/account/SelfSponsorshipAlgoV2.java @@ -0,0 +1,249 @@ +package org.qortal.account; + +import org.qortal.api.resource.TransactionsResource; +import org.qortal.asset.Asset; +import org.qortal.block.BlockChain; +import org.qortal.data.account.AccountData; +import org.qortal.data.transaction.*; +import org.qortal.repository.DataException; +import org.qortal.repository.Repository; +import org.qortal.transaction.Transaction.TransactionType; + +import java.util.*; + +public class SelfSponsorshipAlgoV2 { + + private final long snapshotTimestampV1 = BlockChain.getInstance().getSelfSponsorshipAlgoV1SnapshotTimestamp(); + private final long snapshotTimestampV2 = BlockChain.getInstance().getSelfSponsorshipAlgoV2SnapshotTimestamp(); + private final long referenceTimestamp = BlockChain.getInstance().getReferenceTimestampBlock(); + + private final boolean override; + private final Repository repository; + private final String address; + + private int recentAssetSendCount = 0; + private int recentSponsorshipCount = 0; + + private final Set assetAddresses = new LinkedHashSet<>(); + private final Set penaltyAddresses = new LinkedHashSet<>(); + private final Set sponsorAddresses = new LinkedHashSet<>(); + + private List sponsorshipRewardShares = new ArrayList<>(); + private List transferAssetForAddress = new ArrayList<>(); + + public SelfSponsorshipAlgoV2(Repository repository, String address, boolean override) { + this.repository = repository; + this.address = address; + this.override = override; + } + + public String getAddress() { + return this.address; + } + + public Set getPenaltyAddresses() { + return this.penaltyAddresses; + } + + public void run() throws DataException { + if (!override) { + this.getAccountPrivs(this.address); + } + + if (override) { + this.fetchTransferAssetForAddress(this.address); + this.findRecentAssetSendCount(); + + if (this.recentAssetSendCount >= 6) { + this.penaltyAddresses.add(this.address); + this.penaltyAddresses.addAll(this.assetAddresses); + } + } + } + + private void getAccountPrivs(String address) throws DataException { + AccountData accountData = this.repository.getAccountRepository().getAccount(address); + List transferPrivsTransactions = fetchTransferPrivsForAddress(address); + transferPrivsTransactions.removeIf(t -> t.getTimestamp() > this.referenceTimestamp || accountData.getAddress().equals(t.getRecipient())); + + if (transferPrivsTransactions.isEmpty()) { + // Nothing to do + return; + } + + for (TransactionData transactionData : transferPrivsTransactions) { + TransferPrivsTransactionData transferPrivsTransactionData = (TransferPrivsTransactionData) transactionData; + this.penaltyAddresses.add(transferPrivsTransactionData.getRecipient()); + this.fetchSponsorshipRewardShares(transferPrivsTransactionData.getRecipient()); + this.findRecentSponsorshipCount(); + + if (this.recentSponsorshipCount >= 1) { + this.penaltyAddresses.addAll(this.sponsorAddresses); + } + + String newAddress = this.getDestinationAccount(transferPrivsTransactionData.getRecipient()); + + while (newAddress != null) { + // Found destination account + this.penaltyAddresses.add(newAddress); + this.fetchSponsorshipRewardShares(newAddress); + this.findRecentSponsorshipCount(); + + if (this.recentSponsorshipCount >= 1) { + this.penaltyAddresses.addAll(this.sponsorAddresses); + } + + newAddress = this.getDestinationAccount(newAddress); + } + } + } + + private String getDestinationAccount(String address) throws DataException { + AccountData accountData = this.repository.getAccountRepository().getAccount(address); + List transferPrivsTransactions = fetchTransferPrivsForAddress(address); + transferPrivsTransactions.removeIf(t -> t.getTimestamp() > this.referenceTimestamp || accountData.getAddress().equals(t.getRecipient())); + + if (transferPrivsTransactions.isEmpty()) { + return null; + } + + if (accountData == null) { + return null; + } + + for (TransactionData transactionData : transferPrivsTransactions) { + TransferPrivsTransactionData transferPrivsTransactionData = (TransferPrivsTransactionData) transactionData; + if (Arrays.equals(transferPrivsTransactionData.getSenderPublicKey(), accountData.getPublicKey())) { + return transferPrivsTransactionData.getRecipient(); + } + } + + return null; + } + + private void fetchSponsorshipRewardShares(String address) throws DataException { + AccountData accountDataRs = this.repository.getAccountRepository().getAccount(address); + List sponsorshipRewardShares = new ArrayList<>(); + + // Define relevant transactions + List txTypes = List.of(TransactionType.REWARD_SHARE); + List transactionDataList = fetchTransactions(repository, txTypes, address, false); + + for (TransactionData transactionData : transactionDataList) { + if (transactionData.getType() != TransactionType.REWARD_SHARE) { + continue; + } + + RewardShareTransactionData rewardShareTransactionData = (RewardShareTransactionData) transactionData; + + // Skip removals + if (rewardShareTransactionData.getSharePercent() < 0) { + continue; + } + + // Skip if not sponsored by this account + if (!Arrays.equals(rewardShareTransactionData.getCreatorPublicKey(), accountDataRs.getPublicKey())) { + continue; + } + + // Skip self shares + if (Objects.equals(rewardShareTransactionData.getRecipient(), address)) { + continue; + } + + boolean duplicateFound = false; + for (RewardShareTransactionData existingRewardShare : sponsorshipRewardShares) { + if (Objects.equals(existingRewardShare.getRecipient(), rewardShareTransactionData.getRecipient())) { + // Duplicate + duplicateFound = true; + break; + } + } + + if (!duplicateFound) { + sponsorshipRewardShares.add(rewardShareTransactionData); + this.sponsorAddresses.add(rewardShareTransactionData.getRecipient()); + } + } + + this.sponsorshipRewardShares = sponsorshipRewardShares; + } + + private void fetchTransferAssetForAddress(String address) throws DataException { + List transferAssetForAddress = new ArrayList<>(); + + // Define relevant transactions + List txTypes = List.of(TransactionType.TRANSFER_ASSET); + List transactionDataList = fetchTransactions(repository, txTypes, address, false); + transactionDataList.removeIf(t -> t.getTimestamp() <= this.snapshotTimestampV1 || t.getTimestamp() >= this.snapshotTimestampV2); + + for (TransactionData transactionData : transactionDataList) { + if (transactionData.getType() != TransactionType.TRANSFER_ASSET) { + continue; + } + + TransferAssetTransactionData transferAssetTransactionData = (TransferAssetTransactionData) transactionData; + + if (transferAssetTransactionData.getAssetId() == Asset.QORT) { + if (!Objects.equals(transferAssetTransactionData.getRecipient(), address)) { + // Outgoing transfer asset for this account + transferAssetForAddress.add(transferAssetTransactionData); + this.assetAddresses.add(transferAssetTransactionData.getRecipient()); + } + } + } + + this.transferAssetForAddress = transferAssetForAddress; + } + + private void findRecentSponsorshipCount() { + int recentSponsorshipCount = 0; + + for (RewardShareTransactionData rewardShare : sponsorshipRewardShares) { + if (rewardShare.getTimestamp() >= this.snapshotTimestampV1) { + recentSponsorshipCount++; + } + } + + this.recentSponsorshipCount = recentSponsorshipCount; + } + + private void findRecentAssetSendCount() { + int recentAssetSendCount = 0; + + for (TransferAssetTransactionData assetSend : transferAssetForAddress) { + if (assetSend.getTimestamp() >= this.snapshotTimestampV1) { + recentAssetSendCount++; + } + } + + this.recentAssetSendCount = recentAssetSendCount; + } + + private List fetchTransferPrivsForAddress(String address) throws DataException { + return fetchTransactions(repository, + List.of(TransactionType.TRANSFER_PRIVS), + address, true); + } + + private static List fetchTransactions(Repository repository, List txTypes, String address, boolean reverse) throws DataException { + // Fetch all relevant transactions for this account + List signatures = repository.getTransactionRepository() + .getSignaturesMatchingCriteria(null, null, null, txTypes, + null, null, address, TransactionsResource.ConfirmationStatus.CONFIRMED, + null, null, reverse); + + List transactionDataList = new ArrayList<>(); + + for (byte[] signature : signatures) { + // Fetch transaction data + TransactionData transactionData = repository.getTransactionRepository().fromSignature(signature); + if (transactionData == null) { + continue; + } + transactionDataList.add(transactionData); + } + + return transactionDataList; + } +} \ No newline at end of file diff --git a/src/main/java/org/qortal/account/SelfSponsorshipAlgoV3.java b/src/main/java/org/qortal/account/SelfSponsorshipAlgoV3.java new file mode 100644 index 00000000..80c1ba2f --- /dev/null +++ b/src/main/java/org/qortal/account/SelfSponsorshipAlgoV3.java @@ -0,0 +1,370 @@ +package org.qortal.account; + +import org.qortal.api.resource.TransactionsResource; +import org.qortal.asset.Asset; +import org.qortal.data.account.AccountData; +import org.qortal.data.naming.NameData; +import org.qortal.data.transaction.*; +import org.qortal.repository.DataException; +import org.qortal.repository.Repository; +import org.qortal.transaction.Transaction.TransactionType; + +import java.util.*; +import java.util.stream.Collectors; + +public class SelfSponsorshipAlgoV3 { + + private final Repository repository; + private final String address; + private final AccountData accountData; + private final long snapshotTimestampV1; + private final long snapshotTimestampV3; + private final boolean override; + + private int registeredNameCount = 0; + private int suspiciousCount = 0; + private int suspiciousPercent = 0; + private int consolidationCount = 0; + private int bulkIssuanceCount = 0; + private int recentSponsorshipCount = 0; + + private List sponsorshipRewardShares = new ArrayList<>(); + private final Map> paymentsByAddress = new HashMap<>(); + private final Set sponsees = new LinkedHashSet<>(); + private Set consolidatedAddresses = new LinkedHashSet<>(); + private final Set zeroTransactionAddreses = new LinkedHashSet<>(); + private final Set penaltyAddresses = new LinkedHashSet<>(); + + public SelfSponsorshipAlgoV3(Repository repository, String address, long snapshotTimestampV1, long snapshotTimestampV3, boolean override) throws DataException { + this.repository = repository; + this.address = address; + this.accountData = this.repository.getAccountRepository().getAccount(this.address); + this.snapshotTimestampV1 = snapshotTimestampV1; + this.snapshotTimestampV3 = snapshotTimestampV3; + this.override = override; + } + + public String getAddress() { + return this.address; + } + + public Set getPenaltyAddresses() { + return this.penaltyAddresses; + } + + + public void run() throws DataException { + if (this.accountData == null) { + // Nothing to do + return; + } + + this.fetchSponsorshipRewardShares(); + if (this.sponsorshipRewardShares.isEmpty()) { + // Nothing to do + return; + } + + this.findConsolidatedRewards(); + this.findBulkIssuance(); + this.findRegisteredNameCount(); + this.findRecentSponsorshipCount(); + + int score = this.calculateScore(); + if (score <= 0 && !override) { + return; + } + + String newAddress = this.getDestinationAccount(this.address); + while (newAddress != null) { + // Found destination account + this.penaltyAddresses.add(newAddress); + + // Run algo for this address, but in "override" mode because it has already been flagged + SelfSponsorshipAlgoV3 algoV3 = new SelfSponsorshipAlgoV3(this.repository, newAddress, this.snapshotTimestampV1, this.snapshotTimestampV3, true); + algoV3.run(); + this.penaltyAddresses.addAll(algoV3.getPenaltyAddresses()); + + newAddress = this.getDestinationAccount(newAddress); + } + + this.penaltyAddresses.add(this.address); + + if (this.override || this.recentSponsorshipCount < 20) { + this.penaltyAddresses.addAll(this.consolidatedAddresses); + this.penaltyAddresses.addAll(this.zeroTransactionAddreses); + } + else { + this.penaltyAddresses.addAll(this.sponsees); + } + } + + private String getDestinationAccount(String address) throws DataException { + List transferPrivsTransactions = fetchTransferPrivsForAddress(address); + if (transferPrivsTransactions.isEmpty()) { + // No TRANSFER_PRIVS transactions for this address + return null; + } + + AccountData accountData = this.repository.getAccountRepository().getAccount(address); + if (accountData == null) { + return null; + } + + for (TransactionData transactionData : transferPrivsTransactions) { + TransferPrivsTransactionData transferPrivsTransactionData = (TransferPrivsTransactionData) transactionData; + if (Arrays.equals(transferPrivsTransactionData.getSenderPublicKey(), accountData.getPublicKey())) { + return transferPrivsTransactionData.getRecipient(); + } + } + + return null; + } + + private void findConsolidatedRewards() throws DataException { + List sponseesThatSentRewards = new ArrayList<>(); + Map paymentRecipients = new HashMap<>(); + + // Collect outgoing payments of each sponsee + for (String sponseeAddress : this.sponsees) { + + // Firstly fetch all payments for address, since the functions below depend on this data + this.fetchPaymentsForAddress(sponseeAddress); + + // Check if the address has zero relevant transactions + if (this.hasZeroTransactions(sponseeAddress)) { + this.zeroTransactionAddreses.add(sponseeAddress); + } + + // Get payment recipients + List allPaymentRecipients = this.fetchOutgoingPaymentRecipientsForAddress(sponseeAddress); + if (allPaymentRecipients.isEmpty()) { + continue; + } + sponseesThatSentRewards.add(sponseeAddress); + + List addressesPaidByThisSponsee = new ArrayList<>(); + for (String paymentRecipient : allPaymentRecipients) { + if (addressesPaidByThisSponsee.contains(paymentRecipient)) { + // We already tracked this association - don't allow multiple to stack up + continue; + } + addressesPaidByThisSponsee.add(paymentRecipient); + + // Increment count for this recipient, or initialize to 1 if not present + if (paymentRecipients.computeIfPresent(paymentRecipient, (k, v) -> v + 1) == null) { + paymentRecipients.put(paymentRecipient, 1); + } + } + + } + + // Exclude addresses with a low number of payments + Map filteredPaymentRecipients = paymentRecipients.entrySet().stream() + .filter(p -> p.getValue() != null && p.getValue() >= 10) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + // Now check how many sponsees have sent to this subset of addresses + Map sponseesThatConsolidatedRewards = new HashMap<>(); + for (String sponseeAddress : sponseesThatSentRewards) { + List allPaymentRecipients = this.fetchOutgoingPaymentRecipientsForAddress(sponseeAddress); + // Remove any that aren't to one of the flagged recipients (i.e. consolidation) + allPaymentRecipients.removeIf(r -> !filteredPaymentRecipients.containsKey(r)); + + int count = allPaymentRecipients.size(); + if (count == 0) { + continue; + } + if (sponseesThatConsolidatedRewards.computeIfPresent(sponseeAddress, (k, v) -> v + count) == null) { + sponseesThatConsolidatedRewards.put(sponseeAddress, count); + } + } + + // Remove sponsees that have only sent a low number of payments to the filtered addresses + Map filteredSponseesThatConsolidatedRewards = sponseesThatConsolidatedRewards.entrySet().stream() + .filter(p -> p.getValue() != null && p.getValue() >= 2) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + this.consolidationCount = sponseesThatConsolidatedRewards.size(); + this.consolidatedAddresses = new LinkedHashSet<>(filteredSponseesThatConsolidatedRewards.keySet()); + this.suspiciousCount = this.consolidationCount + this.zeroTransactionAddreses.size(); + this.suspiciousPercent = (int)(this.suspiciousCount / (float) this.sponsees.size() * 100); + } + + private void findBulkIssuance() { + Long lastTimestamp = null; + for (RewardShareTransactionData rewardShareTransactionData : sponsorshipRewardShares) { + long timestamp = rewardShareTransactionData.getTimestamp(); + if (timestamp >= this.snapshotTimestampV3) { + continue; + } + + if (lastTimestamp != null) { + if (timestamp - lastTimestamp < 3*60*1000L) { + this.bulkIssuanceCount++; + } + } + lastTimestamp = timestamp; + } + } + + private void findRegisteredNameCount() throws DataException { + int registeredNameCount = 0; + for (String sponseeAddress : sponsees) { + List names = repository.getNameRepository().getNamesByOwner(sponseeAddress); + for (NameData name : names) { + if (name.getRegistered() < this.snapshotTimestampV3) { + registeredNameCount++; + break; + } + } + } + this.registeredNameCount = registeredNameCount; + } + + private void findRecentSponsorshipCount() { + int recentSponsorshipCount = 0; + for (RewardShareTransactionData rewardShare : sponsorshipRewardShares) { + if (rewardShare.getTimestamp() >= this.snapshotTimestampV1) { + recentSponsorshipCount++; + } + } + this.recentSponsorshipCount = recentSponsorshipCount; + } + + private int calculateScore() { + final int suspiciousMultiplier = (this.suspiciousCount >= 100) ? this.suspiciousPercent : 1; + final int nameMultiplier = (this.sponsees.size() >= 25 && this.registeredNameCount <= 1) ? 21 : + (this.sponsees.size() >= 15 && this.registeredNameCount <= 1) ? 11 : + (this.sponsees.size() >= 5 && this.registeredNameCount <= 1) ? 5 : 1; + final int consolidationMultiplier = Math.max(this.consolidationCount, 1); + final int bulkIssuanceMultiplier = Math.max(this.bulkIssuanceCount / 2, 1); + final int offset = 20; + return suspiciousMultiplier * nameMultiplier * consolidationMultiplier * bulkIssuanceMultiplier - offset; + } + + private void fetchSponsorshipRewardShares() throws DataException { + List sponsorshipRewardShares = new ArrayList<>(); + + // Define relevant transactions + List txTypes = List.of(TransactionType.REWARD_SHARE); + List transactionDataList = fetchTransactions(repository, txTypes, this.address, false); + transactionDataList.removeIf(t -> t.getTimestamp() <= this.snapshotTimestampV1 || t.getTimestamp() >= this.snapshotTimestampV3); + + for (TransactionData transactionData : transactionDataList) { + if (transactionData.getType() != TransactionType.REWARD_SHARE) { + continue; + } + + RewardShareTransactionData rewardShareTransactionData = (RewardShareTransactionData) transactionData; + + // Skip removals + if (rewardShareTransactionData.getSharePercent() < 0) { + continue; + } + + // Skip if not sponsored by this account + if (!Arrays.equals(rewardShareTransactionData.getCreatorPublicKey(), accountData.getPublicKey())) { + continue; + } + + // Skip self shares + if (Objects.equals(rewardShareTransactionData.getRecipient(), this.address)) { + continue; + } + + boolean duplicateFound = false; + for (RewardShareTransactionData existingRewardShare : sponsorshipRewardShares) { + if (Objects.equals(existingRewardShare.getRecipient(), rewardShareTransactionData.getRecipient())) { + // Duplicate + duplicateFound = true; + break; + } + } + if (!duplicateFound) { + sponsorshipRewardShares.add(rewardShareTransactionData); + this.sponsees.add(rewardShareTransactionData.getRecipient()); + } + } + + this.sponsorshipRewardShares = sponsorshipRewardShares; + } + + private List fetchTransferPrivsForAddress(String address) throws DataException { + return fetchTransactions(repository, + List.of(TransactionType.TRANSFER_PRIVS), + address, true); + } + + private void fetchPaymentsForAddress(String address) throws DataException { + List payments = fetchTransactions(repository, + Arrays.asList(TransactionType.PAYMENT, TransactionType.TRANSFER_ASSET), + address, false); + this.paymentsByAddress.put(address, payments); + } + + private List fetchOutgoingPaymentRecipientsForAddress(String address) { + List outgoingPaymentRecipients = new ArrayList<>(); + + List transactionDataList = this.paymentsByAddress.get(address); + if (transactionDataList == null) transactionDataList = new ArrayList<>(); + transactionDataList.removeIf(t -> t.getTimestamp() <= this.snapshotTimestampV1 || t.getTimestamp() >= this.snapshotTimestampV3); + for (TransactionData transactionData : transactionDataList) { + switch (transactionData.getType()) { + + case PAYMENT: + PaymentTransactionData paymentTransactionData = (PaymentTransactionData) transactionData; + if (!Objects.equals(paymentTransactionData.getRecipient(), address)) { + // Outgoing payment from this account + outgoingPaymentRecipients.add(paymentTransactionData.getRecipient()); + } + break; + + case TRANSFER_ASSET: + TransferAssetTransactionData transferAssetTransactionData = (TransferAssetTransactionData) transactionData; + if (transferAssetTransactionData.getAssetId() == Asset.QORT) { + if (!Objects.equals(transferAssetTransactionData.getRecipient(), address)) { + // Outgoing payment from this account + outgoingPaymentRecipients.add(transferAssetTransactionData.getRecipient()); + } + } + break; + + default: + break; + } + } + + return outgoingPaymentRecipients; + } + + private boolean hasZeroTransactions(String address) { + List transactionDataList = this.paymentsByAddress.get(address); + if (transactionDataList == null) { + return true; + } + transactionDataList.removeIf(t -> t.getTimestamp() <= this.snapshotTimestampV1 || t.getTimestamp() >= this.snapshotTimestampV3); + return transactionDataList.size() == 0; + } + + private static List fetchTransactions(Repository repository, List txTypes, String address, boolean reverse) throws DataException { + // Fetch all relevant transactions for this account + List signatures = repository.getTransactionRepository() + .getSignaturesMatchingCriteria(null, null, null, txTypes, + null, null, address, TransactionsResource.ConfirmationStatus.CONFIRMED, + null, null, reverse); + + List transactionDataList = new ArrayList<>(); + + for (byte[] signature : signatures) { + // Fetch transaction data + TransactionData transactionData = repository.getTransactionRepository().fromSignature(signature); + if (transactionData == null) { + continue; + } + transactionDataList.add(transactionData); + } + + return transactionDataList; + } +} \ No newline at end of file diff --git a/src/main/java/org/qortal/block/Block.java b/src/main/java/org/qortal/block/Block.java index baa56179..caaa0c76 100644 --- a/src/main/java/org/qortal/block/Block.java +++ b/src/main/java/org/qortal/block/Block.java @@ -1062,9 +1062,9 @@ public class Block { long expectedTimestamp = calcTimestamp(parentBlockData, this.blockData.getMinterPublicKey(), minterLevel); if (this.blockData.getTimestamp() != expectedTimestamp) { - LOGGER.debug(String.format("timestamp mismatch! block had %s but we expected %s", this.blockData.getTimestamp(), expectedTimestamp)); - return ValidationResult.TIMESTAMP_INCORRECT; - } + LOGGER.debug(String.format("timestamp mismatch! block had %s but we expected %s", this.blockData.getTimestamp(), expectedTimestamp)); + return ValidationResult.TIMESTAMP_INCORRECT; + } return ValidationResult.OK; } @@ -1558,6 +1558,14 @@ public class Block { if (this.blockData.getHeight() == BlockChain.getInstance().getSelfSponsorshipAlgoV1Height()) { SelfSponsorshipAlgoV1Block.processAccountPenalties(this); } + + if (this.blockData.getHeight() == BlockChain.getInstance().getSelfSponsorshipAlgoV2Height()) { + SelfSponsorshipAlgoV2Block.processAccountPenalties(this); + } + + if (this.blockData.getHeight() == BlockChain.getInstance().getSelfSponsorshipAlgoV3Height()) { + SelfSponsorshipAlgoV3Block.processAccountPenalties(this); + } } // We're about to (test-)process a batch of transactions, @@ -1851,6 +1859,14 @@ public class Block { SelfSponsorshipAlgoV1Block.orphanAccountPenalties(this); } + if (this.blockData.getHeight() == BlockChain.getInstance().getSelfSponsorshipAlgoV2Height()) { + SelfSponsorshipAlgoV2Block.orphanAccountPenalties(this); + } + + if (this.blockData.getHeight() == BlockChain.getInstance().getSelfSponsorshipAlgoV3Height()) { + SelfSponsorshipAlgoV3Block.orphanAccountPenalties(this); + } + // Account levels and block rewards are only processed/orphaned on block reward distribution blocks if (this.isRewardDistributionBlock()) { // Block rewards, including transaction fees, removed after transactions undone diff --git a/src/main/java/org/qortal/block/BlockChain.java b/src/main/java/org/qortal/block/BlockChain.java index eb98000e..dc9dfe4c 100644 --- a/src/main/java/org/qortal/block/BlockChain.java +++ b/src/main/java/org/qortal/block/BlockChain.java @@ -74,6 +74,7 @@ public class BlockChain { onlineAccountMinterLevelValidationHeight, selfSponsorshipAlgoV1Height, selfSponsorshipAlgoV2Height, + selfSponsorshipAlgoV3Height, feeValidationFixTimestamp, chatReferenceTimestamp, arbitraryOptionalFeeTimestamp, @@ -216,6 +217,12 @@ public class BlockChain { /** Snapshot timestamp for self sponsorship algo V2 */ private long selfSponsorshipAlgoV2SnapshotTimestamp; + /** Snapshot timestamp for self sponsorship algo V3 */ + private long selfSponsorshipAlgoV3SnapshotTimestamp; + + /** Reference timestamp for self sponsorship algo V1 block height */ + private long referenceTimestampBlock; + /** Feature-trigger timestamp to modify behaviour of various transactions that support mempow */ private long mempowTransactionUpdatesTimestamp; @@ -418,6 +425,15 @@ public class BlockChain { return this.selfSponsorshipAlgoV2SnapshotTimestamp; } + // Self sponsorship algo V3 + public long getSelfSponsorshipAlgoV3SnapshotTimestamp() { + return this.selfSponsorshipAlgoV3SnapshotTimestamp; + } + + // Self sponsorship algo V3 + public long getReferenceTimestampBlock() { + return this.referenceTimestampBlock; + } // Feature-trigger timestamp to modify behaviour of various transactions that support mempow public long getMemPoWTransactionUpdatesTimestamp() { return this.mempowTransactionUpdatesTimestamp; @@ -562,6 +578,10 @@ public class BlockChain { return this.featureTriggers.get(FeatureTrigger.selfSponsorshipAlgoV2Height.name()).intValue(); } + public int getSelfSponsorshipAlgoV3Height() { + return this.featureTriggers.get(FeatureTrigger.selfSponsorshipAlgoV3Height.name()).intValue(); + } + public long getOnlineAccountMinterLevelValidationHeight() { return this.featureTriggers.get(FeatureTrigger.onlineAccountMinterLevelValidationHeight.name()).intValue(); } diff --git a/src/main/java/org/qortal/block/SelfSponsorshipAlgoV2Block.java b/src/main/java/org/qortal/block/SelfSponsorshipAlgoV2Block.java new file mode 100644 index 00000000..7957de6a --- /dev/null +++ b/src/main/java/org/qortal/block/SelfSponsorshipAlgoV2Block.java @@ -0,0 +1,143 @@ +package org.qortal.block; + +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.qortal.account.SelfSponsorshipAlgoV2; +import org.qortal.api.model.AccountPenaltyStats; +import org.qortal.crypto.Crypto; +import org.qortal.data.account.AccountData; +import org.qortal.data.account.AccountPenaltyData; +import org.qortal.repository.DataException; +import org.qortal.repository.Repository; +import org.qortal.utils.Base58; + +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.stream.Collectors; + +/** + * Self Sponsorship AlgoV2 Block + *

+ * Selected block for the initial run on the "self sponsorship detection algorithm" + */ +public final class SelfSponsorshipAlgoV2Block { + + private static final Logger LOGGER = LogManager.getLogger(SelfSponsorshipAlgoV2Block.class); + + private SelfSponsorshipAlgoV2Block() { + /* Do not instantiate */ + } + + public static void processAccountPenalties(Block block) throws DataException { + LOGGER.info("Process Self Sponsorship Algo V2 - this will take a while..."); + logPenaltyStats(block.repository); + long startTime = System.currentTimeMillis(); + Set penalties = getAccountPenalties(block.repository, -5000000); + block.repository.getAccountRepository().updateBlocksMintedPenalties(penalties); + long totalTime = System.currentTimeMillis() - startTime; + String hash = getHash(penalties.stream().map(p -> p.getAddress()).collect(Collectors.toList())); + LOGGER.info("{} penalty addresses processed (hash: {}). Total time taken: {} seconds", penalties.size(), hash, (int)(totalTime / 1000.0f)); + logPenaltyStats(block.repository); + + int updatedCount = updateAccountLevels(block.repository, penalties); + LOGGER.info("Account levels updated for {} penalty addresses", updatedCount); + } + + public static void orphanAccountPenalties(Block block) throws DataException { + LOGGER.info("Orphan Self Sponsorship Algo V2 - this will take a while..."); + logPenaltyStats(block.repository); + long startTime = System.currentTimeMillis(); + Set penalties = getAccountPenalties(block.repository, 5000000); + block.repository.getAccountRepository().updateBlocksMintedPenalties(penalties); + long totalTime = System.currentTimeMillis() - startTime; + String hash = getHash(penalties.stream().map(p -> p.getAddress()).collect(Collectors.toList())); + LOGGER.info("{} penalty addresses orphaned (hash: {}). Total time taken: {} seconds", penalties.size(), hash, (int)(totalTime / 1000.0f)); + logPenaltyStats(block.repository); + + int updatedCount = updateAccountLevels(block.repository, penalties); + LOGGER.info("Account levels updated for {} penalty addresses", updatedCount); + } + + private static Set getAccountPenalties(Repository repository, int penalty) throws DataException { + Set penalties = new LinkedHashSet<>(); + List penalizedAddresses = repository.getAccountRepository().getPenaltyAccounts(); + List assetAddresses = repository.getTransactionRepository().getConfirmedTransferAssetCreators(); + + for (AccountData penalizedAddress : penalizedAddresses) { + //System.out.println(String.format("address: %s", address)); + SelfSponsorshipAlgoV2 selfSponsorshipAlgoV2 = new SelfSponsorshipAlgoV2(repository, penalizedAddress.getAddress(), false); + selfSponsorshipAlgoV2.run(); + //System.out.println(String.format("Penalty addresses: %d", selfSponsorshipAlgoV2.getPenaltyAddresses().size())); + for (String penaltyAddress : selfSponsorshipAlgoV2.getPenaltyAddresses()) { + penalties.add(new AccountPenaltyData(penaltyAddress, penalty)); + } + } + + for (String assetAddress : assetAddresses) { + //System.out.println(String.format("address: %s", address)); + SelfSponsorshipAlgoV2 selfSponsorshipAlgoV2 = new SelfSponsorshipAlgoV2(repository, assetAddress, true); + selfSponsorshipAlgoV2.run(); + //System.out.println(String.format("Penalty addresses: %d", selfSponsorshipAlgoV2.getPenaltyAddresses().size())); + for (String penaltyAddress : selfSponsorshipAlgoV2.getPenaltyAddresses()) { + penalties.add(new AccountPenaltyData(penaltyAddress, penalty)); + } + } + + return penalties; + } + + private static int updateAccountLevels(Repository repository, Set accountPenalties) throws DataException { + final List cumulativeBlocksByLevel = BlockChain.getInstance().getCumulativeBlocksByLevel(); + final int maximumLevel = cumulativeBlocksByLevel.size() - 1; + + int updatedCount = 0; + + for (AccountPenaltyData penaltyData : accountPenalties) { + AccountData accountData = repository.getAccountRepository().getAccount(penaltyData.getAddress()); + final int effectiveBlocksMinted = accountData.getBlocksMinted() + accountData.getBlocksMintedAdjustment() + accountData.getBlocksMintedPenalty(); + + // Shortcut for penalties + if (effectiveBlocksMinted < 0) { + accountData.setLevel(0); + repository.getAccountRepository().setLevel(accountData); + updatedCount++; + LOGGER.trace(() -> String.format("Block minter %s dropped to level %d", accountData.getAddress(), accountData.getLevel())); + continue; + } + + for (int newLevel = maximumLevel; newLevel >= 0; --newLevel) { + if (effectiveBlocksMinted >= cumulativeBlocksByLevel.get(newLevel)) { + accountData.setLevel(newLevel); + repository.getAccountRepository().setLevel(accountData); + updatedCount++; + LOGGER.trace(() -> String.format("Block minter %s increased to level %d", accountData.getAddress(), accountData.getLevel())); + break; + } + } + } + + return updatedCount; + } + + private static void logPenaltyStats(Repository repository) { + try { + LOGGER.info(getPenaltyStats(repository)); + + } catch (DataException e) {} + } + + private static AccountPenaltyStats getPenaltyStats(Repository repository) throws DataException { + List accounts = repository.getAccountRepository().getPenaltyAccounts(); + return AccountPenaltyStats.fromAccounts(accounts); + } + + public static String getHash(List penaltyAddresses) { + if (penaltyAddresses == null || penaltyAddresses.isEmpty()) { + return null; + } + Collections.sort(penaltyAddresses); + return Base58.encode(Crypto.digest(StringUtils.join(penaltyAddresses).getBytes(StandardCharsets.UTF_8))); + } + +} \ No newline at end of file diff --git a/src/main/java/org/qortal/block/SelfSponsorshipAlgoV3Block.java b/src/main/java/org/qortal/block/SelfSponsorshipAlgoV3Block.java new file mode 100644 index 00000000..dec8957d --- /dev/null +++ b/src/main/java/org/qortal/block/SelfSponsorshipAlgoV3Block.java @@ -0,0 +1,136 @@ +package org.qortal.block; + +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.qortal.account.SelfSponsorshipAlgoV3; +import org.qortal.api.model.AccountPenaltyStats; +import org.qortal.crypto.Crypto; +import org.qortal.data.account.AccountData; +import org.qortal.data.account.AccountPenaltyData; +import org.qortal.repository.DataException; +import org.qortal.repository.Repository; +import org.qortal.utils.Base58; + +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Self Sponsorship AlgoV3 Block + *

+ * Selected block for the initial run on the "self sponsorship detection algorithm" + */ +public final class SelfSponsorshipAlgoV3Block { + + private static final Logger LOGGER = LogManager.getLogger(SelfSponsorshipAlgoV3Block.class); + + private SelfSponsorshipAlgoV3Block() { + /* Do not instantiate */ + } + + public static void processAccountPenalties(Block block) throws DataException { + LOGGER.info("Process Self Sponsorship Algo V3 - this will take a while..."); + logPenaltyStats(block.repository); + long startTime = System.currentTimeMillis(); + Set penalties = getAccountPenalties(block.repository, -5000000); + block.repository.getAccountRepository().updateBlocksMintedPenalties(penalties); + long totalTime = System.currentTimeMillis() - startTime; + String hash = getHash(penalties.stream().map(p -> p.getAddress()).collect(Collectors.toList())); + LOGGER.info("{} penalty addresses processed (hash: {}). Total time taken: {} seconds", penalties.size(), hash, (int)(totalTime / 1000.0f)); + logPenaltyStats(block.repository); + + int updatedCount = updateAccountLevels(block.repository, penalties); + LOGGER.info("Account levels updated for {} penalty addresses", updatedCount); + } + + public static void orphanAccountPenalties(Block block) throws DataException { + LOGGER.info("Orphan Self Sponsorship Algo V3 - this will take a while..."); + logPenaltyStats(block.repository); + long startTime = System.currentTimeMillis(); + Set penalties = getAccountPenalties(block.repository, 5000000); + block.repository.getAccountRepository().updateBlocksMintedPenalties(penalties); + long totalTime = System.currentTimeMillis() - startTime; + String hash = getHash(penalties.stream().map(p -> p.getAddress()).collect(Collectors.toList())); + LOGGER.info("{} penalty addresses orphaned (hash: {}). Total time taken: {} seconds", penalties.size(), hash, (int)(totalTime / 1000.0f)); + logPenaltyStats(block.repository); + + int updatedCount = updateAccountLevels(block.repository, penalties); + LOGGER.info("Account levels updated for {} penalty addresses", updatedCount); + } + + public static Set getAccountPenalties(Repository repository, int penalty) throws DataException { + final long snapshotTimestampV1 = BlockChain.getInstance().getSelfSponsorshipAlgoV1SnapshotTimestamp(); + final long snapshotTimestampV3 = BlockChain.getInstance().getSelfSponsorshipAlgoV3SnapshotTimestamp(); + Set penalties = new LinkedHashSet<>(); + List addresses = repository.getTransactionRepository().getConfirmedRewardShareCreatorsExcludingSelfShares(); + for (String address : addresses) { + //System.out.println(String.format("address: %s", address)); + SelfSponsorshipAlgoV3 selfSponsorshipAlgoV3 = new SelfSponsorshipAlgoV3(repository, address, snapshotTimestampV1, snapshotTimestampV3, false); + selfSponsorshipAlgoV3.run(); + //System.out.println(String.format("Penalty addresses: %d", selfSponsorshipAlgoV3.getPenaltyAddresses().size())); + + for (String penaltyAddress : selfSponsorshipAlgoV3.getPenaltyAddresses()) { + penalties.add(new AccountPenaltyData(penaltyAddress, penalty)); + } + } + return penalties; + } + + private static int updateAccountLevels(Repository repository, Set accountPenalties) throws DataException { + final List cumulativeBlocksByLevel = BlockChain.getInstance().getCumulativeBlocksByLevel(); + final int maximumLevel = cumulativeBlocksByLevel.size() - 1; + + int updatedCount = 0; + + for (AccountPenaltyData penaltyData : accountPenalties) { + AccountData accountData = repository.getAccountRepository().getAccount(penaltyData.getAddress()); + final int effectiveBlocksMinted = accountData.getBlocksMinted() + accountData.getBlocksMintedAdjustment() + accountData.getBlocksMintedPenalty(); + + // Shortcut for penalties + if (effectiveBlocksMinted < 0) { + accountData.setLevel(0); + repository.getAccountRepository().setLevel(accountData); + updatedCount++; + LOGGER.trace(() -> String.format("Block minter %s dropped to level %d", accountData.getAddress(), accountData.getLevel())); + continue; + } + + for (int newLevel = maximumLevel; newLevel >= 0; --newLevel) { + if (effectiveBlocksMinted >= cumulativeBlocksByLevel.get(newLevel)) { + accountData.setLevel(newLevel); + repository.getAccountRepository().setLevel(accountData); + updatedCount++; + LOGGER.trace(() -> String.format("Block minter %s increased to level %d", accountData.getAddress(), accountData.getLevel())); + break; + } + } + } + + return updatedCount; + } + + private static void logPenaltyStats(Repository repository) { + try { + LOGGER.info(getPenaltyStats(repository)); + + } catch (DataException e) {} + } + + private static AccountPenaltyStats getPenaltyStats(Repository repository) throws DataException { + List accounts = repository.getAccountRepository().getPenaltyAccounts(); + return AccountPenaltyStats.fromAccounts(accounts); + } + + public static String getHash(List penaltyAddresses) { + if (penaltyAddresses == null || penaltyAddresses.isEmpty()) { + return null; + } + Collections.sort(penaltyAddresses); + return Base58.encode(Crypto.digest(StringUtils.join(penaltyAddresses).getBytes(StandardCharsets.UTF_8))); + } + +} \ No newline at end of file diff --git a/src/main/java/org/qortal/crosschain/PirateChain.java b/src/main/java/org/qortal/crosschain/PirateChain.java index 434932d1..64eda0be 100644 --- a/src/main/java/org/qortal/crosschain/PirateChain.java +++ b/src/main/java/org/qortal/crosschain/PirateChain.java @@ -51,7 +51,12 @@ public class PirateChain extends Bitcoiny { public Collection getServers() { return Arrays.asList( // Servers chosen on NO BASIS WHATSOEVER from various sources! - new Server("lightd.pirate.black", Server.ConnectionType.SSL, 443) + new Server("lightd.pirate.black", Server.ConnectionType.SSL, 443), + new Server("wallet-arrr1.qortal.online", Server.ConnectionType.SSL, 443), + new Server("wallet-arrr2.qortal.online", Server.ConnectionType.SSL, 443), + new Server("wallet-arrr3.qortal.online", Server.ConnectionType.SSL, 443), + new Server("wallet-arrr4.qortal.online", Server.ConnectionType.SSL, 443), + new Server("wallet-arrr5.qortal.online", Server.ConnectionType.SSL, 443) ); } diff --git a/src/main/java/org/qortal/repository/TransactionRepository.java b/src/main/java/org/qortal/repository/TransactionRepository.java index e007586e..d4517485 100644 --- a/src/main/java/org/qortal/repository/TransactionRepository.java +++ b/src/main/java/org/qortal/repository/TransactionRepository.java @@ -205,6 +205,15 @@ public interface TransactionRepository { */ public List getConfirmedRewardShareCreatorsExcludingSelfShares() throws DataException; + /** + * Returns list of transfer asset transaction creators. + * This uses confirmed transactions only. + * + * @return + * @throws DataException + */ + public List getConfirmedTransferAssetCreators() throws DataException; + /** * Returns list of transactions pending approval, with optional txGgroupId filtering. *

diff --git a/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBTransactionRepository.java b/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBTransactionRepository.java index 5b41a85d..fe0b4d0b 100644 --- a/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBTransactionRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/transaction/HSQLDBTransactionRepository.java @@ -1043,6 +1043,33 @@ public class HSQLDBTransactionRepository implements TransactionRepository { } } + public List getConfirmedTransferAssetCreators() throws DataException { + List transferAssetCreators = new ArrayList<>(); + + String sql = "SELECT account " + + "FROM TransferAssetTransactions " + + "JOIN Accounts ON Accounts.public_key = TransferAssetTransactions.sender " + + "JOIN Transactions ON Transactions.signature = TransferAssetTransactions.signature " + + "WHERE block_height IS NOT NULL AND TransferAssetTransactions.recipient != Accounts.account " + + "GROUP BY account " + + "ORDER BY account"; + + try (ResultSet resultSet = this.repository.checkedExecute(sql)) { + if (resultSet == null) + return transferAssetCreators; + + do { + String address = resultSet.getString(1); + + transferAssetCreators.add(address); + } while (resultSet.next()); + + return transferAssetCreators; + } catch (SQLException e) { + throw new DataException("Unable to fetch transfer asset from repository", e); + } + } + @Override public List getApprovalPendingTransactions(Integer txGroupId, Integer limit, Integer offset, Boolean reverse) throws DataException { StringBuilder sql = new StringBuilder(512); diff --git a/src/main/java/org/qortal/settings/Settings.java b/src/main/java/org/qortal/settings/Settings.java index 3ca86cc0..df59c037 100644 --- a/src/main/java/org/qortal/settings/Settings.java +++ b/src/main/java/org/qortal/settings/Settings.java @@ -211,7 +211,7 @@ public class Settings { public long recoveryModeTimeout = 9999999999999L; /** Minimum peer version number required in order to sync with them */ - private String minPeerVersion = "4.4.2"; + private String minPeerVersion = "4.5.0"; /** Whether to allow connections with peers below minPeerVersion * If true, we won't sync with them but they can still sync with us, and will show in the peers list * If false, sync will be blocked both ways, and they will not appear in the peers list */ diff --git a/src/main/java/org/qortal/transaction/RegisterNameTransaction.java b/src/main/java/org/qortal/transaction/RegisterNameTransaction.java index 79976199..a89e60c0 100644 --- a/src/main/java/org/qortal/transaction/RegisterNameTransaction.java +++ b/src/main/java/org/qortal/transaction/RegisterNameTransaction.java @@ -54,6 +54,10 @@ public class RegisterNameTransaction extends Transaction { Account registrant = getRegistrant(); String name = this.registerNameTransactionData.getName(); + int blockchainHeight = this.repository.getBlockRepository().getBlockchainHeight(); + final int start = BlockChain.getInstance().getSelfSponsorshipAlgoV2Height() - 1180; + final int end = BlockChain.getInstance().getSelfSponsorshipAlgoV3Height(); + // Check name size bounds int nameLength = Utf8.encodedLength(name); if (nameLength < Name.MIN_NAME_SIZE || nameLength > Name.MAX_NAME_SIZE) @@ -76,6 +80,10 @@ public class RegisterNameTransaction extends Transaction { if (registrant.getConfirmedBalance(Asset.QORT) < this.registerNameTransactionData.getFee()) return ValidationResult.NO_BALANCE; + // Check if we are on algo runs + if (blockchainHeight >= start && blockchainHeight <= end) + return ValidationResult.TEMPORARY_DISABLED; + return ValidationResult.OK; } diff --git a/src/main/java/org/qortal/transaction/RewardShareTransaction.java b/src/main/java/org/qortal/transaction/RewardShareTransaction.java index 1b608a0c..635c8c8a 100644 --- a/src/main/java/org/qortal/transaction/RewardShareTransaction.java +++ b/src/main/java/org/qortal/transaction/RewardShareTransaction.java @@ -183,12 +183,28 @@ public class RewardShareTransaction extends Transaction { @Override public boolean isConfirmableAtHeight(int height) { + final int startV2 = BlockChain.getInstance().getSelfSponsorshipAlgoV2Height() - 15; + final int startV3 = BlockChain.getInstance().getSelfSponsorshipAlgoV3Height() - 15; + final int endV2 = BlockChain.getInstance().getSelfSponsorshipAlgoV2Height() + 10; + final int endV3 = BlockChain.getInstance().getSelfSponsorshipAlgoV3Height() + 10; + if (height >= BlockChain.getInstance().getUnconfirmableRewardSharesHeight()) { // Not confirmable in online accounts or distribution blocks if (Block.isOnlineAccountsBlock(height) || Block.isBatchRewardDistributionBlock(height)) { return false; } } + + if (height >= startV2 && height <= endV2) { + // Not confirmable on algo V2 run + return false; + } + + if (height >= startV3 && height <= endV3) { + // Not confirmable on algo V3 run + return false; + } + return true; } diff --git a/src/main/java/org/qortal/transaction/Transaction.java b/src/main/java/org/qortal/transaction/Transaction.java index c142fff8..5e5b9fba 100644 --- a/src/main/java/org/qortal/transaction/Transaction.java +++ b/src/main/java/org/qortal/transaction/Transaction.java @@ -173,6 +173,7 @@ public abstract class Transaction { INVALID_OPTION_LENGTH(20), DUPLICATE_OPTION(21), POLL_ALREADY_EXISTS(22), + POLL_ALREADY_HAS_VOTES(23), POLL_DOES_NOT_EXIST(24), POLL_OPTION_DOES_NOT_EXIST(25), ALREADY_VOTED_FOR_THAT_OPTION(26), @@ -247,6 +248,7 @@ public abstract class Transaction { GROUP_APPROVAL_REQUIRED(98), ACCOUNT_NOT_TRANSFERABLE(99), TRANSFER_PRIVS_DISABLED(100), + TEMPORARY_DISABLED(101), INVALID_BUT_OK(999), NOT_YET_RELEASED(1000), NOT_SUPPORTED(1001); diff --git a/src/main/java/org/qortal/transaction/TransferAssetTransaction.java b/src/main/java/org/qortal/transaction/TransferAssetTransaction.java index 50c6f24a..2144e4b9 100644 --- a/src/main/java/org/qortal/transaction/TransferAssetTransaction.java +++ b/src/main/java/org/qortal/transaction/TransferAssetTransaction.java @@ -1,6 +1,7 @@ package org.qortal.transaction; import org.qortal.account.Account; +import org.qortal.block.BlockChain; import org.qortal.data.PaymentData; import org.qortal.data.transaction.TransactionData; import org.qortal.data.transaction.TransferAssetTransactionData; @@ -51,6 +52,14 @@ public class TransferAssetTransaction extends Transaction { @Override public ValidationResult isValid() throws DataException { + int blockchainHeight = this.repository.getBlockRepository().getBlockchainHeight(); + final int start = BlockChain.getInstance().getSelfSponsorshipAlgoV2Height(); + final int end = BlockChain.getInstance().getSelfSponsorshipAlgoV3Height(); + + // Check if we are on algo runs + if (blockchainHeight >= start && blockchainHeight <= end) + return ValidationResult.ASSET_NOT_SPENDABLE; + // Wrap asset transfer as a payment and delegate final payment checks to Payment class return new Payment(this.repository).isValid(this.transferAssetTransactionData.getSenderPublicKey(), getPaymentData(), this.transferAssetTransactionData.getFee()); } diff --git a/src/main/resources/blockchain.json b/src/main/resources/blockchain.json index ff3d1c37..8b8373a8 100644 --- a/src/main/resources/blockchain.json +++ b/src/main/resources/blockchain.json @@ -30,7 +30,9 @@ "onlineAccountSignaturesMaxLifetime": 86400000, "onlineAccountsModulusV2Timestamp": 1659801600000, "selfSponsorshipAlgoV1SnapshotTimestamp": 1670230000000, - "selfSponsorshipAlgoV2SnapshotTimestamp": 1706745600000, + "selfSponsorshipAlgoV2SnapshotTimestamp": 1708360200000, + "selfSponsorshipAlgoV3SnapshotTimestamp": 1708432200000, + "referenceTimestampBlock": 1670684455220, "mempowTransactionUpdatesTimestamp": 1693558800000, "blockRewardBatchStartHeight": 1508000, "blockRewardBatchSize": 1000, @@ -94,7 +96,8 @@ "increaseOnlineAccountsDifficultyTimestamp": 9999999999999, "onlineAccountMinterLevelValidationHeight": 1092000, "selfSponsorshipAlgoV1Height": 1092400, - "selfSponsorshipAlgoV2Height": 9999999, + "selfSponsorshipAlgoV2Height": 1611200, + "selfSponsorshipAlgoV3Height": 1612200, "feeValidationFixTimestamp": 1671918000000, "chatReferenceTimestamp": 1674316800000, "arbitraryOptionalFeeTimestamp": 1680278400000, diff --git a/src/main/resources/i18n/TransactionValidity_de.properties b/src/main/resources/i18n/TransactionValidity_de.properties index bdb88a29..a2019670 100644 --- a/src/main/resources/i18n/TransactionValidity_de.properties +++ b/src/main/resources/i18n/TransactionValidity_de.properties @@ -195,3 +195,5 @@ TRANSACTION_UNKNOWN = Transaktion unbekannt TX_GROUP_ID_MISMATCH = die Gruppen-ID der Transaktion stimmt nicht überein TRANSFER_PRIVS_DISABLED = Übertragungsberechtigungen deaktiviert + +TEMPORARY_DISABLED = Namensregistrierung vorübergehend deaktiviert diff --git a/src/main/resources/i18n/TransactionValidity_en.properties b/src/main/resources/i18n/TransactionValidity_en.properties index 0bfeeef2..a93db3da 100644 --- a/src/main/resources/i18n/TransactionValidity_en.properties +++ b/src/main/resources/i18n/TransactionValidity_en.properties @@ -195,3 +195,5 @@ TRANSACTION_UNKNOWN = transaction unknown TX_GROUP_ID_MISMATCH = transaction's group ID does not match TRANSFER_PRIVS_DISABLED = transfer privileges disabled + +TEMPORARY_DISABLED = Name registration temporary disabled diff --git a/src/main/resources/i18n/TransactionValidity_es.properties b/src/main/resources/i18n/TransactionValidity_es.properties index b3e981ae..8ac7ccf4 100644 --- a/src/main/resources/i18n/TransactionValidity_es.properties +++ b/src/main/resources/i18n/TransactionValidity_es.properties @@ -195,3 +195,5 @@ TRANSACTION_UNKNOWN = transacción desconocida TX_GROUP_ID_MISMATCH = el ID de grupo de la transacción no coincide TRANSFER_PRIVS_DISABLED = privilegios de transferencia deshabilitados + +TEMPORARY_DISABLED = Registro de nombre temporalmente deshabilitado diff --git a/src/main/resources/i18n/TransactionValidity_fi.properties b/src/main/resources/i18n/TransactionValidity_fi.properties index 1a9b9f31..a7bc9c0a 100644 --- a/src/main/resources/i18n/TransactionValidity_fi.properties +++ b/src/main/resources/i18n/TransactionValidity_fi.properties @@ -195,3 +195,5 @@ TRANSACTION_UNKNOWN = tuntematon transaktio TX_GROUP_ID_MISMATCH = transaktion ryhmä-ID:n vastaavuusvirhe TRANSFER_PRIVS_DISABLED = siirtooikeudet poistettu käytöstä + +TEMPORARY_DISABLED = Nimen rekisteröinti tilapäisesti poistettu käytöstä diff --git a/src/main/resources/i18n/TransactionValidity_fr.properties b/src/main/resources/i18n/TransactionValidity_fr.properties index 75fcf369..55ae9082 100644 --- a/src/main/resources/i18n/TransactionValidity_fr.properties +++ b/src/main/resources/i18n/TransactionValidity_fr.properties @@ -195,3 +195,5 @@ TRANSACTION_UNKNOWN = transaction inconnue TX_GROUP_ID_MISMATCH = l'identifiant du groupe de transaction ne correspond pas TRANSFER_PRIVS_DISABLED = privilèges de transfert désactivés + +TEMPORARY_DISABLED = Enregistrement du nom temporairement désactivé diff --git a/src/main/resources/i18n/TransactionValidity_he.properties b/src/main/resources/i18n/TransactionValidity_he.properties index 427f01e3..2f9338f0 100644 --- a/src/main/resources/i18n/TransactionValidity_he.properties +++ b/src/main/resources/i18n/TransactionValidity_he.properties @@ -195,3 +195,5 @@ TRANSACTION_UNKNOWN = עסקה לא ידועה TX_GROUP_ID_MISMATCH = מזהה הקבוצה של העסקה אינו תואם TRANSFER_PRIVS_DISABLED = הרשאות העברה מושבתות + +TEMPORARY_DISABLED = רישום שמות מושבת זמנית diff --git a/src/main/resources/i18n/TransactionValidity_hu.properties b/src/main/resources/i18n/TransactionValidity_hu.properties index 77e7dcff..2dbd9fd0 100644 --- a/src/main/resources/i18n/TransactionValidity_hu.properties +++ b/src/main/resources/i18n/TransactionValidity_hu.properties @@ -195,3 +195,5 @@ TRANSACTION_UNKNOWN = ismeretlen tranzakció TX_GROUP_ID_MISMATCH = a tranzakció csoportazonosítója nem egyezik TRANSFER_PRIVS_DISABLED = átviteli jogosultságok letiltva + +TEMPORARY_DISABLED = A névregisztráció ideiglenesen le van tiltva diff --git a/src/main/resources/i18n/TransactionValidity_it.properties b/src/main/resources/i18n/TransactionValidity_it.properties index 02a13667..a520d5ae 100644 --- a/src/main/resources/i18n/TransactionValidity_it.properties +++ b/src/main/resources/i18n/TransactionValidity_it.properties @@ -195,3 +195,5 @@ TRANSACTION_UNKNOWN = transazione sconosciuta TX_GROUP_ID_MISMATCH = identificazione di gruppo della transazione non corrisponde TRANSFER_PRIVS_DISABLED = privilegi di trasferimento disabilitati + +TEMPORARY_DISABLED = Registrazione del nome temporaneamente disabilitata diff --git a/src/main/resources/i18n/TransactionValidity_jp.properties b/src/main/resources/i18n/TransactionValidity_jp.properties index 10fa273c..3827635c 100644 --- a/src/main/resources/i18n/TransactionValidity_jp.properties +++ b/src/main/resources/i18n/TransactionValidity_jp.properties @@ -195,3 +195,5 @@ TRANSACTION_UNKNOWN = 不明なトランザクション TX_GROUP_ID_MISMATCH = トランザクションのグループIDが一致しません TRANSFER_PRIVS_DISABLED = 転送権限が無効になっています + +TEMPORARY_DISABLED = 名前の登録が一時的に無効になっています diff --git a/src/main/resources/i18n/TransactionValidity_ko.properties b/src/main/resources/i18n/TransactionValidity_ko.properties index cbab9190..2667471c 100644 --- a/src/main/resources/i18n/TransactionValidity_ko.properties +++ b/src/main/resources/i18n/TransactionValidity_ko.properties @@ -195,3 +195,5 @@ TRANSACTION_UNKNOWN = 알 수 없는 거래 TX_GROUP_ID_MISMATCH = 트랜잭션의 그룹 ID가 일치하지 않습니다 TRANSFER_PRIVS_DISABLED = 권한 이전이 비활성화되었습니다. + +TEMPORARY_DISABLED = 이름 등록이 일시적으로 비활성화되었습니다. diff --git a/src/main/resources/i18n/TransactionValidity_nl.properties b/src/main/resources/i18n/TransactionValidity_nl.properties index 933d6166..4f0dd8b7 100644 --- a/src/main/resources/i18n/TransactionValidity_nl.properties +++ b/src/main/resources/i18n/TransactionValidity_nl.properties @@ -195,3 +195,5 @@ TRANSACTION_UNKNOWN = transactie onbekend TX_GROUP_ID_MISMATCH = groep-ID komt niet overeen TRANSFER_PRIVS_DISABLED = overdrachtsrechten uitgeschakeld + +TEMPORARY_DISABLED = Naamregistratie tijdelijk uitgeschakeld diff --git a/src/main/resources/i18n/TransactionValidity_pl.properties b/src/main/resources/i18n/TransactionValidity_pl.properties index 80cb4b12..50944674 100644 --- a/src/main/resources/i18n/TransactionValidity_pl.properties +++ b/src/main/resources/i18n/TransactionValidity_pl.properties @@ -195,3 +195,5 @@ TRANSACTION_UNKNOWN = transakcja nieznana TX_GROUP_ID_MISMATCH = niezgodność ID grupy transakcji TRANSFER_PRIVS_DISABLED = uprawnienia do przenoszenia wyłączone + +TEMPORARY_DISABLED = Rejestracja nazwy tymczasowo wyłączona diff --git a/src/main/resources/i18n/TransactionValidity_ro.properties b/src/main/resources/i18n/TransactionValidity_ro.properties index be0d8596..4c149a62 100644 --- a/src/main/resources/i18n/TransactionValidity_ro.properties +++ b/src/main/resources/i18n/TransactionValidity_ro.properties @@ -195,3 +195,5 @@ TRANSACTION_UNKNOWN = tranzactie necunoscuta TX_GROUP_ID_MISMATCH = ID-ul de grup al tranzactiei nu se potriveste TRANSFER_PRIVS_DISABLED = privilegii de transfer dezactivate + +TEMPORARY_DISABLED = Înregistrarea numelui a fost temporar dezactivată diff --git a/src/main/resources/i18n/TransactionValidity_ru.properties b/src/main/resources/i18n/TransactionValidity_ru.properties index 55b19487..79307d7d 100644 --- a/src/main/resources/i18n/TransactionValidity_ru.properties +++ b/src/main/resources/i18n/TransactionValidity_ru.properties @@ -195,3 +195,5 @@ TRANSACTION_UNKNOWN = неизвестная транзакция TX_GROUP_ID_MISMATCH = не соответствие идентификатора группы в хэш транзации TRANSFER_PRIVS_DISABLED = права на передачу отключены + +TEMPORARY_DISABLED = Регистрация имени временно отключена diff --git a/src/main/resources/i18n/TransactionValidity_sv.properties b/src/main/resources/i18n/TransactionValidity_sv.properties index 2f6f85d9..d4688310 100644 --- a/src/main/resources/i18n/TransactionValidity_sv.properties +++ b/src/main/resources/i18n/TransactionValidity_sv.properties @@ -195,3 +195,5 @@ TRANSACTION_UNKNOWN = okänd transaktion TX_GROUP_ID_MISMATCH = transaktionens grupp-ID matchar inte TRANSFER_PRIVS_DISABLED = överföringsprivilegier inaktiverade + +TEMPORARY_DISABLED = Namnregistrering tillfälligt inaktiverad diff --git a/src/main/resources/i18n/TransactionValidity_zh_CN.properties b/src/main/resources/i18n/TransactionValidity_zh_CN.properties index ce35b7b3..cd16bf64 100644 --- a/src/main/resources/i18n/TransactionValidity_zh_CN.properties +++ b/src/main/resources/i18n/TransactionValidity_zh_CN.properties @@ -195,3 +195,5 @@ TRANSACTION_UNKNOWN = 未知的交易 TX_GROUP_ID_MISMATCH = 群组ID交易不吻合 TRANSFER_PRIVS_DISABLED = 传输权限已禁用 + +TEMPORARY_DISABLED = 名称注册暂时禁用 diff --git a/src/main/resources/i18n/TransactionValidity_zh_TW.properties b/src/main/resources/i18n/TransactionValidity_zh_TW.properties index 13c1210e..d039a8e7 100644 --- a/src/main/resources/i18n/TransactionValidity_zh_TW.properties +++ b/src/main/resources/i18n/TransactionValidity_zh_TW.properties @@ -195,3 +195,5 @@ TRANSACTION_UNKNOWN = 未知的交易 TX_GROUP_ID_MISMATCH = 群組ID交易不吻合 TRANSFER_PRIVS_DISABLED = 傳輸權限已停用 + +TEMPORARY_DISABLED = 名稱註冊暫時停用 diff --git a/src/test/java/org/qortal/test/SelfSponsorshipAlgoV2Tests.java b/src/test/java/org/qortal/test/SelfSponsorshipAlgoV2Tests.java new file mode 100644 index 00000000..495df511 --- /dev/null +++ b/src/test/java/org/qortal/test/SelfSponsorshipAlgoV2Tests.java @@ -0,0 +1,342 @@ +package org.qortal.test; + +import org.junit.Before; +import org.junit.Test; +import org.qortal.account.Account; +import org.qortal.account.PrivateKeyAccount; +import org.qortal.asset.Asset; +import org.qortal.block.Block; +import org.qortal.controller.BlockMinter; +import org.qortal.data.account.AccountPenaltyData; +import org.qortal.data.transaction.*; +import org.qortal.repository.DataException; +import org.qortal.repository.Repository; +import org.qortal.repository.RepositoryManager; +import org.qortal.settings.Settings; +import org.qortal.test.common.BlockUtils; +import org.qortal.test.common.Common; +import org.qortal.test.common.TransactionUtils; +import org.qortal.utils.NTP; + +import java.util.*; + +import static org.junit.Assert.*; +import static org.qortal.test.common.AccountUtils.fee; + +public class SelfSponsorshipAlgoV2Tests extends Common { + + @Before + public void beforeTest() throws DataException { + Common.useSettings("test-settings-v2-self-sponsorship-algo-v2.json"); + NTP.setFixedOffset(Settings.getInstance().getTestNtpOffset()); + } + + @Test + public void tesTransferAssetsQort() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + + // Alice self share online, and will be used to mint the blocks + PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share"); + List onlineAccounts = new ArrayList<>(); + onlineAccounts.add(aliceSelfShare); + + PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob"); + PrivateKeyAccount chloeAccount = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount dilbertAccount = Common.getTestAccount(repository, "dilbert"); + + // Mint blocks + Block block = null; + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Ensure that Bob, Chloe and Dilbert are greater than level 0 + assertTrue(new Account(repository, bobAccount.getAddress()).getLevel() > 0); + assertTrue(new Account(repository, chloeAccount.getAddress()).getLevel() > 0); + assertTrue(new Account(repository, dilbertAccount.getAddress()).getLevel() > 0); + + // Mint some blocks, until accounts have leveled up + for (int i = 0; i <= 5; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Ensure that Chloe and Dilbert have more than 20 qort + assertTrue(new Account(repository, chloeAccount.getAddress()).getConfirmedBalance(Asset.QORT) > 20); // 10 for transfer asset, 10 for fee + assertTrue(new Account(repository, dilbertAccount.getAddress()).getConfirmedBalance(Asset.QORT) > 20); // 10 for transfer asset, 10 for fee + + // Mint until block 10 + while (block.getBlockData().getHeight() < 10) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + assertEquals(10, (int) block.getBlockData().getHeight()); + + // Chloe transfer assets to Bob and Dilbert + transferAssets(repository, chloeAccount, bobAccount); + transferAssets(repository, chloeAccount, dilbertAccount); + + // Dilbert transfer assets to Bob and Chloe + transferAssets(repository, dilbertAccount, bobAccount); + transferAssets(repository, dilbertAccount, chloeAccount); + + // Mint until block 29 (the algo runs at block 30) + while (block.getBlockData().getHeight() < 29) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + assertEquals(29, (int) block.getBlockData().getHeight()); + + // Ensure that Bob have no penalties and level 5 + assertEquals(0, (int) new Account(repository, bobAccount.getAddress()).getBlocksMintedPenalty()); + assertEquals(5, (int)bobAccount.getLevel()); + + // Ensure that Chloe have no penalties and level 5 + assertEquals(0, (int) new Account(repository, chloeAccount.getAddress()).getBlocksMintedPenalty()); + assertEquals(5, (int)chloeAccount.getLevel()); + + // Ensure that Dilbert have no penalties and level6 + assertEquals(0, (int) new Account(repository, dilbertAccount.getAddress()).getBlocksMintedPenalty()); + assertEquals(6, (int)dilbertAccount.getLevel()); + + // Mint a block, so the algo runs + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Ensure that Bob, Chloe and Dilbert are now have penalties + assertEquals(-5000000, (int) new Account(repository, bobAccount.getAddress()).getBlocksMintedPenalty()); + assertEquals(-5000000, (int) new Account(repository, chloeAccount.getAddress()).getBlocksMintedPenalty()); + assertEquals(-5000000, (int) new Account(repository, dilbertAccount.getAddress()).getBlocksMintedPenalty()); + + // Ensure that Bob, Chloe and Dilbert are now level 0 + assertEquals(0, (int) new Account(repository, bobAccount.getAddress()).getLevel()); + assertEquals(0, (int) new Account(repository, chloeAccount.getAddress()).getLevel()); + assertEquals(0, (int) new Account(repository, dilbertAccount.getAddress()).getLevel()); + + // Orphan last block + BlockUtils.orphanLastBlock(repository); + + // Ensure that Bob, Chloe and Dilbert are now greater than level 0 + assertTrue(new Account(repository, bobAccount.getAddress()).getLevel() > 0); + assertTrue(new Account(repository, chloeAccount.getAddress()).getLevel() > 0); + assertTrue(new Account(repository, dilbertAccount.getAddress()).getLevel() > 0); + + // Ensure that Bob, Chloe and Dilbert have no penalties again + assertEquals(0, (int) new Account(repository, bobAccount.getAddress()).getBlocksMintedPenalty()); + assertEquals(0, (int) new Account(repository, chloeAccount.getAddress()).getBlocksMintedPenalty()); + assertEquals(0, (int) new Account(repository, dilbertAccount.getAddress()).getBlocksMintedPenalty()); + + // Run orphan check - this can't be in afterTest() because some tests access the live db + Common.orphanCheck(); + } + } + + @Test + public void testSingleTransferPrivsBeforeAlgoBlock() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + + // Alice self share online, and will be used to mint the blocks + PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share"); + List onlineAccounts = new ArrayList<>(); + onlineAccounts.add(aliceSelfShare); + + PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob"); + + // Mint blocks + Block block = null; + for (int i = 0; i <= 5; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Ensure that Bob have more than 20 qort + assertTrue(new Account(repository, bobAccount.getAddress()).getConfirmedBalance(Asset.QORT) > 20); + + // Mint until block 17 (the algo runs at block 20) + while (block.getBlockData().getHeight() < 26) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + assertEquals(26, (int) block.getBlockData().getHeight()); + + // Bob then issues a TRANSFER_PRIVS + PrivateKeyAccount recipientAccount = randomTransferPrivs(repository, bobAccount); + + // Ensure recipient has no level (actually, no account record) at this point (pre-confirmation) + assertNull(recipientAccount.getLevel()); + + // Mint a block, so that the TRANSFER_PRIVS confirms + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Now ensure that the TRANSFER_PRIVS recipient has inherited Bob's level, and Bob is at level 0 + assertTrue(recipientAccount.getLevel() > 0); + assertEquals(0, (int)bobAccount.getLevel()); + assertEquals(0, (int) new Account(repository, recipientAccount.getAddress()).getBlocksMintedPenalty()); + + // Mint a block, so that we can penalize Bob after transfer privs + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Update blocks minted penalty for Bob + Set penalties = new LinkedHashSet<>(); + penalties.add(new AccountPenaltyData(bobAccount.getAddress(), -5000000)); + repository.getAccountRepository().updateBlocksMintedPenalties(penalties); + + // Mint a block, so that we check if Bob got penalized before algo runs + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Ensure Bob got penalized + assertEquals(-5000000, (int) new Account(repository, bobAccount.getAddress()).getBlocksMintedPenalty()); + + // Mint a block, so the algo runs + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Ensure recipient account has penalty too + assertEquals(-5000000, (int) new Account(repository, recipientAccount.getAddress()).getBlocksMintedPenalty()); + assertEquals(0, (int) new Account(repository, recipientAccount.getAddress()).getLevel()); + + // Orphan last block + BlockUtils.orphanLastBlock(repository); + + // Ensure recipient account has no penalty again and has a level greater than 0 + assertEquals(0, (int) new Account(repository, recipientAccount.getAddress()).getBlocksMintedPenalty()); + assertTrue(new Account(repository, recipientAccount.getAddress()).getLevel() > 0); + + // Run orphan check - this can't be in afterTest() because some tests access the live db + Common.orphanCheck(); + } + } + + @Test + public void testMultipleTransferPrivsBeforeAlgoBlock() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + + // Alice self share online, and will be used to mint the blocks + PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share"); + List onlineAccounts = new ArrayList<>(); + onlineAccounts.add(aliceSelfShare); + + PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob"); + PrivateKeyAccount chloeAccount = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount dilbertAccount = Common.getTestAccount(repository, "dilbert"); + + // Mint blocks + Block block = null; + for (int i = 0; i <= 5; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Ensure that Bob, Chloe and Dilbert have more than 20 qort + assertTrue(new Account(repository, bobAccount.getAddress()).getConfirmedBalance(Asset.QORT) > 20); + assertTrue(new Account(repository, chloeAccount.getAddress()).getConfirmedBalance(Asset.QORT) > 20); + assertTrue(new Account(repository, dilbertAccount.getAddress()).getConfirmedBalance(Asset.QORT) > 20); + + // Mint until block 12 (the algo runs at block 20) + while (block.getBlockData().getHeight() < 22) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + assertEquals(22, (int) block.getBlockData().getHeight()); + + // Bob then issues a TRANSFER_PRIVS + PrivateKeyAccount recipientAccount1 = randomTransferPrivs(repository, bobAccount); + + // Ensure Bob's recipient has no level (actually, no account record) at this point (pre-confirmation) + assertNull(recipientAccount1.getLevel()); + + // Mint a block, so that Bob's TRANSFER_PRIVS confirms + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Now ensure that the Bob's TRANSFER_PRIVS recipient has inherited Bob's level, and Bob is at level 0 + assertTrue(recipientAccount1.getLevel() > 0); + assertEquals(0, (int)bobAccount.getLevel()); + assertEquals(0, (int) new Account(repository, recipientAccount1.getAddress()).getBlocksMintedPenalty()); + + // Mint a block, so that Chloe can issue a TRANSFER_PRIVS + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Chloe then issues a TRANSFER_PRIVS + PrivateKeyAccount recipientAccount2 = randomTransferPrivs(repository, chloeAccount); + + // Ensure Chloe's recipient has no level (actually, no account record) at this point (pre-confirmation) + assertNull(recipientAccount2.getLevel()); + + // Mint a block, so that Chloe's TRANSFER_PRIVS confirms + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Now ensure that the Chloe's TRANSFER_PRIVS recipient has inherited Chloe's level, and Chloe is at level 0 + assertTrue(recipientAccount2.getLevel() > 0); + assertEquals(0, (int)chloeAccount.getLevel()); + assertEquals(0, (int) new Account(repository, recipientAccount2.getAddress()).getBlocksMintedPenalty()); + + // Mint a block, so that Dilbert can issue a TRANSFER_PRIVS + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Dilbert then issues a TRANSFER_PRIVS + PrivateKeyAccount recipientAccount3 = randomTransferPrivs(repository, dilbertAccount); + + // Ensure Dilbert's recipient has no level (actually, no account record) at this point (pre-confirmation) + assertNull(recipientAccount3.getLevel()); + + // Mint a block, so that Dilbert's TRANSFER_PRIVS confirms + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Now ensure that the Dilbert's TRANSFER_PRIVS recipient has inherited Dilbert's level, and Dilbert is at level 0 + assertTrue(recipientAccount3.getLevel() > 0); + assertEquals(0, (int)dilbertAccount.getLevel()); + assertEquals(0, (int) new Account(repository, recipientAccount3.getAddress()).getBlocksMintedPenalty()); + + // Mint a block, so that we can penalize Bob, Chloe and Dilbert after transfer privs + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Update blocks minted penalty for Bob, Chloe and Dilbert + Set penalties = new LinkedHashSet<>(); + penalties.add(new AccountPenaltyData(bobAccount.getAddress(), -5000000)); + penalties.add(new AccountPenaltyData(chloeAccount.getAddress(), -5000000)); + penalties.add(new AccountPenaltyData(dilbertAccount.getAddress(), -5000000)); + repository.getAccountRepository().updateBlocksMintedPenalties(penalties); + + // Mint a block, so that we check if Bob, Chloe and Dilbert got penalized before algo runs + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Ensure Bob, Chloe and Dilbert got penalized + assertEquals(-5000000, (int) new Account(repository, bobAccount.getAddress()).getBlocksMintedPenalty()); + assertEquals(-5000000, (int) new Account(repository, chloeAccount.getAddress()).getBlocksMintedPenalty()); + assertEquals(-5000000, (int) new Account(repository, dilbertAccount.getAddress()).getBlocksMintedPenalty()); + + // Mint a block, so the algo runs + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Ensure recipients accounts has penalty too + assertEquals(-5000000, (int) new Account(repository, recipientAccount1.getAddress()).getBlocksMintedPenalty()); + assertEquals(-5000000, (int) new Account(repository, recipientAccount2.getAddress()).getBlocksMintedPenalty()); + assertEquals(-5000000, (int) new Account(repository, recipientAccount3.getAddress()).getBlocksMintedPenalty()); + assertEquals(0, (int) new Account(repository, recipientAccount1.getAddress()).getLevel()); + assertEquals(0, (int) new Account(repository, recipientAccount2.getAddress()).getLevel()); + assertEquals(0, (int) new Account(repository, recipientAccount3.getAddress()).getLevel()); + + // Orphan last block + BlockUtils.orphanLastBlock(repository); + + // Ensure recipients accounts has no penalty again and has a level greater than 0 + assertEquals(0, (int) new Account(repository, recipientAccount1.getAddress()).getBlocksMintedPenalty()); + assertEquals(0, (int) new Account(repository, recipientAccount2.getAddress()).getBlocksMintedPenalty()); + assertEquals(0, (int) new Account(repository, recipientAccount3.getAddress()).getBlocksMintedPenalty()); + assertTrue(new Account(repository, recipientAccount1.getAddress()).getLevel() > 0); + assertTrue(new Account(repository, recipientAccount2.getAddress()).getLevel() > 0); + assertTrue(new Account(repository, recipientAccount3.getAddress()).getLevel() > 0); + + // Run orphan check - this can't be in afterTest() because some tests access the live db + Common.orphanCheck(); + } + } + + private static void transferAssets(Repository repository, PrivateKeyAccount senderAccount, PrivateKeyAccount recipientAccount) throws DataException { + for (int i = 0; i < 5; i++) { + // Generate new asset transfers from sender to recipient + BaseTransactionData baseTransactionData = new BaseTransactionData(NTP.getTime(), 0, senderAccount.getLastReference(), senderAccount.getPublicKey(), fee, null); + TransactionData transactionData; + transactionData = new TransferAssetTransactionData(baseTransactionData, recipientAccount.getAddress(), 1, 0); + TransactionUtils.signAndImportValid(repository, transactionData, senderAccount); // updates paymentData's signature + } + } + + private static PrivateKeyAccount randomTransferPrivs(Repository repository, PrivateKeyAccount senderAccount) throws DataException { + // Generate random recipient account + byte[] randomPrivateKey = new byte[32]; + new Random().nextBytes(randomPrivateKey); + PrivateKeyAccount recipientAccount = new PrivateKeyAccount(repository, randomPrivateKey); + + BaseTransactionData baseTransactionData = new BaseTransactionData(NTP.getTime(), 0, senderAccount.getLastReference(), senderAccount.getPublicKey(), fee, null); + TransactionData transactionData = new TransferPrivsTransactionData(baseTransactionData, recipientAccount.getAddress()); + + TransactionUtils.signAndImportValid(repository, transactionData, senderAccount); + + return recipientAccount; + } + +} \ No newline at end of file diff --git a/src/test/java/org/qortal/test/SelfSponsorshipAlgoV3Tests.java b/src/test/java/org/qortal/test/SelfSponsorshipAlgoV3Tests.java new file mode 100644 index 00000000..bb9de4e3 --- /dev/null +++ b/src/test/java/org/qortal/test/SelfSponsorshipAlgoV3Tests.java @@ -0,0 +1,1578 @@ +package org.qortal.test; + +import org.junit.Before; +import org.junit.Test; +import org.qortal.account.Account; +import org.qortal.account.PrivateKeyAccount; +import org.qortal.asset.Asset; +import org.qortal.block.Block; +import org.qortal.controller.BlockMinter; +import org.qortal.data.transaction.BaseTransactionData; +import org.qortal.data.transaction.PaymentTransactionData; +import org.qortal.data.transaction.TransactionData; +import org.qortal.data.transaction.TransferPrivsTransactionData; +import org.qortal.repository.DataException; +import org.qortal.repository.Repository; +import org.qortal.repository.RepositoryManager; +import org.qortal.settings.Settings; +import org.qortal.test.common.AccountUtils; +import org.qortal.test.common.BlockUtils; +import org.qortal.test.common.Common; +import org.qortal.test.common.TransactionUtils; +import org.qortal.test.common.transaction.TestTransaction; +import org.qortal.transaction.Transaction; +import org.qortal.transaction.TransferPrivsTransaction; +import org.qortal.utils.NTP; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import static org.junit.Assert.*; +import static org.qortal.test.common.AccountUtils.fee; +import static org.qortal.transaction.Transaction.ValidationResult.ACCOUNT_NOT_TRANSFERABLE; +import static org.qortal.transaction.Transaction.ValidationResult.OK; + +public class SelfSponsorshipAlgoV3Tests extends Common { + + + @Before + public void beforeTest() throws DataException { + Common.useSettings("test-settings-v2-self-sponsorship-algo-v3.json"); + NTP.setFixedOffset(Settings.getInstance().getTestNtpOffset()); + } + + + @Test + public void testSingleSponsor() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + final int initialRewardShareCount = repository.getAccountRepository().getRewardShares().size(); + + // Alice self share online, and will be used to mint the blocks + PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share"); + List onlineAccounts = new ArrayList<>(); + onlineAccounts.add(aliceSelfShare); + + PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob"); + + // Bob self sponsors 10 accounts + List bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 10); + List bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees); + onlineAccounts.addAll(bobSponseesOnlineAccounts); + + // Mint blocks + Block block = null; + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + assertEquals(11, block.getBlockData().getOnlineAccountsCount()); + assertEquals(10, repository.getAccountRepository().getRewardShares().size() - initialRewardShareCount); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() == 0); + + // Mint some blocks, until accounts have leveled up + for (int i = 0; i <= 5; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0); + + // Generate self shares so the sponsees can start minting + List bobSponseeSelfShares = AccountUtils.generateSelfShares(repository, bobSponsees); + onlineAccounts.addAll(bobSponseeSelfShares); + + // Mint blocks + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getConfirmedBalance(Asset.QORT) > 10); // 5 for transaction, 5 for fee + + // Bob then consolidates funds + consolidateFunds(repository, bobSponsees, bobAccount); + + // Mint until block 29 (the algo runs at block 30) + while (block.getBlockData().getHeight() < 29) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + assertEquals(29, (int) block.getBlockData().getHeight()); + + // Ensure that bob and his sponsees have no penalties + List bobAndSponsees = new ArrayList<>(bobSponsees); + bobAndSponsees.add(bobAccount); + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Mint a block, so the algo runs + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Ensure that bob and his sponsees now have penalties + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(-5000000, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that bob and his sponsees are now level 0 + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getLevel()); + + // Orphan last block + BlockUtils.orphanLastBlock(repository); + + // Ensure that bob and his sponsees are now greater than level 0 + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0); + + // Ensure that bob and his sponsees have no penalties again + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Run orphan check - this can't be in afterTest() because some tests access the live db + Common.orphanCheck(); + } + } + + @Test + public void testMultipleSponsors() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + final int initialRewardShareCount = repository.getAccountRepository().getRewardShares().size(); + + // Alice self share online, and will be used to mint the blocks + PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share"); + List onlineAccounts = new ArrayList<>(); + onlineAccounts.add(aliceSelfShare); + + PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob"); + PrivateKeyAccount chloeAccount = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount dilbertAccount = Common.getTestAccount(repository, "dilbert"); + + // Bob sponsors 10 accounts + List bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 10); + List bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees); + onlineAccounts.addAll(bobSponseesOnlineAccounts); + + // Chloe sponsors 10 accounts + List chloeSponsees = AccountUtils.generateSponsorshipRewardShares(repository, chloeAccount, 10); + List chloeSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, chloeAccount, chloeSponsees); + onlineAccounts.addAll(chloeSponseesOnlineAccounts); + + // Dilbert sponsors 5 accounts + List dilbertSponsees = AccountUtils.generateSponsorshipRewardShares(repository, dilbertAccount, 5); + List dilbertSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, dilbertAccount, dilbertSponsees); + onlineAccounts.addAll(dilbertSponseesOnlineAccounts); + + // Mint blocks + Block block = null; + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + assertEquals(26, block.getBlockData().getOnlineAccountsCount()); + assertEquals(25, repository.getAccountRepository().getRewardShares().size() - initialRewardShareCount); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() == 0); + + // Mint some blocks, until accounts have leveled up + for (int i = 0; i <= 5; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0); + + // Generate self shares so the sponsees can start minting + List bobSponseeSelfShares = AccountUtils.generateSelfShares(repository, bobSponsees); + onlineAccounts.addAll(bobSponseeSelfShares); + + // Mint blocks + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getConfirmedBalance(Asset.QORT) > 10); // 5 for transaction, 5 for fee + + // Bob then consolidates funds + consolidateFunds(repository, bobSponsees, bobAccount); + + // Mint until block 29 (the algo runs at block 30) + while (block.getBlockData().getHeight() < 29) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + assertEquals(29, (int) block.getBlockData().getHeight()); + + // Ensure that bob and his sponsees have no penalties + List bobAndSponsees = new ArrayList<>(bobSponsees); + bobAndSponsees.add(bobAccount); + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + assertEquals(6, (int)dilbertAccount.getLevel()); + + // Mint a block, so the algo runs + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Ensure that bob and his sponsees now have penalties + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(-5000000, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that chloe and her sponsees have no penalties + List chloeAndSponsees = new ArrayList<>(chloeSponsees); + chloeAndSponsees.add(chloeAccount); + for (PrivateKeyAccount chloeSponsee : chloeAndSponsees) + assertEquals(0, (int) new Account(repository, chloeSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that dilbert and his sponsees have no penalties + List dilbertAndSponsees = new ArrayList<>(dilbertSponsees); + dilbertAndSponsees.add(dilbertAccount); + for (PrivateKeyAccount dilbertSponsee : dilbertAndSponsees) + assertEquals(0, (int) new Account(repository, dilbertSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that bob and his sponsees are now level 0 + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getLevel()); + + // Orphan last block + BlockUtils.orphanLastBlock(repository); + + // Ensure that bob and his sponsees are now greater than level 0 + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0); + + // Ensure that bob and his sponsees have no penalties again + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that chloe and her sponsees still have no penalties + for (PrivateKeyAccount chloeSponsee : chloeAndSponsees) + assertEquals(0, (int) new Account(repository, chloeSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that dilbert and his sponsees still have no penalties + for (PrivateKeyAccount dilbertSponsee : dilbertAndSponsees) + assertEquals(0, (int) new Account(repository, dilbertSponsee.getAddress()).getBlocksMintedPenalty()); + + // Run orphan check - this can't be in afterTest() because some tests access the live db + Common.orphanCheck(); + } + } + + @Test + public void testMintBlockWithSignerPenalty() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + final int initialRewardShareCount = repository.getAccountRepository().getRewardShares().size(); + + List onlineAccountsAliceSigner = new ArrayList<>(); + List onlineAccountsBobSigner = new ArrayList<>(); + + // Alice self share online, and will be used to mint (some of) the blocks + PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share"); + onlineAccountsAliceSigner.add(aliceSelfShare); + + // Bob self share online, and will be used to mint (some of) the blocks + PrivateKeyAccount bobSelfShare = Common.getTestAccount(repository, "bob-reward-share"); + onlineAccountsBobSigner.add(bobSelfShare); + + // Include Alice and Bob's online accounts in each other's arrays + onlineAccountsAliceSigner.add(bobSelfShare); + onlineAccountsBobSigner.add(aliceSelfShare); + + PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob"); + PrivateKeyAccount chloeAccount = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount dilbertAccount = Common.getTestAccount(repository, "dilbert"); + + // Bob sponsors 10 accounts + List bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 10); + List bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees); + onlineAccountsAliceSigner.addAll(bobSponseesOnlineAccounts); + onlineAccountsBobSigner.addAll(bobSponseesOnlineAccounts); + + // Chloe sponsors 10 accounts + List chloeSponsees = AccountUtils.generateSponsorshipRewardShares(repository, chloeAccount, 10); + List chloeSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, chloeAccount, chloeSponsees); + onlineAccountsAliceSigner.addAll(chloeSponseesOnlineAccounts); + onlineAccountsBobSigner.addAll(chloeSponseesOnlineAccounts); + + // Dilbert sponsors 5 accounts + List dilbertSponsees = AccountUtils.generateSponsorshipRewardShares(repository, dilbertAccount, 5); + List dilbertSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, dilbertAccount, dilbertSponsees); + onlineAccountsAliceSigner.addAll(dilbertSponseesOnlineAccounts); + onlineAccountsBobSigner.addAll(dilbertSponseesOnlineAccounts); + + // Mint blocks (Bob is the signer) + Block block = null; + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccountsBobSigner.toArray(new PrivateKeyAccount[0])); + + // Get reward share transaction count + assertEquals(25, repository.getAccountRepository().getRewardShares().size() - initialRewardShareCount); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() == 0); + + // Mint some blocks, until accounts have leveled up (Bob is the signer) + for (int i = 0; i <= 5; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccountsBobSigner.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0); + + // Generate self shares so the sponsees can start minting + List bobSponseeSelfShares = AccountUtils.generateSelfShares(repository, bobSponsees); + onlineAccountsAliceSigner.addAll(bobSponseeSelfShares); + onlineAccountsBobSigner.addAll(bobSponseeSelfShares); + + // Mint blocks (Bob is the signer) + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccountsBobSigner.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getConfirmedBalance(Asset.QORT) > 10); // 5 for transaction, 5 for fee + + // Bob then consolidates funds + consolidateFunds(repository, bobSponsees, bobAccount); + + // Mint until block 29 (the algo runs at block 30) (Bob is the signer) + while (block.getBlockData().getHeight() < 29) + block = BlockMinter.mintTestingBlock(repository, onlineAccountsBobSigner.toArray(new PrivateKeyAccount[0])); + assertEquals(29, (int) block.getBlockData().getHeight()); + + // Ensure that bob and his sponsees have no penalties + List bobAndSponsees = new ArrayList<>(bobSponsees); + bobAndSponsees.add(bobAccount); + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Mint a block, so the algo runs (Bob is the signer) + // Block should be valid, because new account levels don't take effect until next block's validation + block = BlockMinter.mintTestingBlock(repository, onlineAccountsBobSigner.toArray(new PrivateKeyAccount[0])); + + // Ensure that bob and his sponsees now have penalties + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(-5000000, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that bob and his sponsees are now level 0 + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getLevel()); + + // Mint a block, but Bob is now an invalid signer because he is level 0 + block = BlockMinter.mintTestingBlockUnvalidated(repository, onlineAccountsBobSigner.toArray(new PrivateKeyAccount[0])); + // Block should be null as it's unable to be minted + assertNull(block); + + // Mint the same block with Alice as the signer, and this time it should be valid + block = BlockMinter.mintTestingBlock(repository, onlineAccountsAliceSigner.toArray(new PrivateKeyAccount[0])); + // Block should NOT be null + assertNotNull(block); + + // Run orphan check - this can't be in afterTest() because some tests access the live db + Common.orphanCheck(); + } + } + + @Test + public void testMintBlockWithFounderSignerPenalty() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + final int initialRewardShareCount = repository.getAccountRepository().getRewardShares().size(); + + List onlineAccountsAliceSigner = new ArrayList<>(); + List onlineAccountsBobSigner = new ArrayList<>(); + + // Alice self share online, and will be used to mint (some of) the blocks + PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share"); + onlineAccountsAliceSigner.add(aliceSelfShare); + + // Bob self share online, and will be used to mint (some of) the blocks + PrivateKeyAccount bobSelfShare = Common.getTestAccount(repository, "bob-reward-share"); + onlineAccountsBobSigner.add(bobSelfShare); + + // Include Alice and Bob's online accounts in each other's arrays + onlineAccountsAliceSigner.add(bobSelfShare); + onlineAccountsBobSigner.add(aliceSelfShare); + + PrivateKeyAccount aliceAccount = Common.getTestAccount(repository, "alice"); + PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob"); + + // Alice sponsors 10 accounts + List aliceSponsees = AccountUtils.generateSponsorshipRewardShares(repository, aliceAccount, 10); + List aliceSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, aliceAccount, aliceSponsees); + onlineAccountsAliceSigner.addAll(aliceSponseesOnlineAccounts); + onlineAccountsBobSigner.addAll(aliceSponseesOnlineAccounts); + + // Bob sponsors 9 accounts + List bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 9); + List bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees); + onlineAccountsAliceSigner.addAll(bobSponseesOnlineAccounts); + onlineAccountsBobSigner.addAll(bobSponseesOnlineAccounts); + + // Mint blocks (Bob is the signer) + Block block = null; + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccountsAliceSigner.toArray(new PrivateKeyAccount[0])); + + // Get reward share transaction count + assertEquals(19, repository.getAccountRepository().getRewardShares().size() - initialRewardShareCount); + + for (PrivateKeyAccount aliceSponsee : aliceSponsees) + assertTrue(new Account(repository, aliceSponsee.getAddress()).getLevel() == 0); + + // Mint some blocks, until accounts have leveled up (Alice is the signer) + for (int i = 0; i <= 5; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccountsAliceSigner.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount aliceSponsee : aliceSponsees) + assertTrue(new Account(repository, aliceSponsee.getAddress()).getLevel() > 0); + + // Generate self shares so the sponsees can start minting + List aliceSponseeSelfShares = AccountUtils.generateSelfShares(repository, aliceSponsees); + onlineAccountsAliceSigner.addAll(aliceSponseeSelfShares); + onlineAccountsBobSigner.addAll(aliceSponseeSelfShares); + + // Mint blocks (Bob is the signer) + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccountsAliceSigner.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount aliceSponsee : aliceSponsees) + assertTrue(new Account(repository, aliceSponsee.getAddress()).getConfirmedBalance(Asset.QORT) > 10); // 5 for transaction, 5 for fee + + // Alice then consolidates funds + consolidateFunds(repository, aliceSponsees, aliceAccount); + + // Mint until block 29 (the algo runs at block 30) (Bob is the signer) + while (block.getBlockData().getHeight() < 29) + block = BlockMinter.mintTestingBlock(repository, onlineAccountsAliceSigner.toArray(new PrivateKeyAccount[0])); + assertEquals(29, (int) block.getBlockData().getHeight()); + + // Ensure that alice and her sponsees have no penalties + List aliceAndSponsees = new ArrayList<>(aliceSponsees); + aliceAndSponsees.add(aliceAccount); + for (PrivateKeyAccount aliceSponsee : aliceAndSponsees) + assertEquals(0, (int) new Account(repository, aliceSponsee.getAddress()).getBlocksMintedPenalty()); + + // Mint a block, so the algo runs (Alice is the signer) + // Block should be valid, because new account levels don't take effect until next block's validation + block = BlockMinter.mintTestingBlock(repository, onlineAccountsAliceSigner.toArray(new PrivateKeyAccount[0])); + + // Ensure that alice and her sponsees now have penalties + for (PrivateKeyAccount aliceSponsee : aliceAndSponsees) + assertEquals(-5000000, (int) new Account(repository, aliceSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that alice and her sponsees are now level 0 + for (PrivateKeyAccount aliceSponsee : aliceAndSponsees) + assertEquals(0, (int) new Account(repository, aliceSponsee.getAddress()).getLevel()); + + // Mint a block, but Alice is now an invalid signer because she has lost founder minting abilities + block = BlockMinter.mintTestingBlockUnvalidated(repository, onlineAccountsAliceSigner.toArray(new PrivateKeyAccount[0])); + // Block should be null as it's unable to be minted + assertNull(block); + + // Mint the same block with Bob as the signer, and this time it should be valid + block = BlockMinter.mintTestingBlock(repository, onlineAccountsBobSigner.toArray(new PrivateKeyAccount[0])); + // Block should NOT be null + assertNotNull(block); + + // Run orphan check - this can't be in afterTest() because some tests access the live db + Common.orphanCheck(); + } + } + + @Test + public void testOnlineAccountsWithPenalties() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + final int initialRewardShareCount = repository.getAccountRepository().getRewardShares().size(); + + // Alice self share online, and will be used to mint the blocks + PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share"); + List onlineAccounts = new ArrayList<>(); + onlineAccounts.add(aliceSelfShare); + + // Bob self share online + PrivateKeyAccount bobSelfShare = Common.getTestAccount(repository, "bob-reward-share"); + onlineAccounts.add(bobSelfShare); + + PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob"); + PrivateKeyAccount chloeAccount = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount dilbertAccount = Common.getTestAccount(repository, "dilbert"); + + // Bob sponsors 10 accounts + List bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 10); + List bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees); + onlineAccounts.addAll(bobSponseesOnlineAccounts); + + // Chloe sponsors 10 accounts + List chloeSponsees = AccountUtils.generateSponsorshipRewardShares(repository, chloeAccount, 10); + List chloeSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, chloeAccount, chloeSponsees); + onlineAccounts.addAll(chloeSponseesOnlineAccounts); + + // Dilbert sponsors 5 accounts + List dilbertSponsees = AccountUtils.generateSponsorshipRewardShares(repository, dilbertAccount, 5); + List dilbertSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, dilbertAccount, dilbertSponsees); + onlineAccounts.addAll(dilbertSponseesOnlineAccounts); + + // Mint blocks + Block block = null; + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + assertEquals(27, block.getBlockData().getOnlineAccountsCount()); + assertEquals(25, repository.getAccountRepository().getRewardShares().size() - initialRewardShareCount); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() == 0); + + // Mint some blocks, until accounts have leveled up + for (int i = 0; i <= 5; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0); + + // Generate self shares so the sponsees can start minting + List bobSponseeSelfShares = AccountUtils.generateSelfShares(repository, bobSponsees); + onlineAccounts.addAll(bobSponseeSelfShares); + + // Mint blocks + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getConfirmedBalance(Asset.QORT) > 10); // 5 for transaction, 5 for fee + + // Bob then consolidates funds + consolidateFunds(repository, bobSponsees, bobAccount); + + // Mint until block 29 (the algo runs at block 30) + while (block.getBlockData().getHeight() < 29) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + assertEquals(29, (int) block.getBlockData().getHeight()); + + // Ensure that bob and his sponsees have no penalties + List bobAndSponsees = new ArrayList<>(bobSponsees); + bobAndSponsees.add(bobAccount); + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that bob and his sponsees are present in block's online accounts + assertTrue(areAllAccountsPresentInBlock(bobAndSponsees, block)); + + // Ensure that chloe's sponsees are present in block's online accounts + assertTrue(areAllAccountsPresentInBlock(chloeSponsees, block)); + + // Ensure that dilbert's sponsees are present in block's online accounts + assertTrue(areAllAccountsPresentInBlock(dilbertSponsees, block)); + + // Mint a block, so the algo runs + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Ensure that bob and his sponsees now have penalties + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(-5000000, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that bob and his sponsees are still present in block's online accounts + assertTrue(areAllAccountsPresentInBlock(bobAndSponsees, block)); + + // Mint another few blocks + while (block.getBlockData().getHeight() < 34) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + assertEquals(34, (int)block.getBlockData().getHeight()); + + // Ensure that bob and his sponsees are NOT present in block's online accounts (due to penalties) + assertFalse(areAllAccountsPresentInBlock(bobAndSponsees, block)); + + // Ensure that chloe's sponsees are still present in block's online accounts + assertTrue(areAllAccountsPresentInBlock(chloeSponsees, block)); + + // Ensure that dilbert's sponsees are still present in block's online accounts + assertTrue(areAllAccountsPresentInBlock(dilbertSponsees, block)); + + // Run orphan check - this can't be in afterTest() because some tests access the live db + Common.orphanCheck(); + } + } + + @Test + public void testFounderOnlineAccountsWithPenalties() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + final int initialRewardShareCount = repository.getAccountRepository().getRewardShares().size(); + + // Bob self share online, and will be used to mint the blocks + PrivateKeyAccount bobSelfShare = Common.getTestAccount(repository, "bob-reward-share"); + List onlineAccounts = new ArrayList<>(); + onlineAccounts.add(bobSelfShare); + + // Alice self share online + PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share"); + onlineAccounts.add(aliceSelfShare); + + PrivateKeyAccount aliceAccount = Common.getTestAccount(repository, "alice"); + PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob"); + + // Alice sponsors 10 accounts + List aliceSponsees = AccountUtils.generateSponsorshipRewardShares(repository, aliceAccount, 10); + List aliceSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, aliceAccount, aliceSponsees); + onlineAccounts.addAll(aliceSponseesOnlineAccounts); + onlineAccounts.addAll(aliceSponseesOnlineAccounts); + + // Bob sponsors 9 accounts + List bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 9); + List bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees); + onlineAccounts.addAll(bobSponseesOnlineAccounts); + onlineAccounts.addAll(bobSponseesOnlineAccounts); + + // Mint blocks (Bob is the signer) + Block block = null; + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Get reward share transaction count + assertEquals(19, repository.getAccountRepository().getRewardShares().size() - initialRewardShareCount); + + for (PrivateKeyAccount aliceSponsee : aliceSponsees) + assertTrue(new Account(repository, aliceSponsee.getAddress()).getLevel() == 0); + + // Mint some blocks, until accounts have leveled up (Alice is the signer) + for (int i = 0; i <= 5; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount aliceSponsee : aliceSponsees) + assertTrue(new Account(repository, aliceSponsee.getAddress()).getLevel() > 0); + + // Generate self shares so the sponsees can start minting + List aliceSponseeSelfShares = AccountUtils.generateSelfShares(repository, aliceSponsees); + onlineAccounts.addAll(aliceSponseeSelfShares); + + // Mint blocks (Bob is the signer) + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount aliceSponsee : aliceSponsees) + assertTrue(new Account(repository, aliceSponsee.getAddress()).getConfirmedBalance(Asset.QORT) > 10); // 5 for transaction, 5 for fee + + // Alice then consolidates funds + consolidateFunds(repository, aliceSponsees, aliceAccount); + + // Mint until block 29 (the algo runs at block 30) (Bob is the signer) + while (block.getBlockData().getHeight() < 29) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + assertEquals(29, (int) block.getBlockData().getHeight()); + + // Ensure that alice and her sponsees have no penalties + List aliceAndSponsees = new ArrayList<>(aliceSponsees); + aliceAndSponsees.add(aliceAccount); + for (PrivateKeyAccount aliceSponsee : aliceAndSponsees) + assertEquals(0, (int) new Account(repository, aliceSponsee.getAddress()).getBlocksMintedPenalty()); + + // Mint a block, so the algo runs (Alice is the signer) + // Block should be valid, because new account levels don't take effect until next block's validation + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Ensure that alice and her sponsees now have penalties + for (PrivateKeyAccount aliceSponsee : aliceAndSponsees) + assertEquals(-5000000, (int) new Account(repository, aliceSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that alice and her sponsees are now level 0 + for (PrivateKeyAccount aliceSponsee : aliceAndSponsees) + assertEquals(0, (int) new Account(repository, aliceSponsee.getAddress()).getLevel()); + + // Ensure that alice and her sponsees don't have penalties + List bobAndSponsees = new ArrayList<>(bobSponsees); + bobAndSponsees.add(bobAccount); + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that bob and his sponsees are still present in block's online accounts + assertTrue(areAllAccountsPresentInBlock(bobAndSponsees, block)); + + // Ensure that alice and her sponsees are still present in block's online accounts + assertTrue(areAllAccountsPresentInBlock(aliceAndSponsees, block)); + + // Mint another few blocks + while (block.getBlockData().getHeight() < 34) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + assertEquals(34, (int)block.getBlockData().getHeight()); + + // Ensure that alice and her sponsees are NOT present in block's online accounts (due to penalties) + assertFalse(areAllAccountsPresentInBlock(aliceAndSponsees, block)); + + // Ensure that bob and his sponsees are still present in block's online accounts + assertTrue(areAllAccountsPresentInBlock(bobAndSponsees, block)); + + // Run orphan check - this can't be in afterTest() because some tests access the live db + Common.orphanCheck(); + } + } + + @Test + public void testPenaltyAccountCreateRewardShare() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + final int initialRewardShareCount = repository.getAccountRepository().getRewardShares().size(); + + // Alice self share online, and will be used to mint the blocks + PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share"); + List onlineAccounts = new ArrayList<>(); + onlineAccounts.add(aliceSelfShare); + + PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob"); + PrivateKeyAccount chloeAccount = Common.getTestAccount(repository, "chloe"); + + // Bob sponsors 10 accounts + List bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 10); + List bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees); + onlineAccounts.addAll(bobSponseesOnlineAccounts); + + // Chloe sponsors 10 accounts + List chloeSponsees = AccountUtils.generateSponsorshipRewardShares(repository, chloeAccount, 10); + List chloeSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, chloeAccount, chloeSponsees); + onlineAccounts.addAll(chloeSponseesOnlineAccounts); + + // Mint blocks + Block block = null; + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + assertEquals(21, block.getBlockData().getOnlineAccountsCount()); + assertEquals(20, repository.getAccountRepository().getRewardShares().size() - initialRewardShareCount); + + // Mint some blocks, until accounts have leveled up + for (int i = 0; i <= 5; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Generate self shares so the sponsees can start minting + List bobSponseeSelfShares = AccountUtils.generateSelfShares(repository, bobSponsees); + onlineAccounts.addAll(bobSponseeSelfShares); + + // Mint some blocks, until accounts have leveled up + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Bob then consolidates funds + consolidateFunds(repository, bobSponsees, bobAccount); + + // Mint until block 12 (rewardshare get disabled at block 15) + while (block.getBlockData().getHeight() < 12) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + assertEquals(12, (int) block.getBlockData().getHeight()); + + // Bob creates a valid reward share transaction + assertEquals(Transaction.ValidationResult.OK, AccountUtils.createRandomRewardShare(repository, bobAccount)); + + // Mint until block 30 (the algo runs at block 30) + while (block.getBlockData().getHeight() < 30) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + assertEquals(30, (int) block.getBlockData().getHeight()); + + // Bob can no longer create a reward share transaction + assertEquals(Transaction.ValidationResult.ACCOUNT_CANNOT_REWARD_SHARE, AccountUtils.createRandomRewardShare(repository, bobAccount)); + + // ... but Chloe still can + assertEquals(Transaction.ValidationResult.OK, AccountUtils.createRandomRewardShare(repository, chloeAccount)); + + // Orphan last block + BlockUtils.orphanLastBlock(repository); + + // Bob creates another valid reward share transaction + assertEquals(Transaction.ValidationResult.OK, AccountUtils.createRandomRewardShare(repository, bobAccount)); + + // Run orphan check - this can't be in afterTest() because some tests access the live db + Common.orphanCheck(); + } + } + + @Test + public void testPenaltyFounderCreateRewardShare() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + final int initialRewardShareCount = repository.getAccountRepository().getRewardShares().size(); + + // Bob self share online, and will be used to mint the blocks + PrivateKeyAccount bobSelfShare = Common.getTestAccount(repository, "bob-reward-share"); + List onlineAccounts = new ArrayList<>(); + onlineAccounts.add(bobSelfShare); + + PrivateKeyAccount aliceAccount = Common.getTestAccount(repository, "alice"); + PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob"); + + // Alice sponsors 10 accounts + List aliceSponsees = AccountUtils.generateSponsorshipRewardShares(repository, aliceAccount, 10); + List aliceSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, aliceAccount, aliceSponsees); + onlineAccounts.addAll(aliceSponseesOnlineAccounts); + + // Bob sponsors 10 accounts + List bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 10); + List bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees); + onlineAccounts.addAll(bobSponseesOnlineAccounts); + + // Mint blocks + Block block = null; + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + assertEquals(21, block.getBlockData().getOnlineAccountsCount()); + assertEquals(20, repository.getAccountRepository().getRewardShares().size() - initialRewardShareCount); + + // Mint some blocks, until accounts have leveled up + for (int i = 0; i <= 5; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Generate self shares so the sponsees can start minting + List aliceSponseeSelfShares = AccountUtils.generateSelfShares(repository, aliceSponsees); + onlineAccounts.addAll(aliceSponseeSelfShares); + + // Mint some blocks + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Alice then consolidates funds + consolidateFunds(repository, aliceSponsees, aliceAccount); + + // Mint until block 12 (rewardshare get disabled at block 15) + while (block.getBlockData().getHeight() < 12) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + assertEquals(12, (int) block.getBlockData().getHeight()); + + // Alice creates a valid reward share transaction + assertEquals(Transaction.ValidationResult.OK, AccountUtils.createRandomRewardShare(repository, aliceAccount)); + + // Mint until block 30 (the algo runs at block 30) + while (block.getBlockData().getHeight() < 30) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + assertEquals(30, (int) block.getBlockData().getHeight()); + + // Ensure that alice now has a penalty + assertEquals(-5000000, (int) new Account(repository, aliceAccount.getAddress()).getBlocksMintedPenalty()); + + // Ensure that alice and her sponsees are now level 0 + assertEquals(0, (int) new Account(repository, aliceAccount.getAddress()).getLevel()); + + // Alice can no longer create a reward share transaction + assertEquals(Transaction.ValidationResult.ACCOUNT_CANNOT_REWARD_SHARE, AccountUtils.createRandomRewardShare(repository, aliceAccount)); + + // Bob can no longer create a reward share transaction (disabled at Block 15) + assertEquals(Transaction.ValidationResult.OK, AccountUtils.createRandomRewardShare(repository, bobAccount)); + + // Orphan last block + BlockUtils.orphanLastBlock(repository); + + // Alice creates another valid reward share transaction + assertEquals(Transaction.ValidationResult.OK, AccountUtils.createRandomRewardShare(repository, aliceAccount)); + + // Run orphan check - this can't be in afterTest() because some tests access the live db + Common.orphanCheck(); + } + } + + /** + * This is a test to prove that Dilbert levels up from 6 to 7 in the same block that the self + * sponsorship algo runs. It is here to give some confidence in the following testPenaltyAccountLevelUp() + * test, in which we will test what happens if a penalty is applied or removed in the same block + * that an account would otherwise have leveled up. It also gives some confidence that the algo + * doesn't affect the levels of unflagged accounts. + * + * @throws DataException + */ + @Test + public void testNonPenaltyAccountLevelUp() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + + // Alice self share online, and will be used to mint the blocks + PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share"); + List onlineAccounts = new ArrayList<>(); + onlineAccounts.add(aliceSelfShare); + + PrivateKeyAccount dilbertAccount = Common.getTestAccount(repository, "dilbert"); + + // Dilbert sponsors 10 accounts + List dilbertSponsees = AccountUtils.generateSponsorshipRewardShares(repository, dilbertAccount, 10); + List dilbertSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, dilbertAccount, dilbertSponsees); + onlineAccounts.addAll(dilbertSponseesOnlineAccounts); + + // Mint blocks + Block block = null; + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Mint some blocks, until accounts have leveled up + for (int i = 0; i <= 5; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Mint blocks + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Mint until block 29 (the algo runs at block 30) + while (block.getBlockData().getHeight() < 29) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + assertEquals(29, (int) block.getBlockData().getHeight()); + + // Make sure Dilbert hasn't leveled up yet + assertEquals(6, (int)dilbertAccount.getLevel()); + + // Mint a block, so the algo runs + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Make sure Dilbert has leveled up + assertEquals(7, (int)dilbertAccount.getLevel()); + + // Orphan last block + BlockUtils.orphanLastBlock(repository); + + // Make sure Dilbert has returned to level 6 + assertEquals(6, (int)dilbertAccount.getLevel()); + + // Run orphan check - this can't be in afterTest() because some tests access the live db + Common.orphanCheck(); + } + } + + @Test + public void testPenaltyAccountLevelUp() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + + // Alice self share online, and will be used to mint the blocks + PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share"); + List onlineAccounts = new ArrayList<>(); + onlineAccounts.add(aliceSelfShare); + + PrivateKeyAccount dilbertAccount = Common.getTestAccount(repository, "dilbert"); + + // Dilbert sponsors 10 accounts + List dilbertSponsees = AccountUtils.generateSponsorshipRewardShares(repository, dilbertAccount, 10); + List dilbertSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, dilbertAccount, dilbertSponsees); + onlineAccounts.addAll(dilbertSponseesOnlineAccounts); + + // Mint blocks + Block block = null; + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Mint some blocks, until accounts have leveled up + for (int i = 0; i <= 5; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Generate self shares so the sponsees can start minting + List dilbertSponseeSelfShares = AccountUtils.generateSelfShares(repository, dilbertSponsees); + onlineAccounts.addAll(dilbertSponseeSelfShares); + + // Mint blocks + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Dilbert then consolidates funds + consolidateFunds(repository, dilbertSponsees, dilbertAccount); + + // Mint until block 29 (the algo runs at block 30) + while (block.getBlockData().getHeight() < 29) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + assertEquals(29, (int) block.getBlockData().getHeight()); + + // Make sure Dilbert hasn't leveled up yet + assertEquals(6, (int)dilbertAccount.getLevel()); + + // Mint a block, so the algo runs + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Make sure Dilbert is now level 0 instead of 7 (due to penalty) + assertEquals(0, (int)dilbertAccount.getLevel()); + + // Orphan last block + BlockUtils.orphanLastBlock(repository); + + // Make sure Dilbert has returned to level 6 + assertEquals(6, (int)dilbertAccount.getLevel()); + + // Run orphan check - this can't be in afterTest() because some tests access the live db + Common.orphanCheck(); + } + } + + @Test + public void testDuplicateSponsors() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + final int initialRewardShareCount = repository.getAccountRepository().getRewardShares().size(); + + // Alice self share online, and will be used to mint the blocks + PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share"); + List onlineAccounts = new ArrayList<>(); + onlineAccounts.add(aliceSelfShare); + + PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob"); + PrivateKeyAccount chloeAccount = Common.getTestAccount(repository, "chloe"); + PrivateKeyAccount dilbertAccount = Common.getTestAccount(repository, "dilbert"); + + // Bob sponsors 10 accounts + List bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 10); + List bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees); + onlineAccounts.addAll(bobSponseesOnlineAccounts); + + // Chloe sponsors THE SAME 10 accounts + for (PrivateKeyAccount bobSponsee : bobSponsees) { + // Create reward-share + TransactionData transactionData = AccountUtils.createRewardShare(repository, chloeAccount, bobSponsee, 0, fee); + TransactionUtils.signAndImportValid(repository, transactionData, chloeAccount); + } + List chloeSponsees = new ArrayList<>(bobSponsees); + List chloeSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, chloeAccount, chloeSponsees); + onlineAccounts.addAll(chloeSponseesOnlineAccounts); + + // Dilbert sponsors 5 accounts + List dilbertSponsees = AccountUtils.generateSponsorshipRewardShares(repository, dilbertAccount, 5); + List dilbertSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, dilbertAccount, dilbertSponsees); + onlineAccounts.addAll(dilbertSponseesOnlineAccounts); + + // Mint blocks + Block block = null; + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + assertEquals(26, block.getBlockData().getOnlineAccountsCount()); + assertEquals(25, repository.getAccountRepository().getRewardShares().size() - initialRewardShareCount); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() == 0); + + // Mint some blocks, until accounts have leveled up + for (int i = 0; i <= 5; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0); + + // Generate self shares so the sponsees can start minting + List bobSponseeSelfShares = AccountUtils.generateSelfShares(repository, bobSponsees); + onlineAccounts.addAll(bobSponseeSelfShares); + + // Mint blocks + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getConfirmedBalance(Asset.QORT) > 10); // 5 for transaction, 5 for fee + + // Bob then consolidates funds + consolidateFunds(repository, bobSponsees, bobAccount); + + // Mint until block 29 (the algo runs at block 30) + while (block.getBlockData().getHeight() < 29) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + assertEquals(29, (int) block.getBlockData().getHeight()); + + // Ensure that bob and his sponsees have no penalties + List bobAndSponsees = new ArrayList<>(bobSponsees); + bobAndSponsees.add(bobAccount); + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + assertEquals(6, (int)dilbertAccount.getLevel()); + + // Mint a block, so the algo runs + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Ensure that bob and his sponsees now have penalties + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(-5000000, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that chloe and her sponsees also have penalties, as they relate to the same network of accounts + List chloeAndSponsees = new ArrayList<>(chloeSponsees); + chloeAndSponsees.add(chloeAccount); + for (PrivateKeyAccount chloeSponsee : chloeAndSponsees) + assertEquals(-5000000, (int) new Account(repository, chloeSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that dilbert and his sponsees have no penalties + List dilbertAndSponsees = new ArrayList<>(dilbertSponsees); + dilbertAndSponsees.add(dilbertAccount); + for (PrivateKeyAccount dilbertSponsee : dilbertAndSponsees) + assertEquals(0, (int) new Account(repository, dilbertSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that bob and his sponsees are now level 0 + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getLevel()); + + // Orphan last block + BlockUtils.orphanLastBlock(repository); + + // Ensure that bob and his sponsees are now greater than level 0 + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0); + + // Ensure that bob and his sponsees have no penalties again + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that chloe and her sponsees still have no penalties again + for (PrivateKeyAccount chloeSponsee : chloeAndSponsees) + assertEquals(0, (int) new Account(repository, chloeSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that dilbert and his sponsees still have no penalties + for (PrivateKeyAccount dilbertSponsee : dilbertAndSponsees) + assertEquals(0, (int) new Account(repository, dilbertSponsee.getAddress()).getBlocksMintedPenalty()); + + // Run orphan check - this can't be in afterTest() because some tests access the live db + Common.orphanCheck(); + } + } + + @Test + public void testTransferPrivsBeforeAlgoBlock() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + + // Alice self share online, and will be used to mint the blocks + PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share"); + List onlineAccounts = new ArrayList<>(); + onlineAccounts.add(aliceSelfShare); + + PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob"); + + // Bob sponsors 10 accounts + List bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 10); + List bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees); + onlineAccounts.addAll(bobSponseesOnlineAccounts); + + // Mint blocks + Block block = null; + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() == 0); + + // Mint some blocks, until accounts have leveled up + for (int i = 0; i <= 5; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0); + + // Generate self shares so the sponsees can start minting + List bobSponseeSelfShares = AccountUtils.generateSelfShares(repository, bobSponsees); + onlineAccounts.addAll(bobSponseeSelfShares); + + // Mint blocks + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getConfirmedBalance(Asset.QORT) > 10); // 5 for transaction, 5 for fee + + // Bob then consolidates funds + consolidateFunds(repository, bobSponsees, bobAccount); + + // Mint until block 28 (the algo runs at block 30) + while (block.getBlockData().getHeight() < 28) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + assertEquals(28, (int) block.getBlockData().getHeight()); + + // Bob then issues a TRANSFER_PRIVS + PrivateKeyAccount recipientAccount = randomTransferPrivs(repository, bobAccount); + + // Ensure recipient has no level (actually, no account record) at this point (pre-confirmation) + assertNull(recipientAccount.getLevel()); + + // Mint another block, so that the TRANSFER_PRIVS confirms + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Now ensure that the TRANSFER_PRIVS recipient has inherited Bob's level, and Bob is at level 0 + assertTrue(recipientAccount.getLevel() > 0); + assertEquals(0, (int)bobAccount.getLevel()); + assertEquals(0, (int) new Account(repository, recipientAccount.getAddress()).getBlocksMintedPenalty()); + + // Ensure that bob and his sponsees have no penalties + List bobAndSponsees = new ArrayList<>(bobSponsees); + bobAndSponsees.add(bobAccount); + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that bob's sponsees are greater than level 0 + // Bob's account won't be, as he has transferred privs + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0); + + // Mint a block, so the algo runs + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Ensure that bob and his sponsees now have penalties + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(-5000000, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that bob and his sponsees are now level 0 + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getLevel()); + + // Ensure recipient account has penalty too + assertEquals(-5000000, (int) new Account(repository, recipientAccount.getAddress()).getBlocksMintedPenalty()); + assertEquals(0, (int) new Account(repository, recipientAccount.getAddress()).getLevel()); + + // Orphan last block + BlockUtils.orphanLastBlock(repository); + + // Ensure that Bob's sponsees are now greater than level 0 + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0); + + // Ensure that bob and his sponsees have no penalties again + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure recipient account has no penalty again and has a level greater than 0 + assertEquals(0, (int) new Account(repository, recipientAccount.getAddress()).getBlocksMintedPenalty()); + assertTrue(new Account(repository, recipientAccount.getAddress()).getLevel() > 0); + + // Run orphan check - this can't be in afterTest() because some tests access the live db + Common.orphanCheck(); + } + } + + @Test + public void testTransferPrivsInAlgoBlock() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + + // Alice self share online, and will be used to mint the blocks + PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share"); + List onlineAccounts = new ArrayList<>(); + onlineAccounts.add(aliceSelfShare); + + PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob"); + + // Bob sponsors 10 accounts + List bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 10); + List bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees); + onlineAccounts.addAll(bobSponseesOnlineAccounts); + + // Mint blocks + Block block = null; + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() == 0); + + // Mint some blocks, until accounts have leveled up + for (int i = 0; i <= 5; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0); + + // Generate self shares so the sponsees can start minting + List bobSponseeSelfShares = AccountUtils.generateSelfShares(repository, bobSponsees); + onlineAccounts.addAll(bobSponseeSelfShares); + + // Mint blocks + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getConfirmedBalance(Asset.QORT) > 10); // 5 for transaction, 5 for fee + + // Bob then consolidates funds + consolidateFunds(repository, bobSponsees, bobAccount); + + // Mint until block 29 (the algo runs at block 30) + while (block.getBlockData().getHeight() < 29) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + assertEquals(29, (int) block.getBlockData().getHeight()); + + // Ensure that bob and his sponsees have no penalties + List bobAndSponsees = new ArrayList<>(bobSponsees); + bobAndSponsees.add(bobAccount); + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Bob then issues a TRANSFER_PRIVS + PrivateKeyAccount recipientAccount = randomTransferPrivs(repository, bobAccount); + + // Ensure recipient has no level (actually, no account record) at this point (pre-confirmation) + assertNull(recipientAccount.getLevel()); + + // Mint a block, so the algo runs + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Now ensure that the TRANSFER_PRIVS recipient has inherited Bob's level, and Bob is at level 0 + assertTrue(recipientAccount.getLevel() > 0); + assertEquals(0, (int)bobAccount.getLevel()); + + // Ensure that bob and his sponsees now have penalties + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(-5000000, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that bob and his sponsees are now level 0 + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getLevel()); + + // Orphan last block + BlockUtils.orphanLastBlock(repository); + + // Ensure recipient has no level again + assertNull(recipientAccount.getLevel()); + + // Ensure that bob and his sponsees are now greater than level 0 + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0); + + // Ensure that bob and his sponsees have no penalties again + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Run orphan check - this can't be in afterTest() because some tests access the live db + Common.orphanCheck(); + } + } + + @Test + public void testTransferPrivsAfterAlgoBlock() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + + // Alice self share online, and will be used to mint the blocks + PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share"); + List onlineAccounts = new ArrayList<>(); + onlineAccounts.add(aliceSelfShare); + + PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob"); + + // Bob sponsors 10 accounts + List bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 10); + List bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees); + onlineAccounts.addAll(bobSponseesOnlineAccounts); + + // Mint blocks + Block block = null; + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() == 0); + + // Mint some blocks, until accounts have leveled up + for (int i = 0; i <= 5; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0); + + // Generate self shares so the sponsees can start minting + List bobSponseeSelfShares = AccountUtils.generateSelfShares(repository, bobSponsees); + onlineAccounts.addAll(bobSponseeSelfShares); + + // Mint blocks + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getConfirmedBalance(Asset.QORT) > 10); // 5 for transaction, 5 for fee + + // Bob then consolidates funds + consolidateFunds(repository, bobSponsees, bobAccount); + + // Mint until block 29 (the algo runs at block 30) + while (block.getBlockData().getHeight() < 29) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + assertEquals(29, (int) block.getBlockData().getHeight()); + + // Ensure that bob and his sponsees have no penalties + List bobAndSponsees = new ArrayList<>(bobSponsees); + bobAndSponsees.add(bobAccount); + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Mint a block, so the algo runs + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Bob then issues a TRANSFER_PRIVS, which should be invalid + Transaction transferPrivsTransaction = randomTransferPrivsTransaction(repository, bobAccount); + assertEquals(ACCOUNT_NOT_TRANSFERABLE, transferPrivsTransaction.isValid()); + + // Orphan last 2 blocks + BlockUtils.orphanLastBlock(repository); + BlockUtils.orphanLastBlock(repository); + + // TRANSFER_PRIVS should now be valid + transferPrivsTransaction = randomTransferPrivsTransaction(repository, bobAccount); + assertEquals(OK, transferPrivsTransaction.isValid()); + + // Run orphan check - this can't be in afterTest() because some tests access the live db + Common.orphanCheck(); + } + } + + @Test + public void testDoubleTransferPrivs() throws DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + + // Alice self share online, and will be used to mint the blocks + PrivateKeyAccount aliceSelfShare = Common.getTestAccount(repository, "alice-reward-share"); + List onlineAccounts = new ArrayList<>(); + onlineAccounts.add(aliceSelfShare); + + PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob"); + + // Bob sponsors 10 accounts + List bobSponsees = AccountUtils.generateSponsorshipRewardShares(repository, bobAccount, 10); + List bobSponseesOnlineAccounts = AccountUtils.toRewardShares(repository, bobAccount, bobSponsees); + onlineAccounts.addAll(bobSponseesOnlineAccounts); + + // Mint blocks + Block block = null; + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() == 0); + + // Mint some blocks, until accounts have leveled up + for (int i = 0; i <= 5; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0); + + // Generate self shares so the sponsees can start minting + List bobSponseeSelfShares = AccountUtils.generateSelfShares(repository, bobSponsees); + onlineAccounts.addAll(bobSponseeSelfShares); + + // Mint blocks + for (int i = 0; i <= 1; i++) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getConfirmedBalance(Asset.QORT) > 10); // 5 for transaction, 5 for fee + + // Bob then consolidates funds + consolidateFunds(repository, bobSponsees, bobAccount); + + // Mint until block 27 (the algo runs at block 30) + while (block.getBlockData().getHeight() < 27) + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + assertEquals(27, (int) block.getBlockData().getHeight()); + + // Bob then issues a TRANSFER_PRIVS + PrivateKeyAccount recipientAccount1 = randomTransferPrivs(repository, bobAccount); + + // Ensure recipient has no level (actually, no account record) at this point (pre-confirmation) + assertNull(recipientAccount1.getLevel()); + + // Bob and also sends some QORT to cover future transaction fees + // This mints another block, and the TRANSFER_PRIVS confirms + AccountUtils.pay(repository, bobAccount, recipientAccount1.getAddress(), 123456789L); + + // Now ensure that the TRANSFER_PRIVS recipient has inherited Bob's level, and Bob is at level 0 + assertTrue(recipientAccount1.getLevel() > 0); + assertEquals(0, (int)bobAccount.getLevel()); + assertEquals(0, (int) new Account(repository, recipientAccount1.getAddress()).getBlocksMintedPenalty()); + + // The recipient account then issues a TRANSFER_PRIVS of their own + PrivateKeyAccount recipientAccount2 = randomTransferPrivs(repository, recipientAccount1); + + // Ensure recipientAccount2 has no level at this point (pre-confirmation) + assertNull(recipientAccount2.getLevel()); + + // Mint another block, so that the TRANSFER_PRIVS confirms + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Now ensure that the TRANSFER_PRIVS recipient2 has inherited Bob's level, and recipient1 is at level 0 + assertTrue(recipientAccount2.getLevel() > 0); + assertEquals(0, (int)recipientAccount1.getLevel()); + assertEquals(0, (int) new Account(repository, recipientAccount2.getAddress()).getBlocksMintedPenalty()); + + // Ensure that bob and his sponsees have no penalties + List bobAndSponsees = new ArrayList<>(bobSponsees); + bobAndSponsees.add(bobAccount); + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that bob's sponsees are greater than level 0 + // Bob's account won't be, as he has transferred privs + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0); + + // Mint a block, so the algo runs + block = BlockMinter.mintTestingBlock(repository, onlineAccounts.toArray(new PrivateKeyAccount[0])); + + // Ensure that bob and his sponsees now have penalties + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(-5000000, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure that bob and his sponsees are now level 0 + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getLevel()); + + // Ensure recipientAccount2 has penalty too + assertEquals(-5000000, (int) new Account(repository, recipientAccount2.getAddress()).getBlocksMintedPenalty()); + assertEquals(0, (int) new Account(repository, recipientAccount2.getAddress()).getLevel()); + + // Ensure recipientAccount1 has penalty too + assertEquals(-5000000, (int) new Account(repository, recipientAccount1.getAddress()).getBlocksMintedPenalty()); + assertEquals(0, (int) new Account(repository, recipientAccount1.getAddress()).getLevel()); + + // Orphan last block + BlockUtils.orphanLastBlock(repository); + + // Ensure that Bob's sponsees are now greater than level 0 + for (PrivateKeyAccount bobSponsee : bobSponsees) + assertTrue(new Account(repository, bobSponsee.getAddress()).getLevel() > 0); + + // Ensure that bob and his sponsees have no penalties again + for (PrivateKeyAccount bobSponsee : bobAndSponsees) + assertEquals(0, (int) new Account(repository, bobSponsee.getAddress()).getBlocksMintedPenalty()); + + // Ensure recipientAccount1 has no penalty again and is level 0 + assertEquals(0, (int) new Account(repository, recipientAccount1.getAddress()).getBlocksMintedPenalty()); + assertEquals(0, (int) new Account(repository, recipientAccount1.getAddress()).getLevel()); + + // Ensure recipientAccount2 has no penalty again and has a level greater than 0 + assertEquals(0, (int) new Account(repository, recipientAccount2.getAddress()).getBlocksMintedPenalty()); + assertTrue(new Account(repository, recipientAccount2.getAddress()).getLevel() > 0); + + // Run orphan check - this can't be in afterTest() because some tests access the live db + Common.orphanCheck(); + } + } + + + + private static PrivateKeyAccount randomTransferPrivs(Repository repository, PrivateKeyAccount senderAccount) throws DataException { + // Generate random recipient account + byte[] randomPrivateKey = new byte[32]; + new Random().nextBytes(randomPrivateKey); + PrivateKeyAccount recipientAccount = new PrivateKeyAccount(repository, randomPrivateKey); + + BaseTransactionData baseTransactionData = new BaseTransactionData(NTP.getTime(), 0, senderAccount.getLastReference(), senderAccount.getPublicKey(), fee, null); + TransactionData transactionData = new TransferPrivsTransactionData(baseTransactionData, recipientAccount.getAddress()); + + TransactionUtils.signAndImportValid(repository, transactionData, senderAccount); + + return recipientAccount; + } + + private static TransferPrivsTransaction randomTransferPrivsTransaction(Repository repository, PrivateKeyAccount senderAccount) throws DataException { + // Generate random recipient account + byte[] randomPrivateKey = new byte[32]; + new Random().nextBytes(randomPrivateKey); + PrivateKeyAccount recipientAccount = new PrivateKeyAccount(repository, randomPrivateKey); + + BaseTransactionData baseTransactionData = new BaseTransactionData(NTP.getTime(), 0, senderAccount.getLastReference(), senderAccount.getPublicKey(), fee, null); + TransactionData transactionData = new TransferPrivsTransactionData(baseTransactionData, recipientAccount.getAddress()); + + return new TransferPrivsTransaction(repository, transactionData); + } + + private boolean areAllAccountsPresentInBlock(List accounts, Block block) throws DataException { + for (PrivateKeyAccount bobSponsee : accounts) { + boolean foundOnlineAccountInBlock = false; + for (Block.ExpandedAccount expandedAccount : block.getExpandedAccounts()) { + if (expandedAccount.getRecipientAccount().getAddress().equals(bobSponsee.getAddress())) { + foundOnlineAccountInBlock = true; + break; + } + } + if (!foundOnlineAccountInBlock) { + return false; + } + } + return true; + } + + private static void consolidateFunds(Repository repository, List sponsees, PrivateKeyAccount sponsor) throws DataException { + for (PrivateKeyAccount sponsee : sponsees) { + for (int i = 0; i < 5; i++) { + // Generate new payments from sponsee to sponsor + TransactionData paymentData = new PaymentTransactionData(TestTransaction.generateBase(sponsee), sponsor.getAddress(), 1); + TransactionUtils.signAndImportValid(repository, paymentData, sponsee); // updates paymentData's signature + } + } + } + +} \ No newline at end of file diff --git a/src/test/resources/test-chain-v2-block-timestamps.json b/src/test/resources/test-chain-v2-block-timestamps.json index 8f863ada..17fc80c4 100644 --- a/src/test/resources/test-chain-v2-block-timestamps.json +++ b/src/test/resources/test-chain-v2-block-timestamps.json @@ -19,6 +19,8 @@ "onlineAccountSignaturesMaxLifetime": 86400000, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, "selfSponsorshipAlgoV2SnapshotTimestamp": 9999999999999, + "selfSponsorshipAlgoV3SnapshotTimestamp": 9999999999999, + "referenceTimestampBlock": 9999999999999, "mempowTransactionUpdatesTimestamp": 0, "blockRewardBatchStartHeight": 999999000, "blockRewardBatchSize": 10, @@ -82,14 +84,14 @@ "increaseOnlineAccountsDifficultyTimestamp": 9999999999999, "onlineAccountMinterLevelValidationHeight": 0, "selfSponsorshipAlgoV1Height": 999999999, + "selfSponsorshipAlgoV2Height": 999999999, + "selfSponsorshipAlgoV3Height": 999999999, "feeValidationFixTimestamp": 0, "chatReferenceTimestamp": 0, "arbitraryOptionalFeeTimestamp": 0, "unconfirmableRewardSharesHeight": 99999999, - "selfSponsorshipAlgoV2Height": 9999999, "disableTransferPrivsTimestamp": 9999999999500, - "enableTransferPrivsTimestamp": 9999999999950, - "penaltyFixHeight": 9999999 + "enableTransferPrivsTimestamp": 9999999999950 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-disable-reference.json b/src/test/resources/test-chain-v2-disable-reference.json index f5938c01..33054732 100644 --- a/src/test/resources/test-chain-v2-disable-reference.json +++ b/src/test/resources/test-chain-v2-disable-reference.json @@ -23,6 +23,8 @@ "onlineAccountSignaturesMaxLifetime": 86400000, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, "selfSponsorshipAlgoV2SnapshotTimestamp": 9999999999999, + "selfSponsorshipAlgoV3SnapshotTimestamp": 9999999999999, + "referenceTimestampBlock": 9999999999999, "mempowTransactionUpdatesTimestamp": 0, "blockRewardBatchStartHeight": 999999000, "blockRewardBatchSize": 10, @@ -85,14 +87,14 @@ "increaseOnlineAccountsDifficultyTimestamp": 9999999999999, "onlineAccountMinterLevelValidationHeight": 0, "selfSponsorshipAlgoV1Height": 999999999, + "selfSponsorshipAlgoV2Height": 999999999, + "selfSponsorshipAlgoV3Height": 999999999, "feeValidationFixTimestamp": 0, "chatReferenceTimestamp": 0, "arbitraryOptionalFeeTimestamp": 0, "unconfirmableRewardSharesHeight": 99999999, - "selfSponsorshipAlgoV2Height": 9999999, "disableTransferPrivsTimestamp": 9999999999500, - "enableTransferPrivsTimestamp": 9999999999950, - "penaltyFixHeight": 9999999 + "enableTransferPrivsTimestamp": 9999999999950 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-founder-rewards.json b/src/test/resources/test-chain-v2-founder-rewards.json index 9fda74f5..577a07f1 100644 --- a/src/test/resources/test-chain-v2-founder-rewards.json +++ b/src/test/resources/test-chain-v2-founder-rewards.json @@ -24,6 +24,8 @@ "onlineAccountsModulusV2Timestamp": 9999999999999, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, "selfSponsorshipAlgoV2SnapshotTimestamp": 9999999999999, + "selfSponsorshipAlgoV3SnapshotTimestamp": 9999999999999, + "referenceTimestampBlock": 9999999999999, "mempowTransactionUpdatesTimestamp": 0, "blockRewardBatchStartHeight": 999999000, "blockRewardBatchSize": 10, @@ -86,14 +88,14 @@ "increaseOnlineAccountsDifficultyTimestamp": 9999999999999, "onlineAccountMinterLevelValidationHeight": 0, "selfSponsorshipAlgoV1Height": 999999999, + "selfSponsorshipAlgoV2Height": 999999999, + "selfSponsorshipAlgoV3Height": 999999999, "feeValidationFixTimestamp": 0, "chatReferenceTimestamp": 0, "arbitraryOptionalFeeTimestamp": 0, "unconfirmableRewardSharesHeight": 99999999, - "selfSponsorshipAlgoV2Height": 9999999, "disableTransferPrivsTimestamp": 9999999999500, - "enableTransferPrivsTimestamp": 9999999999950, - "penaltyFixHeight": 9999999 + "enableTransferPrivsTimestamp": 9999999999950 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-leftover-reward.json b/src/test/resources/test-chain-v2-leftover-reward.json index 20a85b8a..82e4ace7 100644 --- a/src/test/resources/test-chain-v2-leftover-reward.json +++ b/src/test/resources/test-chain-v2-leftover-reward.json @@ -24,6 +24,8 @@ "onlineAccountsModulusV2Timestamp": 9999999999999, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, "selfSponsorshipAlgoV2SnapshotTimestamp": 9999999999999, + "selfSponsorshipAlgoV3SnapshotTimestamp": 9999999999999, + "referenceTimestampBlock": 9999999999999, "mempowTransactionUpdatesTimestamp": 0, "blockRewardBatchStartHeight": 999999000, "blockRewardBatchSize": 10, @@ -86,14 +88,14 @@ "increaseOnlineAccountsDifficultyTimestamp": 9999999999999, "onlineAccountMinterLevelValidationHeight": 0, "selfSponsorshipAlgoV1Height": 999999999, + "selfSponsorshipAlgoV2Height": 999999999, + "selfSponsorshipAlgoV3Height": 999999999, "feeValidationFixTimestamp": 0, "chatReferenceTimestamp": 0, "arbitraryOptionalFeeTimestamp": 0, "unconfirmableRewardSharesHeight": 99999999, - "selfSponsorshipAlgoV2Height": 9999999, "disableTransferPrivsTimestamp": 9999999999500, - "enableTransferPrivsTimestamp": 9999999999950, - "penaltyFixHeight": 9999999 + "enableTransferPrivsTimestamp": 9999999999950 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-minting.json b/src/test/resources/test-chain-v2-minting.json index 7c28435a..16032a9c 100644 --- a/src/test/resources/test-chain-v2-minting.json +++ b/src/test/resources/test-chain-v2-minting.json @@ -24,6 +24,8 @@ "onlineAccountsModulusV2Timestamp": 9999999999999, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, "selfSponsorshipAlgoV2SnapshotTimestamp": 9999999999999, + "selfSponsorshipAlgoV3SnapshotTimestamp": 9999999999999, + "referenceTimestampBlock": 9999999999999, "mempowTransactionUpdatesTimestamp": 9999999999999, "blockRewardBatchStartHeight": 999999000, "blockRewardBatchSize": 10, @@ -86,14 +88,14 @@ "increaseOnlineAccountsDifficultyTimestamp": 9999999999999, "onlineAccountMinterLevelValidationHeight": 0, "selfSponsorshipAlgoV1Height": 999999999, + "selfSponsorshipAlgoV2Height": 999999999, + "selfSponsorshipAlgoV3Height": 999999999, "feeValidationFixTimestamp": 0, "chatReferenceTimestamp": 0, "arbitraryOptionalFeeTimestamp": 9999999999999, "unconfirmableRewardSharesHeight": 99999999, - "selfSponsorshipAlgoV2Height": 9999999, "disableTransferPrivsTimestamp": 9999999999500, - "enableTransferPrivsTimestamp": 9999999999950, - "penaltyFixHeight": 9999999 + "enableTransferPrivsTimestamp": 9999999999950 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-qora-holder-extremes.json b/src/test/resources/test-chain-v2-qora-holder-extremes.json index 0372ba4f..3ec11942 100644 --- a/src/test/resources/test-chain-v2-qora-holder-extremes.json +++ b/src/test/resources/test-chain-v2-qora-holder-extremes.json @@ -24,6 +24,8 @@ "onlineAccountsModulusV2Timestamp": 9999999999999, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, "selfSponsorshipAlgoV2SnapshotTimestamp": 9999999999999, + "selfSponsorshipAlgoV3SnapshotTimestamp": 9999999999999, + "referenceTimestampBlock": 9999999999999, "mempowTransactionUpdatesTimestamp": 0, "blockRewardBatchStartHeight": 999999000, "blockRewardBatchSize": 10, @@ -86,14 +88,14 @@ "increaseOnlineAccountsDifficultyTimestamp": 9999999999999, "onlineAccountMinterLevelValidationHeight": 0, "selfSponsorshipAlgoV1Height": 999999999, + "selfSponsorshipAlgoV2Height": 999999999, + "selfSponsorshipAlgoV3Height": 999999999, "feeValidationFixTimestamp": 0, "chatReferenceTimestamp": 0, "arbitraryOptionalFeeTimestamp": 0, "unconfirmableRewardSharesHeight": 99999999, - "selfSponsorshipAlgoV2Height": 9999999, "disableTransferPrivsTimestamp": 9999999999500, - "enableTransferPrivsTimestamp": 9999999999950, - "penaltyFixHeight": 9999999 + "enableTransferPrivsTimestamp": 9999999999950 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-qora-holder-reduction.json b/src/test/resources/test-chain-v2-qora-holder-reduction.json index 9fbeb317..2b8834ce 100644 --- a/src/test/resources/test-chain-v2-qora-holder-reduction.json +++ b/src/test/resources/test-chain-v2-qora-holder-reduction.json @@ -24,6 +24,8 @@ "onlineAccountsModulusV2Timestamp": 9999999999999, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, "selfSponsorshipAlgoV2SnapshotTimestamp": 9999999999999, + "selfSponsorshipAlgoV3SnapshotTimestamp": 9999999999999, + "referenceTimestampBlock": 9999999999999, "mempowTransactionUpdatesTimestamp": 0, "blockRewardBatchStartHeight": 999999000, "blockRewardBatchSize": 10, @@ -87,14 +89,14 @@ "increaseOnlineAccountsDifficultyTimestamp": 9999999999999, "onlineAccountMinterLevelValidationHeight": 0, "selfSponsorshipAlgoV1Height": 999999999, + "selfSponsorshipAlgoV2Height": 999999999, + "selfSponsorshipAlgoV3Height": 999999999, "feeValidationFixTimestamp": 0, "chatReferenceTimestamp": 0, "arbitraryOptionalFeeTimestamp": 0, "unconfirmableRewardSharesHeight": 99999999, - "selfSponsorshipAlgoV2Height": 9999999, "disableTransferPrivsTimestamp": 9999999999500, - "enableTransferPrivsTimestamp": 9999999999950, - "penaltyFixHeight": 9999999 + "enableTransferPrivsTimestamp": 9999999999950 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-qora-holder.json b/src/test/resources/test-chain-v2-qora-holder.json index e9a2b485..ab96a243 100644 --- a/src/test/resources/test-chain-v2-qora-holder.json +++ b/src/test/resources/test-chain-v2-qora-holder.json @@ -24,6 +24,8 @@ "onlineAccountsModulusV2Timestamp": 9999999999999, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, "selfSponsorshipAlgoV2SnapshotTimestamp": 9999999999999, + "selfSponsorshipAlgoV3SnapshotTimestamp": 9999999999999, + "referenceTimestampBlock": 9999999999999, "mempowTransactionUpdatesTimestamp": 0, "blockRewardBatchStartHeight": 999999000, "blockRewardBatchSize": 10, @@ -86,14 +88,14 @@ "increaseOnlineAccountsDifficultyTimestamp": 9999999999999, "onlineAccountMinterLevelValidationHeight": 0, "selfSponsorshipAlgoV1Height": 999999999, + "selfSponsorshipAlgoV2Height": 999999999, + "selfSponsorshipAlgoV3Height": 999999999, "feeValidationFixTimestamp": 0, "chatReferenceTimestamp": 0, "arbitraryOptionalFeeTimestamp": 0, "unconfirmableRewardSharesHeight": 99999999, - "selfSponsorshipAlgoV2Height": 9999999, "disableTransferPrivsTimestamp": 9999999999500, - "enableTransferPrivsTimestamp": 9999999999950, - "penaltyFixHeight": 9999999 + "enableTransferPrivsTimestamp": 9999999999950 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-reward-levels.json b/src/test/resources/test-chain-v2-reward-levels.json index b4993aee..35535c75 100644 --- a/src/test/resources/test-chain-v2-reward-levels.json +++ b/src/test/resources/test-chain-v2-reward-levels.json @@ -24,6 +24,8 @@ "onlineAccountsModulusV2Timestamp": 9999999999999, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, "selfSponsorshipAlgoV2SnapshotTimestamp": 9999999999999, + "selfSponsorshipAlgoV3SnapshotTimestamp": 9999999999999, + "referenceTimestampBlock": 9999999999999, "mempowTransactionUpdatesTimestamp": 0, "blockRewardBatchStartHeight": 999999000, "blockRewardBatchSize": 10, @@ -86,14 +88,14 @@ "increaseOnlineAccountsDifficultyTimestamp": 9999999999999, "onlineAccountMinterLevelValidationHeight": 0, "selfSponsorshipAlgoV1Height": 999999999, + "selfSponsorshipAlgoV2Height": 999999999, + "selfSponsorshipAlgoV3Height": 999999999, "feeValidationFixTimestamp": 0, "chatReferenceTimestamp": 0, "arbitraryOptionalFeeTimestamp": 0, "unconfirmableRewardSharesHeight": 99999999, - "selfSponsorshipAlgoV2Height": 9999999, "disableTransferPrivsTimestamp": 9999999999500, - "enableTransferPrivsTimestamp": 9999999999950, - "penaltyFixHeight": 9999999 + "enableTransferPrivsTimestamp": 9999999999950 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-reward-scaling.json b/src/test/resources/test-chain-v2-reward-scaling.json index 4c3595bf..616d0925 100644 --- a/src/test/resources/test-chain-v2-reward-scaling.json +++ b/src/test/resources/test-chain-v2-reward-scaling.json @@ -24,6 +24,8 @@ "onlineAccountsModulusV2Timestamp": 9999999999999, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, "selfSponsorshipAlgoV2SnapshotTimestamp": 9999999999999, + "selfSponsorshipAlgoV3SnapshotTimestamp": 9999999999999, + "referenceTimestampBlock": 9999999999999, "mempowTransactionUpdatesTimestamp": 0, "blockRewardBatchStartHeight": 999999000, "blockRewardBatchSize": 10, @@ -86,14 +88,14 @@ "increaseOnlineAccountsDifficultyTimestamp": 9999999999999, "onlineAccountMinterLevelValidationHeight": 0, "selfSponsorshipAlgoV1Height": 999999999, + "selfSponsorshipAlgoV2Height": 999999999, + "selfSponsorshipAlgoV3Height": 999999999, "feeValidationFixTimestamp": 0, "chatReferenceTimestamp": 0, "arbitraryOptionalFeeTimestamp": 0, "unconfirmableRewardSharesHeight": 500, - "selfSponsorshipAlgoV2Height": 9999999, "disableTransferPrivsTimestamp": 9999999999500, - "enableTransferPrivsTimestamp": 9999999999950, - "penaltyFixHeight": 9999999 + "enableTransferPrivsTimestamp": 9999999999950 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-reward-shares.json b/src/test/resources/test-chain-v2-reward-shares.json index 243a3ead..ec6ffd2e 100644 --- a/src/test/resources/test-chain-v2-reward-shares.json +++ b/src/test/resources/test-chain-v2-reward-shares.json @@ -23,6 +23,8 @@ "onlineAccountSignaturesMaxLifetime": 86400000, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, "selfSponsorshipAlgoV2SnapshotTimestamp": 9999999999999, + "selfSponsorshipAlgoV3SnapshotTimestamp": 9999999999999, + "referenceTimestampBlock": 9999999999999, "mempowTransactionUpdatesTimestamp": 0, "blockRewardBatchStartHeight": 999999000, "blockRewardBatchSize": 10, @@ -86,14 +88,14 @@ "increaseOnlineAccountsDifficultyTimestamp": 9999999999999, "onlineAccountMinterLevelValidationHeight": 0, "selfSponsorshipAlgoV1Height": 999999999, + "selfSponsorshipAlgoV2Height": 999999999, + "selfSponsorshipAlgoV3Height": 999999999, "feeValidationFixTimestamp": 0, "chatReferenceTimestamp": 0, "arbitraryOptionalFeeTimestamp": 0, "unconfirmableRewardSharesHeight": 99999999, - "selfSponsorshipAlgoV2Height": 9999999, "disableTransferPrivsTimestamp": 9999999999500, - "enableTransferPrivsTimestamp": 9999999999950, - "penaltyFixHeight": 9999999 + "enableTransferPrivsTimestamp": 9999999999950 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-self-sponsorship-algo-v1.json b/src/test/resources/test-chain-v2-self-sponsorship-algo-v1.json index caf8ffe7..d0d989cf 100644 --- a/src/test/resources/test-chain-v2-self-sponsorship-algo-v1.json +++ b/src/test/resources/test-chain-v2-self-sponsorship-algo-v1.json @@ -24,6 +24,8 @@ "onlineAccountsModulusV2Timestamp": 9999999999999, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, "selfSponsorshipAlgoV2SnapshotTimestamp": 9999999999999, + "selfSponsorshipAlgoV3SnapshotTimestamp": 9999999999999, + "referenceTimestampBlock": 9999999999999, "mempowTransactionUpdatesTimestamp": 0, "blockRewardBatchStartHeight": 999999000, "blockRewardBatchSize": 10, @@ -86,14 +88,14 @@ "increaseOnlineAccountsDifficultyTimestamp": 9999999999999, "onlineAccountMinterLevelValidationHeight": 0, "selfSponsorshipAlgoV1Height": 20, + "selfSponsorshipAlgoV2Height": 999999999, + "selfSponsorshipAlgoV3Height": 999999999, "feeValidationFixTimestamp": 0, "chatReferenceTimestamp": 0, "arbitraryOptionalFeeTimestamp": 0, "unconfirmableRewardSharesHeight": 99999999, - "selfSponsorshipAlgoV2Height": 9999999, "disableTransferPrivsTimestamp": 9999999999500, - "enableTransferPrivsTimestamp": 9999999999950, - "penaltyFixHeight": 9999999 + "enableTransferPrivsTimestamp": 9999999999950 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-chain-v2-self-sponsorship-algo-v2.json b/src/test/resources/test-chain-v2-self-sponsorship-algo-v2.json new file mode 100644 index 00000000..5f09cb47 --- /dev/null +++ b/src/test/resources/test-chain-v2-self-sponsorship-algo-v2.json @@ -0,0 +1,132 @@ +{ + "isTestChain": true, + "blockTimestampMargin": 500, + "transactionExpiryPeriod": 86400000, + "maxBlockSize": 2097152, + "maxBytesPerUnitFee": 0, + "unitFees": [ + { "timestamp": 0, "fee": "0.00000001" } + ], + "nameRegistrationUnitFees": [ + { "timestamp": 0, "fee": "0.00000001" }, + { "timestamp": 1645372800000, "fee": "5" } + ], + "requireGroupForApproval": false, + "minAccountLevelToRewardShare": 5, + "maxRewardSharesPerFounderMintingAccount": 20, + "maxRewardSharesByTimestamp": [ + { "timestamp": 0, "maxShares": 20 }, + { "timestamp": 9999999999999, "maxShares": 3 } + ], + "founderEffectiveMintingLevel": 10, + "onlineAccountSignaturesMinLifetime": 3600000, + "onlineAccountSignaturesMaxLifetime": 86400000, + "onlineAccountsModulusV2Timestamp": 9999999999999, + "selfSponsorshipAlgoV1SnapshotTimestamp": 0, + "selfSponsorshipAlgoV2SnapshotTimestamp": 9999999999999, + "selfSponsorshipAlgoV3SnapshotTimestamp": 9999999999999, + "referenceTimestampBlock": 9999999999999, + "mempowTransactionUpdatesTimestamp": 0, + "blockRewardBatchStartHeight": 999999000, + "blockRewardBatchSize": 10, + "blockRewardBatchAccountsBlockCount": 3, + "rewardsByHeight": [ + { "height": 1, "reward": 100 }, + { "height": 11, "reward": 10 }, + { "height": 21, "reward": 1 } + ], + "sharesByLevelV1": [ + { "id": 1, "levels": [ 1, 2 ], "share": 0.05 }, + { "id": 2, "levels": [ 3, 4 ], "share": 0.10 }, + { "id": 3, "levels": [ 5, 6 ], "share": 0.15 }, + { "id": 4, "levels": [ 7, 8 ], "share": 0.20 }, + { "id": 5, "levels": [ 9, 10 ], "share": 0.25 } + ], + "sharesByLevelV2": [ + { "id": 1, "levels": [ 1, 2 ], "share": 0.06 }, + { "id": 2, "levels": [ 3, 4 ], "share": 0.13 }, + { "id": 3, "levels": [ 5, 6 ], "share": 0.19 }, + { "id": 4, "levels": [ 7, 8 ], "share": 0.26 }, + { "id": 5, "levels": [ 9, 10 ], "share": 0.32 } + ], + "qoraHoldersShareByHeight": [ + { "height": 1, "share": 0.20 }, + { "height": 1000000, "share": 0.01 } + ], + "qoraPerQortReward": 250, + "minAccountsToActivateShareBin": 0, + "shareBinActivationMinLevel": 7, + "blocksNeededByLevel": [ 5, 20, 30, 40, 50, 60, 18, 80, 90, 100 ], + "blockTimingsByHeight": [ + { "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 } + ], + "ciyamAtSettings": { + "feePerStep": "0.0001", + "maxStepsPerRound": 500, + "stepsPerFunctionCall": 10, + "minutesPerBlock": 1 + }, + "featureTriggers": { + "messageHeight": 0, + "atHeight": 0, + "assetsTimestamp": 0, + "votingTimestamp": 0, + "arbitraryTimestamp": 0, + "powfixTimestamp": 0, + "qortalTimestamp": 0, + "newAssetPricingTimestamp": 0, + "groupApprovalTimestamp": 0, + "atFindNextTransactionFix": 0, + "newBlockSigHeight": 999999, + "shareBinFix": 999999, + "sharesByLevelV2Height": 999999, + "rewardShareLimitTimestamp": 9999999999999, + "calcChainWeightTimestamp": 0, + "transactionV5Timestamp": 0, + "transactionV6Timestamp": 0, + "disableReferenceTimestamp": 9999999999999, + "increaseOnlineAccountsDifficultyTimestamp": 9999999999999, + "onlineAccountMinterLevelValidationHeight": 0, + "selfSponsorshipAlgoV1Height": 999999999, + "selfSponsorshipAlgoV2Height": 30, + "selfSponsorshipAlgoV3Height": 999999999, + "feeValidationFixTimestamp": 0, + "chatReferenceTimestamp": 0, + "arbitraryOptionalFeeTimestamp": 0, + "unconfirmableRewardSharesHeight": 99999999, + "disableTransferPrivsTimestamp": 9999999999500, + "enableTransferPrivsTimestamp": 9999999999950 + }, + "genesisInfo": { + "version": 4, + "timestamp": 0, + "transactions": [ + { "type": "ISSUE_ASSET", "assetName": "QORT", "description": "QORT native coin", "data": "", "quantity": 0, "isDivisible": true, "fee": 0 }, + { "type": "ISSUE_ASSET", "assetName": "Legacy-QORA", "description": "Representative legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true }, + { "type": "ISSUE_ASSET", "assetName": "QORT-from-QORA", "description": "QORT gained from holding legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true }, + + { "type": "GENESIS", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "amount": "1000000000" }, + { "type": "GENESIS", "recipient": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "amount": "1000000" }, + { "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "amount": "1000000" }, + { "type": "GENESIS", "recipient": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "amount": "1000000" }, + + { "type": "CREATE_GROUP", "creatorPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "groupName": "dev-group", "description": "developer group", "isOpen": false, "approvalThreshold": "PCT100", "minimumBlockDelay": 0, "maximumBlockDelay": 1440 }, + + { "type": "UPDATE_GROUP", "ownerPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "groupId": 1, "newOwner": "QdSnUy6sUiEnaN87dWmE92g1uQjrvPgrWG", "newDescription": "developer group", "newIsOpen": false, "newApprovalThreshold": "PCT40", "minimumBlockDelay": 10, "maximumBlockDelay": 1440 }, + + { "type": "ISSUE_ASSET", "issuerPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "assetName": "TEST", "description": "test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 }, + { "type": "ISSUE_ASSET", "issuerPublicKey": "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry", "assetName": "OTHER", "description": "other test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 }, + { "type": "ISSUE_ASSET", "issuerPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "assetName": "GOLD", "description": "gold test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 }, + + { "type": "ACCOUNT_FLAGS", "target": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "andMask": -1, "orMask": 1, "xorMask": 0 }, + { "type": "REWARD_SHARE", "minterPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "rewardSharePublicKey": "7PpfnvLSG7y4HPh8hE7KoqAjLCkv7Ui6xw4mKAkbZtox", "sharePercent": "100" }, + + { "type": "ACCOUNT_LEVEL", "target": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "level": 5 }, + { "type": "REWARD_SHARE", "minterPublicKey": "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry", "recipient": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "rewardSharePublicKey": "CcABzvk26TFEHG7Yok84jxyd4oBtLkx8RJdGFVz2csvp", "sharePercent": 100 }, + + { "type": "ACCOUNT_LEVEL", "target": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "level": 5 }, + { "type": "ACCOUNT_LEVEL", "target": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "level": 5 }, + { "type": "ACCOUNT_LEVEL", "target": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "level": 6 } + ] + } +} diff --git a/src/test/resources/test-chain-v2-self-sponsorship-algo-v3.json b/src/test/resources/test-chain-v2-self-sponsorship-algo-v3.json new file mode 100644 index 00000000..f7d1faa2 --- /dev/null +++ b/src/test/resources/test-chain-v2-self-sponsorship-algo-v3.json @@ -0,0 +1,132 @@ +{ + "isTestChain": true, + "blockTimestampMargin": 500, + "transactionExpiryPeriod": 86400000, + "maxBlockSize": 2097152, + "maxBytesPerUnitFee": 0, + "unitFees": [ + { "timestamp": 0, "fee": "0.00000001" } + ], + "nameRegistrationUnitFees": [ + { "timestamp": 0, "fee": "0.00000001" }, + { "timestamp": 1645372800000, "fee": "5" } + ], + "requireGroupForApproval": false, + "minAccountLevelToRewardShare": 5, + "maxRewardSharesPerFounderMintingAccount": 20, + "maxRewardSharesByTimestamp": [ + { "timestamp": 0, "maxShares": 20 }, + { "timestamp": 9999999999999, "maxShares": 3 } + ], + "founderEffectiveMintingLevel": 10, + "onlineAccountSignaturesMinLifetime": 3600000, + "onlineAccountSignaturesMaxLifetime": 86400000, + "onlineAccountsModulusV2Timestamp": 9999999999999, + "selfSponsorshipAlgoV1SnapshotTimestamp": 0, + "selfSponsorshipAlgoV2SnapshotTimestamp": 9999999999999, + "selfSponsorshipAlgoV3SnapshotTimestamp": 9999999999999, + "referenceTimestampBlock": 9999999999999, + "mempowTransactionUpdatesTimestamp": 0, + "blockRewardBatchStartHeight": 999999000, + "blockRewardBatchSize": 10, + "blockRewardBatchAccountsBlockCount": 3, + "rewardsByHeight": [ + { "height": 1, "reward": 100 }, + { "height": 11, "reward": 10 }, + { "height": 21, "reward": 1 } + ], + "sharesByLevelV1": [ + { "id": 1, "levels": [ 1, 2 ], "share": 0.05 }, + { "id": 2, "levels": [ 3, 4 ], "share": 0.10 }, + { "id": 3, "levels": [ 5, 6 ], "share": 0.15 }, + { "id": 4, "levels": [ 7, 8 ], "share": 0.20 }, + { "id": 5, "levels": [ 9, 10 ], "share": 0.25 } + ], + "sharesByLevelV2": [ + { "id": 1, "levels": [ 1, 2 ], "share": 0.06 }, + { "id": 2, "levels": [ 3, 4 ], "share": 0.13 }, + { "id": 3, "levels": [ 5, 6 ], "share": 0.19 }, + { "id": 4, "levels": [ 7, 8 ], "share": 0.26 }, + { "id": 5, "levels": [ 9, 10 ], "share": 0.32 } + ], + "qoraHoldersShareByHeight": [ + { "height": 1, "share": 0.20 }, + { "height": 1000000, "share": 0.01 } + ], + "qoraPerQortReward": 250, + "minAccountsToActivateShareBin": 0, + "shareBinActivationMinLevel": 7, + "blocksNeededByLevel": [ 5, 20, 30, 40, 50, 60, 28, 80, 90, 100 ], + "blockTimingsByHeight": [ + { "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 } + ], + "ciyamAtSettings": { + "feePerStep": "0.0001", + "maxStepsPerRound": 500, + "stepsPerFunctionCall": 10, + "minutesPerBlock": 1 + }, + "featureTriggers": { + "messageHeight": 0, + "atHeight": 0, + "assetsTimestamp": 0, + "votingTimestamp": 0, + "arbitraryTimestamp": 0, + "powfixTimestamp": 0, + "qortalTimestamp": 0, + "newAssetPricingTimestamp": 0, + "groupApprovalTimestamp": 0, + "atFindNextTransactionFix": 0, + "newBlockSigHeight": 999999, + "shareBinFix": 999999, + "sharesByLevelV2Height": 999999, + "rewardShareLimitTimestamp": 9999999999999, + "calcChainWeightTimestamp": 0, + "transactionV5Timestamp": 0, + "transactionV6Timestamp": 0, + "disableReferenceTimestamp": 9999999999999, + "increaseOnlineAccountsDifficultyTimestamp": 9999999999999, + "onlineAccountMinterLevelValidationHeight": 0, + "selfSponsorshipAlgoV1Height": 999999999, + "selfSponsorshipAlgoV2Height": 999999999, + "selfSponsorshipAlgoV3Height": 30, + "feeValidationFixTimestamp": 0, + "chatReferenceTimestamp": 0, + "arbitraryOptionalFeeTimestamp": 0, + "unconfirmableRewardSharesHeight": 99999999, + "disableTransferPrivsTimestamp": 9999999999500, + "enableTransferPrivsTimestamp": 9999999999950 + }, + "genesisInfo": { + "version": 4, + "timestamp": 0, + "transactions": [ + { "type": "ISSUE_ASSET", "assetName": "QORT", "description": "QORT native coin", "data": "", "quantity": 0, "isDivisible": true, "fee": 0 }, + { "type": "ISSUE_ASSET", "assetName": "Legacy-QORA", "description": "Representative legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true }, + { "type": "ISSUE_ASSET", "assetName": "QORT-from-QORA", "description": "QORT gained from holding legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true }, + + { "type": "GENESIS", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "amount": "1000000000" }, + { "type": "GENESIS", "recipient": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "amount": "1000000" }, + { "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "amount": "1000000" }, + { "type": "GENESIS", "recipient": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "amount": "1000000" }, + + { "type": "CREATE_GROUP", "creatorPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "groupName": "dev-group", "description": "developer group", "isOpen": false, "approvalThreshold": "PCT100", "minimumBlockDelay": 0, "maximumBlockDelay": 1440 }, + + { "type": "UPDATE_GROUP", "ownerPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "groupId": 1, "newOwner": "QdSnUy6sUiEnaN87dWmE92g1uQjrvPgrWG", "newDescription": "developer group", "newIsOpen": false, "newApprovalThreshold": "PCT40", "minimumBlockDelay": 10, "maximumBlockDelay": 1440 }, + + { "type": "ISSUE_ASSET", "issuerPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "assetName": "TEST", "description": "test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 }, + { "type": "ISSUE_ASSET", "issuerPublicKey": "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry", "assetName": "OTHER", "description": "other test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 }, + { "type": "ISSUE_ASSET", "issuerPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "assetName": "GOLD", "description": "gold test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 }, + + { "type": "ACCOUNT_FLAGS", "target": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "andMask": -1, "orMask": 1, "xorMask": 0 }, + { "type": "REWARD_SHARE", "minterPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "rewardSharePublicKey": "7PpfnvLSG7y4HPh8hE7KoqAjLCkv7Ui6xw4mKAkbZtox", "sharePercent": "100" }, + + { "type": "ACCOUNT_LEVEL", "target": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "level": 5 }, + { "type": "REWARD_SHARE", "minterPublicKey": "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry", "recipient": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "rewardSharePublicKey": "CcABzvk26TFEHG7Yok84jxyd4oBtLkx8RJdGFVz2csvp", "sharePercent": 100 }, + + { "type": "ACCOUNT_LEVEL", "target": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "level": 5 }, + { "type": "ACCOUNT_LEVEL", "target": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "level": 5 }, + { "type": "ACCOUNT_LEVEL", "target": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "level": 6 } + ] + } +} diff --git a/src/test/resources/test-chain-v2.json b/src/test/resources/test-chain-v2.json index f867aa46..542c0cf8 100644 --- a/src/test/resources/test-chain-v2.json +++ b/src/test/resources/test-chain-v2.json @@ -25,6 +25,8 @@ "onlineAccountsModulusV2Timestamp": 9999999999999, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, "selfSponsorshipAlgoV2SnapshotTimestamp": 9999999999999, + "selfSponsorshipAlgoV3SnapshotTimestamp": 9999999999999, + "referenceTimestampBlock": 9999999999999, "mempowTransactionUpdatesTimestamp": 0, "blockRewardBatchStartHeight": 999999000, "blockRewardBatchSize": 10, @@ -87,14 +89,14 @@ "increaseOnlineAccountsDifficultyTimestamp": 9999999999999, "onlineAccountMinterLevelValidationHeight": 0, "selfSponsorshipAlgoV1Height": 999999999, + "selfSponsorshipAlgoV2Height": 999999999, + "selfSponsorshipAlgoV3Height": 999999999, "feeValidationFixTimestamp": 0, "chatReferenceTimestamp": 0, "arbitraryOptionalFeeTimestamp": 0, "unconfirmableRewardSharesHeight": 99999999, - "selfSponsorshipAlgoV2Height": 9999999, "disableTransferPrivsTimestamp": 9999999999500, - "enableTransferPrivsTimestamp": 9999999999950, - "penaltyFixHeight": 9999999 + "enableTransferPrivsTimestamp": 9999999999950 }, "genesisInfo": { "version": 4, diff --git a/src/test/resources/test-settings-v2-self-sponsorship-algo-v2.json b/src/test/resources/test-settings-v2-self-sponsorship-algo-v2.json new file mode 100644 index 00000000..430586fa --- /dev/null +++ b/src/test/resources/test-settings-v2-self-sponsorship-algo-v2.json @@ -0,0 +1,20 @@ +{ + "repositoryPath": "testdb", + "bitcoinNet": "TEST3", + "litecoinNet": "TEST3", + "restrictedApi": false, + "blockchainConfig": "src/test/resources/test-chain-v2-self-sponsorship-algo-v2.json", + "exportPath": "qortal-backup-test", + "bootstrap": false, + "wipeUnconfirmedOnStart": false, + "testNtpOffset": 0, + "minPeers": 0, + "pruneBlockLimit": 100, + "bootstrapFilenamePrefix": "test-", + "dataPath": "data-test", + "tempDataPath": "data-test/_temp", + "listsPath": "lists-test", + "storagePolicy": "FOLLOWED_OR_VIEWED", + "maxStorageCapacity": 104857600, + "arrrDefaultBirthday": 1900000 +} diff --git a/src/test/resources/test-settings-v2-self-sponsorship-algo-v3.json b/src/test/resources/test-settings-v2-self-sponsorship-algo-v3.json new file mode 100644 index 00000000..0579b1e1 --- /dev/null +++ b/src/test/resources/test-settings-v2-self-sponsorship-algo-v3.json @@ -0,0 +1,20 @@ +{ + "repositoryPath": "testdb", + "bitcoinNet": "TEST3", + "litecoinNet": "TEST3", + "restrictedApi": false, + "blockchainConfig": "src/test/resources/test-chain-v2-self-sponsorship-algo-v3.json", + "exportPath": "qortal-backup-test", + "bootstrap": false, + "wipeUnconfirmedOnStart": false, + "testNtpOffset": 0, + "minPeers": 0, + "pruneBlockLimit": 100, + "bootstrapFilenamePrefix": "test-", + "dataPath": "data-test", + "tempDataPath": "data-test/_temp", + "listsPath": "lists-test", + "storagePolicy": "FOLLOWED_OR_VIEWED", + "maxStorageCapacity": 104857600, + "arrrDefaultBirthday": 1900000 +} From c4a7fb3b926196ebff10c5e23532c7bde8e1bca8 Mon Sep 17 00:00:00 2001 From: AlphaX-Projects <77661270+AlphaX-Projects@users.noreply.github.com> Date: Sun, 18 Feb 2024 18:14:33 +0100 Subject: [PATCH 3/3] Bump version to 4.5.1 --- pom.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index eb797523..bbc044a2 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.qortal qortal - 4.5.0 + 4.5.1 jar true @@ -21,7 +21,7 @@ 1.2.2 0.12.3 4.9.10 - 1.61.0 + 1.61.1 33.0.0-jre 2.2 1.2.1 @@ -31,9 +31,9 @@ 4.0.1 2.3.9 2.41 - 9.4.53.v20231009 + 9.4.54.v20240208 1.1.1 - 20231013 + 20240205 1.17.2 5.10.0 1.0.0 @@ -46,7 +46,7 @@ 3.2.5 1.1.0 UTF-8 - 3.25.1 + 3.25.2 1.5.3 0.16 1.17