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 22e6e9f1..caaa0c76 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)
+ 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;
}
@@ -1556,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,
@@ -1849,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 f4dd79f4..5475c929 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
+}