forked from Qortal/qortal
Compare commits
27 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
8ffb0625a1 | ||
|
2ce02faa07 | ||
|
89999e6b33 | ||
|
4d28ba692d | ||
|
cd6d7a3a98 | ||
|
0a44928e93 | ||
|
4b037ad13f | ||
|
1f9a2edca4 | ||
|
c010ab47db | ||
|
b0d43a1890 | ||
|
f277611d31 | ||
|
d89f7ad41d | ||
|
39cc56c4d8 | ||
|
fccd5a7c97 | ||
|
46395bf4dc | ||
|
0eb551acc1 | ||
|
f55efe38c5 | ||
|
130bb6cf50 | ||
|
8319193453 | ||
|
831ed72e56 | ||
|
885133195e | ||
|
c45d59b389 | ||
|
30a289baab | ||
|
d79d64f6b0 | ||
0a47ca1462 | |||
0cf9b23142 | |||
0850654519 |
12
pom.xml
12
pom.xml
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.qortal</groupId>
|
||||
<artifactId>qortal</artifactId>
|
||||
<version>4.6.1</version>
|
||||
<version>4.6.5</version>
|
||||
<packaging>jar</packaging>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
@@ -16,7 +16,7 @@
|
||||
<ciyam-at.version>1.4.2</ciyam-at.version>
|
||||
<commons-net.version>3.8.0</commons-net.version>
|
||||
<commons-text.version>1.12.0</commons-text.version>
|
||||
<commons-io.version>2.17.0</commons-io.version>
|
||||
<commons-io.version>2.18.0</commons-io.version>
|
||||
<commons-compress.version>1.27.1</commons-compress.version>
|
||||
<commons-lang3.version>3.17.0</commons-lang3.version>
|
||||
<dagger.version>1.2.2</dagger.version>
|
||||
@@ -26,9 +26,9 @@
|
||||
<guava.version>33.3.1-jre</guava.version>
|
||||
<hamcrest-library.version>2.2</hamcrest-library.version>
|
||||
<homoglyph.version>1.2.1</homoglyph.version>
|
||||
<hsqldb.version>2.5.1</hsqldb.version>
|
||||
<hsqldb.version>2.7.4</hsqldb.version>
|
||||
<icu4j.version>76.1</icu4j.version>
|
||||
<java-diff-utils.version>4.12</java-diff-utils.version>
|
||||
<java-diff-utils.version>4.15</java-diff-utils.version>
|
||||
<javax.servlet-api.version>4.0.1</javax.servlet-api.version>
|
||||
<jaxb-runtime.version>2.3.9</jaxb-runtime.version>
|
||||
<jersey.version>2.42</jersey.version>
|
||||
@@ -45,7 +45,7 @@
|
||||
<maven-dependency-plugin.version>3.6.1</maven-dependency-plugin.version>
|
||||
<maven-jar-plugin.version>3.4.2</maven-jar-plugin.version>
|
||||
<maven-package-info-plugin.version>1.1.0</maven-package-info-plugin.version>
|
||||
<maven-plugin.version>2.17.1</maven-plugin.version>
|
||||
<maven-plugin.version>2.18.0</maven-plugin.version>
|
||||
<maven-reproducible-build-plugin.version>0.17</maven-reproducible-build-plugin.version>
|
||||
<maven-resources-plugin.version>3.3.1</maven-resources-plugin.version>
|
||||
<maven-shade-plugin.version>3.6.0</maven-shade-plugin.version>
|
||||
@@ -55,7 +55,7 @@
|
||||
<simplemagic.version>1.17</simplemagic.version>
|
||||
<slf4j.version>1.7.36</slf4j.version>
|
||||
<swagger-api.version>2.0.10</swagger-api.version>
|
||||
<swagger-ui.version>5.17.14</swagger-ui.version>
|
||||
<swagger-ui.version>5.18.2</swagger-ui.version>
|
||||
<upnp.version>1.2</upnp.version>
|
||||
<xz.version>1.10</xz.version>
|
||||
</properties>
|
||||
|
@@ -198,66 +198,76 @@ public class Account {
|
||||
|
||||
/** Returns whether account can be considered a "minting account".
|
||||
* <p>
|
||||
* To be considered a "minting account", the account needs to pass all of these tests:<br>
|
||||
* To be considered a "minting account", the account needs to pass some of these tests:<br>
|
||||
* <ul>
|
||||
* <li>account's level is at least <tt>minAccountLevelToMint</tt> from blockchain config</li>
|
||||
* <li>account's address have registered a name</li>
|
||||
* <li>account's address is member of minter group</li>
|
||||
* <li>account's address has registered a name</li>
|
||||
* <li>account's address is a member of the minter group</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param isGroupValidated true if this account has already been validated for MINTER Group membership
|
||||
* @return true if account can be considered "minting account"
|
||||
* @throws DataException
|
||||
*/
|
||||
public boolean canMint() throws DataException {
|
||||
public boolean canMint(boolean isGroupValidated) throws DataException {
|
||||
AccountData accountData = this.repository.getAccountRepository().getAccount(this.address);
|
||||
NameRepository nameRepository = this.repository.getNameRepository();
|
||||
GroupRepository groupRepository = this.repository.getGroupRepository();
|
||||
String myAddress = accountData.getAddress();
|
||||
|
||||
int blockchainHeight = this.repository.getBlockRepository().getBlockchainHeight();
|
||||
int nameCheckHeight = BlockChain.getInstance().getOnlyMintWithNameHeight();
|
||||
int levelToMint = BlockChain.getInstance().getMinAccountLevelToMint();
|
||||
int level = accountData.getLevel();
|
||||
int groupIdToMint = BlockChain.getInstance().getMintingGroupId();
|
||||
int nameCheckHeight = BlockChain.getInstance().getOnlyMintWithNameHeight();
|
||||
int groupCheckHeight = BlockChain.getInstance().getGroupMemberCheckHeight();
|
||||
int removeNameCheckHeight = BlockChain.getInstance().getRemoveOnlyMintWithNameHeight();
|
||||
|
||||
String myAddress = accountData.getAddress();
|
||||
List<NameData> myName = nameRepository.getNamesByOwner(myAddress);
|
||||
boolean isMember = groupRepository.memberExists(groupIdToMint, myAddress);
|
||||
// Can only mint if:
|
||||
// Account's level is at least minAccountLevelToMint from blockchain config
|
||||
if (blockchainHeight < nameCheckHeight) {
|
||||
if (Account.isFounder(accountData.getFlags())) {
|
||||
return accountData.getBlocksMintedPenalty() == 0;
|
||||
} else {
|
||||
return level >= levelToMint;
|
||||
}
|
||||
}
|
||||
|
||||
if (accountData == null)
|
||||
return false;
|
||||
// Can only mint on onlyMintWithNameHeight from blockchain config if:
|
||||
// Account's level is at least minAccountLevelToMint from blockchain config
|
||||
// Account's address has registered a name
|
||||
if (blockchainHeight >= nameCheckHeight && blockchainHeight < groupCheckHeight) {
|
||||
List<NameData> myName = nameRepository.getNamesByOwner(myAddress);
|
||||
if (Account.isFounder(accountData.getFlags())) {
|
||||
return accountData.getBlocksMintedPenalty() == 0 && !myName.isEmpty();
|
||||
} else {
|
||||
return level >= levelToMint && !myName.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
// Can only mint if level is at least minAccountLevelToMint< from blockchain config
|
||||
if (blockchainHeight < nameCheckHeight && level >= levelToMint)
|
||||
return true;
|
||||
// Can only mint on groupMemberCheckHeight from blockchain config if:
|
||||
// Account's level is at least minAccountLevelToMint from blockchain config
|
||||
// Account's address has registered a name
|
||||
// Account's address is a member of the minter group
|
||||
if (blockchainHeight >= groupCheckHeight && blockchainHeight < removeNameCheckHeight) {
|
||||
List<NameData> myName = nameRepository.getNamesByOwner(myAddress);
|
||||
if (Account.isFounder(accountData.getFlags())) {
|
||||
return accountData.getBlocksMintedPenalty() == 0 && !myName.isEmpty() && (isGroupValidated || groupRepository.memberExists(groupIdToMint, myAddress));
|
||||
} else {
|
||||
return level >= levelToMint && !myName.isEmpty() && (isGroupValidated || groupRepository.memberExists(groupIdToMint, myAddress));
|
||||
}
|
||||
}
|
||||
|
||||
// Can only mint if have registered a name
|
||||
if (blockchainHeight >= nameCheckHeight && blockchainHeight < groupCheckHeight && level >= levelToMint && !myName.isEmpty())
|
||||
return true;
|
||||
|
||||
// Can only mint if have registered a name and is member of minter group id
|
||||
if (blockchainHeight >= groupCheckHeight && level >= levelToMint && !myName.isEmpty() && isMember)
|
||||
return true;
|
||||
|
||||
// Founders needs to pass same tests like minters
|
||||
if (blockchainHeight < nameCheckHeight &&
|
||||
Account.isFounder(accountData.getFlags()) &&
|
||||
accountData.getBlocksMintedPenalty() == 0)
|
||||
return true;
|
||||
|
||||
if (blockchainHeight >= nameCheckHeight &&
|
||||
blockchainHeight < groupCheckHeight &&
|
||||
Account.isFounder(accountData.getFlags()) &&
|
||||
accountData.getBlocksMintedPenalty() == 0 &&
|
||||
!myName.isEmpty())
|
||||
return true;
|
||||
|
||||
if (blockchainHeight >= groupCheckHeight &&
|
||||
Account.isFounder(accountData.getFlags()) &&
|
||||
accountData.getBlocksMintedPenalty() == 0 &&
|
||||
!myName.isEmpty() &&
|
||||
isMember)
|
||||
return true;
|
||||
// Can only mint on removeOnlyMintWithNameHeight from blockchain config if:
|
||||
// Account's level is at least minAccountLevelToMint from blockchain config
|
||||
// Account's address is a member of the minter group
|
||||
if (blockchainHeight >= removeNameCheckHeight) {
|
||||
if (Account.isFounder(accountData.getFlags())) {
|
||||
return accountData.getBlocksMintedPenalty() == 0 && (isGroupValidated || groupRepository.memberExists(groupIdToMint, myAddress));
|
||||
} else {
|
||||
return level >= levelToMint && (isGroupValidated || groupRepository.memberExists(groupIdToMint, myAddress));
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -272,7 +282,6 @@ public class Account {
|
||||
return this.repository.getAccountRepository().getBlocksMintedPenaltyCount(this.address);
|
||||
}
|
||||
|
||||
|
||||
/** Returns whether account can build reward-shares.
|
||||
* <p>
|
||||
* To be able to create reward-shares, the account needs to pass at least one of these tests:<br>
|
||||
@@ -286,6 +295,7 @@ public class Account {
|
||||
*/
|
||||
public boolean canRewardShare() throws DataException {
|
||||
AccountData accountData = this.repository.getAccountRepository().getAccount(this.address);
|
||||
|
||||
if (accountData == null)
|
||||
return false;
|
||||
|
||||
@@ -355,6 +365,7 @@ public class Account {
|
||||
Account rewardShareMinter = new Account(repository, rewardShareData.getMinter());
|
||||
return rewardShareMinter.getEffectiveMintingLevel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns 'effective' minting level, with a fix for the zero level.
|
||||
* <p>
|
||||
|
@@ -459,7 +459,7 @@ public class AdminResource {
|
||||
|
||||
// Qortal: check reward-share's minting account is still allowed to mint
|
||||
Account rewardShareMintingAccount = new Account(repository, rewardShareData.getMinter());
|
||||
if (!rewardShareMintingAccount.canMint())
|
||||
if (!rewardShareMintingAccount.canMint(false))
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.CANNOT_MINT);
|
||||
|
||||
MintingAccountData mintingAccountData = new MintingAccountData(mintingAccount.getPrivateKey(), mintingAccount.getPublicKey());
|
||||
|
@@ -168,7 +168,7 @@ public class ArbitraryDataRenderer {
|
||||
byte[] data = Files.readAllBytes(filePath); // TODO: limit file size that can be read into memory
|
||||
HTMLParser htmlParser = new HTMLParser(resourceId, inPath, prefix, includeResourceIdInPrefix, data, qdnContext, service, identifier, theme, usingCustomRouting);
|
||||
htmlParser.addAdditionalHeaderTags();
|
||||
response.addHeader("Content-Security-Policy", "default-src 'self' 'unsafe-inline' 'unsafe-eval'; media-src 'self' data: blob:; img-src 'self' data: blob:;");
|
||||
response.addHeader("Content-Security-Policy", "default-src 'self' 'unsafe-inline' 'unsafe-eval'; font-src 'self' data:; media-src 'self' data: blob:; img-src 'self' data: blob:; connect-src 'self' wss:;");
|
||||
response.setContentType(context.getMimeType(filename));
|
||||
response.setContentLength(htmlParser.getData().length);
|
||||
response.getOutputStream().write(htmlParser.getData());
|
||||
|
@@ -25,10 +25,7 @@ import org.qortal.data.block.BlockSummaryData;
|
||||
import org.qortal.data.block.BlockTransactionData;
|
||||
import org.qortal.data.network.OnlineAccountData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.repository.ATRepository;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.TransactionRepository;
|
||||
import org.qortal.repository.*;
|
||||
import org.qortal.settings.Settings;
|
||||
import org.qortal.transaction.AtTransaction;
|
||||
import org.qortal.transaction.Transaction;
|
||||
@@ -144,9 +141,12 @@ public class Block {
|
||||
private final Account mintingAccount;
|
||||
private final AccountData mintingAccountData;
|
||||
private final boolean isMinterFounder;
|
||||
private final boolean isMinterMember;
|
||||
|
||||
private final Account recipientAccount;
|
||||
private final AccountData recipientAccountData;
|
||||
|
||||
final BlockChain blockChain = BlockChain.getInstance();
|
||||
|
||||
ExpandedAccount(Repository repository, RewardShareData rewardShareData) throws DataException {
|
||||
this.rewardShareData = rewardShareData;
|
||||
@@ -157,6 +157,7 @@ public class Block {
|
||||
this.isMinterFounder = Account.isFounder(mintingAccountData.getFlags());
|
||||
|
||||
this.isRecipientAlsoMinter = this.rewardShareData.getRecipient().equals(this.mintingAccount.getAddress());
|
||||
this.isMinterMember = repository.getGroupRepository().memberExists(BlockChain.getInstance().getMintingGroupId(), this.mintingAccount.getAddress());
|
||||
|
||||
if (this.isRecipientAlsoMinter) {
|
||||
// Self-share: minter is also recipient
|
||||
@@ -181,7 +182,7 @@ public class Block {
|
||||
* <p>
|
||||
* This is a method, not a final variable, because account's level can change between construction and call,
|
||||
* e.g. during Block.process() where account levels are bumped right before Block.distributeBlockReward().
|
||||
*
|
||||
*
|
||||
* @return account-level share "bin" from blockchain config, or null if founder / none found
|
||||
*/
|
||||
public AccountLevelShareBin getShareBin(int blockHeight) {
|
||||
@@ -192,8 +193,12 @@ public class Block {
|
||||
if (accountLevel <= 0)
|
||||
return null; // level 0 isn't included in any share bins
|
||||
|
||||
if (blockHeight >= blockChain.getFixBatchRewardHeight()) {
|
||||
if (!this.isMinterMember)
|
||||
return null; // not member of minter group isn't included in any share bins
|
||||
}
|
||||
|
||||
// Select the correct set of share bins based on block height
|
||||
final BlockChain blockChain = BlockChain.getInstance();
|
||||
final AccountLevelShareBin[] shareBinsByLevel = (blockHeight >= blockChain.getSharesByLevelV2Height()) ?
|
||||
blockChain.getShareBinsByAccountLevelV2() : blockChain.getShareBinsByAccountLevelV1();
|
||||
|
||||
@@ -262,7 +267,7 @@ public class Block {
|
||||
* Constructs new Block without loading transactions and AT states.
|
||||
* <p>
|
||||
* Transactions and AT states are loaded on first call to getTransactions() or getATStates() respectively.
|
||||
*
|
||||
*
|
||||
* @param repository
|
||||
* @param blockData
|
||||
*/
|
||||
@@ -333,7 +338,7 @@ public class Block {
|
||||
|
||||
/**
|
||||
* Constructs new Block with empty transaction list, using passed minter account.
|
||||
*
|
||||
*
|
||||
* @param repository
|
||||
* @param blockData
|
||||
* @param minter
|
||||
@@ -351,7 +356,7 @@ public class Block {
|
||||
* This constructor typically used when minting a new block.
|
||||
* <p>
|
||||
* Note that CIYAM ATs will be executed and AT-Transactions prepended to this block, along with AT state data and fees.
|
||||
*
|
||||
*
|
||||
* @param repository
|
||||
* @param parentBlockData
|
||||
* @param minter
|
||||
@@ -377,7 +382,7 @@ public class Block {
|
||||
byte[] encodedOnlineAccounts = new byte[0];
|
||||
int onlineAccountsCount = 0;
|
||||
byte[] onlineAccountsSignatures = null;
|
||||
|
||||
|
||||
if (isBatchRewardDistributionBlock(height)) {
|
||||
// Batch reward distribution block - copy online accounts from recent block with highest online accounts count
|
||||
|
||||
@@ -512,7 +517,7 @@ public class Block {
|
||||
* Mints new block using this block as template, but with different minting account.
|
||||
* <p>
|
||||
* NOTE: uses the same transactions list, AT states, etc.
|
||||
*
|
||||
*
|
||||
* @param minter
|
||||
* @return
|
||||
* @throws DataException
|
||||
@@ -598,7 +603,7 @@ public class Block {
|
||||
|
||||
/**
|
||||
* Return composite block signature (minterSignature + transactionsSignature).
|
||||
*
|
||||
*
|
||||
* @return byte[], or null if either component signature is null.
|
||||
*/
|
||||
public byte[] getSignature() {
|
||||
@@ -613,7 +618,7 @@ public class Block {
|
||||
* <p>
|
||||
* We're starting with version 4 as a nod to being newer than successor Qora,
|
||||
* whose latest block version was 3.
|
||||
*
|
||||
*
|
||||
* @return 1, 2, 3 or 4
|
||||
*/
|
||||
public int getNextBlockVersion() {
|
||||
@@ -627,7 +632,7 @@ public class Block {
|
||||
* Return block's transactions.
|
||||
* <p>
|
||||
* If the block was loaded from repository then it's possible this method will call the repository to fetch the transactions if not done already.
|
||||
*
|
||||
*
|
||||
* @return
|
||||
* @throws DataException
|
||||
*/
|
||||
@@ -661,7 +666,7 @@ public class Block {
|
||||
* If the block was loaded from repository then it's possible this method will call the repository to fetch the AT states if not done already.
|
||||
* <p>
|
||||
* <b>Note:</b> AT states fetched from repository only contain summary info, not actual data like serialized state data or AT creation timestamps!
|
||||
*
|
||||
*
|
||||
* @return
|
||||
* @throws DataException
|
||||
*/
|
||||
@@ -697,7 +702,7 @@ public class Block {
|
||||
* <p>
|
||||
* Typically called as part of Block.process() or Block.orphan()
|
||||
* so ideally after any calls to Block.isValid().
|
||||
*
|
||||
*
|
||||
* @throws DataException
|
||||
*/
|
||||
public List<ExpandedAccount> getExpandedAccounts() throws DataException {
|
||||
@@ -715,8 +720,18 @@ public class Block {
|
||||
|
||||
List<ExpandedAccount> expandedAccounts = new ArrayList<>();
|
||||
|
||||
for (RewardShareData rewardShare : this.cachedOnlineRewardShares)
|
||||
expandedAccounts.add(new ExpandedAccount(repository, rewardShare));
|
||||
for (RewardShareData rewardShare : this.cachedOnlineRewardShares) {
|
||||
if (this.getBlockData().getHeight() < BlockChain.getInstance().getFixBatchRewardHeight()) {
|
||||
expandedAccounts.add(new ExpandedAccount(repository, rewardShare));
|
||||
}
|
||||
if (this.getBlockData().getHeight() >= BlockChain.getInstance().getFixBatchRewardHeight()) {
|
||||
boolean isMinterGroupMember = repository.getGroupRepository().memberExists(BlockChain.getInstance().getMintingGroupId(), rewardShare.getMinter());
|
||||
if (isMinterGroupMember) {
|
||||
expandedAccounts.add(new ExpandedAccount(repository, rewardShare));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.cachedExpandedAccounts = expandedAccounts;
|
||||
|
||||
@@ -727,7 +742,7 @@ public class Block {
|
||||
|
||||
/**
|
||||
* Load parent block's data from repository via this block's reference.
|
||||
*
|
||||
*
|
||||
* @return parent's BlockData, or null if no parent found
|
||||
* @throws DataException
|
||||
*/
|
||||
@@ -741,7 +756,7 @@ public class Block {
|
||||
|
||||
/**
|
||||
* Load child block's data from repository via this block's signature.
|
||||
*
|
||||
*
|
||||
* @return child's BlockData, or null if no parent found
|
||||
* @throws DataException
|
||||
*/
|
||||
@@ -761,7 +776,7 @@ public class Block {
|
||||
* Used when constructing a new block during minting.
|
||||
* <p>
|
||||
* Requires block's {@code minter} being a {@code PrivateKeyAccount} so block's transactions signature can be recalculated.
|
||||
*
|
||||
*
|
||||
* @param transactionData
|
||||
* @return true if transaction successfully added to block, false otherwise
|
||||
* @throws IllegalStateException
|
||||
@@ -814,7 +829,7 @@ public class Block {
|
||||
* Used when constructing a new block during minting.
|
||||
* <p>
|
||||
* Requires block's {@code minter} being a {@code PrivateKeyAccount} so block's transactions signature can be recalculated.
|
||||
*
|
||||
*
|
||||
* @param transactionData
|
||||
* @throws IllegalStateException
|
||||
* if block's {@code minter} is not a {@code PrivateKeyAccount}.
|
||||
@@ -859,7 +874,7 @@ public class Block {
|
||||
* previous block's minter signature + minter's public key + (encoded) online-accounts data
|
||||
* <p>
|
||||
* (Previous block's minter signature is extracted from this block's reference).
|
||||
*
|
||||
*
|
||||
* @throws IllegalStateException
|
||||
* if block's {@code minter} is not a {@code PrivateKeyAccount}.
|
||||
* @throws RuntimeException
|
||||
@@ -876,7 +891,7 @@ public class Block {
|
||||
* Recalculate block's transactions signature.
|
||||
* <p>
|
||||
* Requires block's {@code minter} being a {@code PrivateKeyAccount}.
|
||||
*
|
||||
*
|
||||
* @throws IllegalStateException
|
||||
* if block's {@code minter} is not a {@code PrivateKeyAccount}.
|
||||
* @throws RuntimeException
|
||||
@@ -998,7 +1013,7 @@ public class Block {
|
||||
* Recalculate block's minter and transactions signatures, thus giving block full signature.
|
||||
* <p>
|
||||
* Note: Block instance must have been constructed with a <tt>PrivateKeyAccount</tt> minter or this call will throw an <tt>IllegalStateException</tt>.
|
||||
*
|
||||
*
|
||||
* @throws IllegalStateException
|
||||
* if block's {@code minter} is not a {@code PrivateKeyAccount}.
|
||||
*/
|
||||
@@ -1011,7 +1026,7 @@ public class Block {
|
||||
|
||||
/**
|
||||
* Returns whether this block's signatures are valid.
|
||||
*
|
||||
*
|
||||
* @return true if both minter and transaction signatures are valid, false otherwise
|
||||
*/
|
||||
public boolean isSignatureValid() {
|
||||
@@ -1035,7 +1050,7 @@ public class Block {
|
||||
* <p>
|
||||
* Used by BlockMinter to check whether it's time to mint a new block,
|
||||
* and also used by Block.isValid for checks (if not a testchain).
|
||||
*
|
||||
*
|
||||
* @return ValidationResult.OK if timestamp valid, or some other ValidationResult otherwise.
|
||||
* @throws DataException
|
||||
*/
|
||||
@@ -1215,7 +1230,7 @@ public class Block {
|
||||
* <p>
|
||||
* Checks block's transactions by testing their validity then processing them.<br>
|
||||
* Hence uses a repository savepoint during execution.
|
||||
*
|
||||
*
|
||||
* @return ValidationResult.OK if block is valid, or some other ValidationResult otherwise.
|
||||
* @throws DataException
|
||||
*/
|
||||
@@ -1386,7 +1401,7 @@ public class Block {
|
||||
* <p>
|
||||
* NOTE: will execute ATs locally if not already done.<br>
|
||||
* This is so we have locally-generated AT states for comparison.
|
||||
*
|
||||
*
|
||||
* @return OK, or some AT-related validation result
|
||||
* @throws DataException
|
||||
*/
|
||||
@@ -1462,11 +1477,11 @@ public class Block {
|
||||
* Note: this method does not store new AT state data into repository - that is handled by <tt>process()</tt>.
|
||||
* <p>
|
||||
* This method is not needed if fetching an existing block from the repository as AT state data will be loaded from repository as well.
|
||||
*
|
||||
*
|
||||
* @see #isValid()
|
||||
*
|
||||
*
|
||||
* @throws DataException
|
||||
*
|
||||
*
|
||||
*/
|
||||
private void executeATs() throws DataException {
|
||||
// We're expecting a lack of AT state data at this point.
|
||||
@@ -1518,7 +1533,7 @@ public class Block {
|
||||
return false;
|
||||
|
||||
Account mintingAccount = new PublicKeyAccount(this.repository, rewardShareData.getMinterPublicKey());
|
||||
return mintingAccount.canMint();
|
||||
return mintingAccount.canMint(false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1538,7 +1553,7 @@ public class Block {
|
||||
|
||||
/**
|
||||
* Process block, and its transactions, adding them to the blockchain.
|
||||
*
|
||||
*
|
||||
* @throws DataException
|
||||
*/
|
||||
public void process() throws DataException {
|
||||
@@ -1839,7 +1854,7 @@ public class Block {
|
||||
|
||||
/**
|
||||
* Removes block from blockchain undoing transactions and adding them to unconfirmed pile.
|
||||
*
|
||||
*
|
||||
* @throws DataException
|
||||
*/
|
||||
public void orphan() throws DataException {
|
||||
@@ -1879,7 +1894,7 @@ public class Block {
|
||||
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
|
||||
@@ -2213,6 +2228,7 @@ public class Block {
|
||||
List<AccountBalanceData> accountBalanceDeltas = balanceChanges.entrySet().stream()
|
||||
.map(entry -> new AccountBalanceData(entry.getKey(), Asset.QORT, entry.getValue()))
|
||||
.collect(Collectors.toList());
|
||||
LOGGER.trace("Account Balance Deltas: {}", accountBalanceDeltas);
|
||||
this.repository.getAccountRepository().modifyAssetBalances(accountBalanceDeltas);
|
||||
}
|
||||
|
||||
@@ -2225,30 +2241,30 @@ public class Block {
|
||||
|
||||
/*
|
||||
* Distribution rules:
|
||||
*
|
||||
*
|
||||
* Distribution is based on the minting account of 'online' reward-shares.
|
||||
*
|
||||
*
|
||||
* If ANY founders are online, then they receive the leftover non-distributed reward.
|
||||
* If NO founders are online, then account-level-based rewards are scaled up so 100% of reward is allocated.
|
||||
*
|
||||
*
|
||||
* If ANY non-maxxed legacy QORA holders exist then they are always allocated their fixed share (e.g. 20%).
|
||||
*
|
||||
*
|
||||
* There has to be either at least one 'online' account for blocks to be minted
|
||||
* so there is always either one account-level-based or founder reward candidate.
|
||||
*
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
*
|
||||
* With at least one founder online:
|
||||
* Level 1/2 accounts: 5%
|
||||
* Legacy QORA holders: 20%
|
||||
* Founders: ~75%
|
||||
*
|
||||
*
|
||||
* No online founders:
|
||||
* Level 1/2 accounts: 5%
|
||||
* Level 5/6 accounts: 15%
|
||||
* Legacy QORA holders: 20%
|
||||
* Total: 40%
|
||||
*
|
||||
*
|
||||
* After scaling account-level-based shares to fill 100%:
|
||||
* Level 1/2 accounts: 20%
|
||||
* Level 5/6 accounts: 60%
|
||||
|
@@ -71,6 +71,7 @@ public class BlockChain {
|
||||
transactionV6Timestamp,
|
||||
disableReferenceTimestamp,
|
||||
increaseOnlineAccountsDifficultyTimestamp,
|
||||
decreaseOnlineAccountsDifficultyTimestamp,
|
||||
onlineAccountMinterLevelValidationHeight,
|
||||
selfSponsorshipAlgoV1Height,
|
||||
selfSponsorshipAlgoV2Height,
|
||||
@@ -85,7 +86,9 @@ public class BlockChain {
|
||||
disableRewardshareHeight,
|
||||
enableRewardshareHeight,
|
||||
onlyMintWithNameHeight,
|
||||
groupMemberCheckHeight
|
||||
removeOnlyMintWithNameHeight,
|
||||
groupMemberCheckHeight,
|
||||
fixBatchRewardHeight
|
||||
}
|
||||
|
||||
// Custom transaction fees
|
||||
@@ -217,6 +220,10 @@ public class BlockChain {
|
||||
* featureTriggers because unit tests need to set this value via Reflection. */
|
||||
private long onlineAccountsModulusV2Timestamp;
|
||||
|
||||
/** Feature trigger timestamp for ONLINE_ACCOUNTS_MODULUS time interval decrease. Can't use
|
||||
* featureTriggers because unit tests need to set this value via Reflection. */
|
||||
private long onlineAccountsModulusV3Timestamp;
|
||||
|
||||
/** Snapshot timestamp for self sponsorship algo V1 */
|
||||
private long selfSponsorshipAlgoV1SnapshotTimestamp;
|
||||
|
||||
@@ -403,6 +410,10 @@ public class BlockChain {
|
||||
return this.onlineAccountsModulusV2Timestamp;
|
||||
}
|
||||
|
||||
public long getOnlineAccountsModulusV3Timestamp() {
|
||||
return this.onlineAccountsModulusV3Timestamp;
|
||||
}
|
||||
|
||||
/* Block reward batching */
|
||||
public long getBlockRewardBatchStartHeight() {
|
||||
return this.blockRewardBatchStartHeight;
|
||||
@@ -579,6 +590,10 @@ public class BlockChain {
|
||||
return this.featureTriggers.get(FeatureTrigger.increaseOnlineAccountsDifficultyTimestamp.name()).longValue();
|
||||
}
|
||||
|
||||
public long getDecreaseOnlineAccountsDifficultyTimestamp() {
|
||||
return this.featureTriggers.get(FeatureTrigger.decreaseOnlineAccountsDifficultyTimestamp.name()).longValue();
|
||||
}
|
||||
|
||||
public int getSelfSponsorshipAlgoV1Height() {
|
||||
return this.featureTriggers.get(FeatureTrigger.selfSponsorshipAlgoV1Height.name()).intValue();
|
||||
}
|
||||
@@ -635,10 +650,18 @@ public class BlockChain {
|
||||
return this.featureTriggers.get(FeatureTrigger.onlyMintWithNameHeight.name()).intValue();
|
||||
}
|
||||
|
||||
public int getRemoveOnlyMintWithNameHeight() {
|
||||
return this.featureTriggers.get(FeatureTrigger.removeOnlyMintWithNameHeight.name()).intValue();
|
||||
}
|
||||
|
||||
public int getGroupMemberCheckHeight() {
|
||||
return this.featureTriggers.get(FeatureTrigger.groupMemberCheckHeight.name()).intValue();
|
||||
}
|
||||
|
||||
public int getFixBatchRewardHeight() {
|
||||
return this.featureTriggers.get(FeatureTrigger.fixBatchRewardHeight.name()).intValue();
|
||||
}
|
||||
|
||||
// More complex getters for aspects that change by height or timestamp
|
||||
|
||||
public long getRewardAtHeight(int ourHeight) {
|
||||
|
@@ -148,7 +148,7 @@ public class BlockMinter extends Thread {
|
||||
}
|
||||
|
||||
Account mintingAccount = new Account(repository, rewardShareData.getMinter());
|
||||
if (!mintingAccount.canMint()) {
|
||||
if (!mintingAccount.canMint(true)) {
|
||||
// Minting-account component of reward-share can no longer mint - disregard
|
||||
madi.remove();
|
||||
continue;
|
||||
|
@@ -13,6 +13,7 @@ import org.qortal.crypto.MemoryPoW;
|
||||
import org.qortal.crypto.Qortal25519Extras;
|
||||
import org.qortal.data.account.MintingAccountData;
|
||||
import org.qortal.data.account.RewardShareData;
|
||||
import org.qortal.data.group.GroupMemberData;
|
||||
import org.qortal.data.network.OnlineAccountData;
|
||||
import org.qortal.network.Network;
|
||||
import org.qortal.network.Peer;
|
||||
@@ -44,6 +45,7 @@ public class OnlineAccountsManager {
|
||||
*/
|
||||
private static final long ONLINE_TIMESTAMP_MODULUS_V1 = 5 * 60 * 1000L;
|
||||
private static final long ONLINE_TIMESTAMP_MODULUS_V2 = 30 * 60 * 1000L;
|
||||
private static final long ONLINE_TIMESTAMP_MODULUS_V3 = 10 * 60 * 1000L;
|
||||
|
||||
/**
|
||||
* How many 'current' timestamp-sets of online accounts we cache.
|
||||
@@ -67,12 +69,13 @@ public class OnlineAccountsManager {
|
||||
private static final long ONLINE_ACCOUNTS_COMPUTE_INITIAL_SLEEP_INTERVAL = 30 * 1000L; // ms
|
||||
|
||||
// MemoryPoW - mainnet
|
||||
public static final int POW_BUFFER_SIZE = 1 * 1024 * 1024; // bytes
|
||||
public static final int POW_BUFFER_SIZE = 1024 * 1024; // bytes
|
||||
public static final int POW_DIFFICULTY_V1 = 18; // leading zero bits
|
||||
public static final int POW_DIFFICULTY_V2 = 19; // leading zero bits
|
||||
public static final int POW_DIFFICULTY_V3 = 6; // leading zero bits
|
||||
|
||||
// MemoryPoW - testnet
|
||||
public static final int POW_BUFFER_SIZE_TESTNET = 1 * 1024 * 1024; // bytes
|
||||
public static final int POW_BUFFER_SIZE_TESTNET = 1024 * 1024; // bytes
|
||||
public static final int POW_DIFFICULTY_TESTNET = 5; // leading zero bits
|
||||
|
||||
// IMPORTANT: if we ever need to dynamically modify the buffer size using a feature trigger, the
|
||||
@@ -106,11 +109,15 @@ public class OnlineAccountsManager {
|
||||
|
||||
public static long getOnlineTimestampModulus() {
|
||||
Long now = NTP.getTime();
|
||||
if (now != null && now >= BlockChain.getInstance().getOnlineAccountsModulusV2Timestamp()) {
|
||||
if (now != null && now >= BlockChain.getInstance().getOnlineAccountsModulusV2Timestamp() && now < BlockChain.getInstance().getOnlineAccountsModulusV3Timestamp()) {
|
||||
return ONLINE_TIMESTAMP_MODULUS_V2;
|
||||
}
|
||||
if (now != null && now >= BlockChain.getInstance().getOnlineAccountsModulusV3Timestamp()) {
|
||||
return ONLINE_TIMESTAMP_MODULUS_V3;
|
||||
}
|
||||
return ONLINE_TIMESTAMP_MODULUS_V1;
|
||||
}
|
||||
|
||||
public static Long getCurrentOnlineAccountTimestamp() {
|
||||
Long now = NTP.getTime();
|
||||
if (now == null)
|
||||
@@ -135,9 +142,12 @@ public class OnlineAccountsManager {
|
||||
if (Settings.getInstance().isTestNet())
|
||||
return POW_DIFFICULTY_TESTNET;
|
||||
|
||||
if (timestamp >= BlockChain.getInstance().getIncreaseOnlineAccountsDifficultyTimestamp())
|
||||
if (timestamp >= BlockChain.getInstance().getIncreaseOnlineAccountsDifficultyTimestamp() && timestamp < BlockChain.getInstance().getDecreaseOnlineAccountsDifficultyTimestamp())
|
||||
return POW_DIFFICULTY_V2;
|
||||
|
||||
if (timestamp >= BlockChain.getInstance().getDecreaseOnlineAccountsDifficultyTimestamp())
|
||||
return POW_DIFFICULTY_V3;
|
||||
|
||||
return POW_DIFFICULTY_V1;
|
||||
}
|
||||
|
||||
@@ -215,6 +225,12 @@ public class OnlineAccountsManager {
|
||||
Set<OnlineAccountData> onlineAccountsToAdd = new HashSet<>();
|
||||
Set<OnlineAccountData> onlineAccountsToRemove = new HashSet<>();
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
List<String> mintingGroupMemberAddresses
|
||||
= repository.getGroupRepository()
|
||||
.getGroupMembers(BlockChain.getInstance().getMintingGroupId()).stream()
|
||||
.map(GroupMemberData::getMember)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (OnlineAccountData onlineAccountData : this.onlineAccountsImportQueue) {
|
||||
if (isStopping)
|
||||
return;
|
||||
@@ -227,7 +243,7 @@ public class OnlineAccountsManager {
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean isValid = this.isValidCurrentAccount(repository, onlineAccountData);
|
||||
boolean isValid = this.isValidCurrentAccount(repository, mintingGroupMemberAddresses, onlineAccountData);
|
||||
if (isValid)
|
||||
onlineAccountsToAdd.add(onlineAccountData);
|
||||
|
||||
@@ -306,7 +322,7 @@ public class OnlineAccountsManager {
|
||||
return inplaceArray;
|
||||
}
|
||||
|
||||
private static boolean isValidCurrentAccount(Repository repository, OnlineAccountData onlineAccountData) throws DataException {
|
||||
private static boolean isValidCurrentAccount(Repository repository, List<String> mintingGroupMemberAddresses, OnlineAccountData onlineAccountData) throws DataException {
|
||||
final Long now = NTP.getTime();
|
||||
if (now == null)
|
||||
return false;
|
||||
@@ -341,9 +357,14 @@ public class OnlineAccountsManager {
|
||||
LOGGER.trace(() -> String.format("Rejecting unknown online reward-share public key %s", Base58.encode(rewardSharePublicKey)));
|
||||
return false;
|
||||
}
|
||||
// reject account address that are not in the MINTER Group
|
||||
else if( !mintingGroupMemberAddresses.contains(rewardShareData.getMinter())) {
|
||||
LOGGER.trace(() -> String.format("Rejecting online reward-share that is not in MINTER Group, account %s", rewardShareData.getMinter()));
|
||||
return false;
|
||||
}
|
||||
|
||||
Account mintingAccount = new Account(repository, rewardShareData.getMinter());
|
||||
if (!mintingAccount.canMint()) {
|
||||
if (!mintingAccount.canMint(true)) { // group validation is a few lines above
|
||||
// Minting-account component of reward-share can no longer mint - disregard
|
||||
LOGGER.trace(() -> String.format("Rejecting online reward-share with non-minting account %s", mintingAccount.getAddress()));
|
||||
return false;
|
||||
@@ -530,7 +551,7 @@ public class OnlineAccountsManager {
|
||||
}
|
||||
|
||||
Account mintingAccount = new Account(repository, rewardShareData.getMinter());
|
||||
if (!mintingAccount.canMint()) {
|
||||
if (!mintingAccount.canMint(true)) {
|
||||
// Minting-account component of reward-share can no longer mint - disregard
|
||||
iterator.remove();
|
||||
continue;
|
||||
|
@@ -4,10 +4,15 @@ import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.qortal.network.Network;
|
||||
import org.qortal.network.Peer;
|
||||
import org.qortal.utils.DaemonThreadFactory;
|
||||
import org.qortal.utils.ExecuteProduceConsume.Task;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
public class PeerConnectTask implements Task {
|
||||
private static final Logger LOGGER = LogManager.getLogger(PeerConnectTask.class);
|
||||
private static final ExecutorService connectionExecutor = Executors.newCachedThreadPool(new DaemonThreadFactory(8));
|
||||
|
||||
private final Peer peer;
|
||||
private final String name;
|
||||
@@ -24,6 +29,24 @@ public class PeerConnectTask implements Task {
|
||||
|
||||
@Override
|
||||
public void perform() throws InterruptedException {
|
||||
Network.getInstance().connectPeer(peer);
|
||||
// Submit connection task to a dedicated thread pool for non-blocking I/O
|
||||
connectionExecutor.submit(() -> {
|
||||
try {
|
||||
connectPeerAsync(peer);
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.error("Connection attempt interrupted for peer {}", peer, e);
|
||||
Thread.currentThread().interrupt(); // Reset interrupt flag
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void connectPeerAsync(Peer peer) throws InterruptedException {
|
||||
// Perform peer connection in a separate thread to avoid blocking main task execution
|
||||
try {
|
||||
Network.getInstance().connectPeer(peer);
|
||||
LOGGER.trace("Successfully connected to peer {}", peer);
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Error connecting to peer {}", peer, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -454,40 +454,41 @@ public class HSQLDBDatabaseUpdates {
|
||||
|
||||
case 12:
|
||||
// Groups
|
||||
stmt.execute("CREATE TABLE Groups (group_id GroupID, owner QortalAddress NOT NULL, group_name GroupName NOT NULL, "
|
||||
// NOTE: We need to set Groups to `Groups` here to avoid SQL Standard Keywords in HSQLDB v2.7.4
|
||||
stmt.execute("CREATE TABLE `Groups` (group_id GroupID, owner QortalAddress NOT NULL, group_name GroupName NOT NULL, "
|
||||
+ "created_when EpochMillis NOT NULL, updated_when EpochMillis, is_open BOOLEAN NOT NULL, "
|
||||
+ "approval_threshold TINYINT NOT NULL, min_block_delay INTEGER NOT NULL, max_block_delay INTEGER NOT NULL, "
|
||||
+ "reference Signature, creation_group_id GroupID, reduced_group_name GroupName NOT NULL, "
|
||||
+ "description GenericDescription NOT NULL, PRIMARY KEY (group_id))");
|
||||
// For finding groups by name
|
||||
stmt.execute("CREATE INDEX GroupNameIndex on Groups (group_name)");
|
||||
stmt.execute("CREATE INDEX GroupNameIndex on `Groups` (group_name)");
|
||||
// For finding groups by reduced name
|
||||
stmt.execute("CREATE INDEX GroupReducedNameIndex on Groups (reduced_group_name)");
|
||||
stmt.execute("CREATE INDEX GroupReducedNameIndex on `Groups` (reduced_group_name)");
|
||||
// For finding groups by owner
|
||||
stmt.execute("CREATE INDEX GroupOwnerIndex ON Groups (owner)");
|
||||
stmt.execute("CREATE INDEX GroupOwnerIndex ON `Groups` (owner)");
|
||||
|
||||
// We need a corresponding trigger to make sure new group_id values are assigned sequentially starting from 1
|
||||
stmt.execute("CREATE TRIGGER Group_ID_Trigger BEFORE INSERT ON Groups "
|
||||
stmt.execute("CREATE TRIGGER Group_ID_Trigger BEFORE INSERT ON `Groups` "
|
||||
+ "REFERENCING NEW ROW AS new_row FOR EACH ROW WHEN (new_row.group_id IS NULL) "
|
||||
+ "SET new_row.group_id = (SELECT IFNULL(MAX(group_id) + 1, 1) FROM Groups)");
|
||||
+ "SET new_row.group_id = (SELECT IFNULL(MAX(group_id) + 1, 1) FROM `Groups`)");
|
||||
|
||||
// Admins
|
||||
stmt.execute("CREATE TABLE GroupAdmins (group_id GroupID, admin QortalAddress, reference Signature NOT NULL, "
|
||||
+ "PRIMARY KEY (group_id, admin), FOREIGN KEY (group_id) REFERENCES Groups (group_id) ON DELETE CASCADE)");
|
||||
+ "PRIMARY KEY (group_id, admin), FOREIGN KEY (group_id) REFERENCES `Groups` (group_id) ON DELETE CASCADE)");
|
||||
// For finding groups by admin address
|
||||
stmt.execute("CREATE INDEX GroupAdminIndex ON GroupAdmins (admin)");
|
||||
|
||||
// Members
|
||||
stmt.execute("CREATE TABLE GroupMembers (group_id GroupID, address QortalAddress, "
|
||||
+ "joined_when EpochMillis NOT NULL, reference Signature NOT NULL, "
|
||||
+ "PRIMARY KEY (group_id, address), FOREIGN KEY (group_id) REFERENCES Groups (group_id) ON DELETE CASCADE)");
|
||||
+ "PRIMARY KEY (group_id, address), FOREIGN KEY (group_id) REFERENCES `Groups` (group_id) ON DELETE CASCADE)");
|
||||
// For finding groups by member address
|
||||
stmt.execute("CREATE INDEX GroupMemberIndex ON GroupMembers (address)");
|
||||
|
||||
// Invites
|
||||
stmt.execute("CREATE TABLE GroupInvites (group_id GroupID, inviter QortalAddress, invitee QortalAddress, "
|
||||
+ "expires_when EpochMillis, reference Signature, "
|
||||
+ "PRIMARY KEY (group_id, invitee), FOREIGN KEY (group_id) REFERENCES Groups (group_id) ON DELETE CASCADE)");
|
||||
+ "PRIMARY KEY (group_id, invitee), FOREIGN KEY (group_id) REFERENCES `Groups` (group_id) ON DELETE CASCADE)");
|
||||
// For finding invites sent by inviter
|
||||
stmt.execute("CREATE INDEX GroupInviteInviterIndex ON GroupInvites (inviter)");
|
||||
// For finding invites by group
|
||||
@@ -503,7 +504,7 @@ public class HSQLDBDatabaseUpdates {
|
||||
// NULL expires_when means does not expire!
|
||||
stmt.execute("CREATE TABLE GroupBans (group_id GroupID, offender QortalAddress, admin QortalAddress NOT NULL, "
|
||||
+ "banned_when EpochMillis NOT NULL, reason GenericDescription NOT NULL, expires_when EpochMillis, reference Signature NOT NULL, "
|
||||
+ "PRIMARY KEY (group_id, offender), FOREIGN KEY (group_id) REFERENCES Groups (group_id) ON DELETE CASCADE)");
|
||||
+ "PRIMARY KEY (group_id, offender), FOREIGN KEY (group_id) REFERENCES `Groups` (group_id) ON DELETE CASCADE)");
|
||||
// For expiry maintenance
|
||||
stmt.execute("CREATE INDEX GroupBanExpiryIndex ON GroupBans (expires_when)");
|
||||
break;
|
||||
|
@@ -213,7 +213,7 @@ public class Settings {
|
||||
public long recoveryModeTimeout = 9999999999999L;
|
||||
|
||||
/** Minimum peer version number required in order to sync with them */
|
||||
private String minPeerVersion = "4.6.0";
|
||||
private String minPeerVersion = "4.6.3";
|
||||
/** 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 */
|
||||
|
@@ -123,7 +123,7 @@ public class RewardShareTransaction extends Transaction {
|
||||
final boolean isCancellingSharePercent = this.rewardShareTransactionData.getSharePercent() < 0;
|
||||
|
||||
// Creator themselves needs to be allowed to mint (unless cancelling)
|
||||
if (!isCancellingSharePercent && !creator.canMint())
|
||||
if (!isCancellingSharePercent && !creator.canMint(false))
|
||||
return ValidationResult.NOT_MINTING_ACCOUNT;
|
||||
|
||||
// Qortal: special rules in play depending whether recipient is also minter
|
||||
|
@@ -29,6 +29,7 @@
|
||||
"onlineAccountSignaturesMinLifetime": 43200000,
|
||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||
"onlineAccountsModulusV2Timestamp": 1659801600000,
|
||||
"onlineAccountsModulusV3Timestamp": 1731961800000,
|
||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 1670230000000,
|
||||
"selfSponsorshipAlgoV2SnapshotTimestamp": 1708360200000,
|
||||
"selfSponsorshipAlgoV3SnapshotTimestamp": 1708432200000,
|
||||
@@ -95,6 +96,7 @@
|
||||
"transactionV6Timestamp": 9999999999999,
|
||||
"disableReferenceTimestamp": 1655222400000,
|
||||
"increaseOnlineAccountsDifficultyTimestamp": 9999999999999,
|
||||
"decreaseOnlineAccountsDifficultyTimestamp": 1731958200000,
|
||||
"onlineAccountMinterLevelValidationHeight": 1092000,
|
||||
"selfSponsorshipAlgoV1Height": 1092400,
|
||||
"selfSponsorshipAlgoV2Height": 1611200,
|
||||
@@ -109,7 +111,9 @@
|
||||
"disableRewardshareHeight": 1899100,
|
||||
"enableRewardshareHeight": 1905100,
|
||||
"onlyMintWithNameHeight": 1900300,
|
||||
"groupMemberCheckHeight": 1902700
|
||||
"removeOnlyMintWithNameHeight": 1935500,
|
||||
"groupMemberCheckHeight": 1902700,
|
||||
"fixBatchRewardHeight": 1945900
|
||||
},
|
||||
"checkpoints": [
|
||||
{ "height": 1136300, "signature": "3BbwawEF2uN8Ni5ofpJXkukoU8ctAPxYoFB7whq9pKfBnjfZcpfEJT4R95NvBDoTP8WDyWvsUvbfHbcr9qSZuYpSKZjUQTvdFf6eqznHGEwhZApWfvXu6zjGCxYCp65F4jsVYYJjkzbjmkCg5WAwN5voudngA23kMK6PpTNygapCzXt" }
|
||||
|
@@ -74,7 +74,7 @@ public class TransferPrivsTests extends Common {
|
||||
public void testAliceIntoNewAccountTransferPrivs() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
TestAccount alice = Common.getTestAccount(repository, "alice");
|
||||
assertTrue(alice.canMint());
|
||||
assertTrue(alice.canMint(false));
|
||||
|
||||
PrivateKeyAccount aliceMintingAccount = Common.getTestAccount(repository, "alice-reward-share");
|
||||
|
||||
@@ -86,8 +86,8 @@ public class TransferPrivsTests extends Common {
|
||||
|
||||
combineAccounts(repository, alice, randomAccount, aliceMintingAccount);
|
||||
|
||||
assertFalse(alice.canMint());
|
||||
assertTrue(randomAccount.canMint());
|
||||
assertFalse(alice.canMint(false));
|
||||
assertTrue(randomAccount.canMint(false));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,8 +97,8 @@ public class TransferPrivsTests extends Common {
|
||||
TestAccount alice = Common.getTestAccount(repository, "alice");
|
||||
TestAccount dilbert = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
assertTrue(alice.canMint());
|
||||
assertTrue(dilbert.canMint());
|
||||
assertTrue(alice.canMint(false));
|
||||
assertTrue(dilbert.canMint(false));
|
||||
|
||||
// Dilbert has level, Alice does not so we need Alice to mint enough blocks to bump Dilbert's level post-combine
|
||||
final int expectedPostCombineLevel = dilbert.getLevel() + 1;
|
||||
@@ -118,11 +118,11 @@ public class TransferPrivsTests extends Common {
|
||||
|
||||
// Post-combine sender checks
|
||||
checkSenderPostTransfer(postCombineAliceData);
|
||||
assertFalse(alice.canMint());
|
||||
assertFalse(alice.canMint(false));
|
||||
|
||||
// Post-combine recipient checks
|
||||
checkRecipientPostTransfer(preCombineAliceData, preCombineDilbertData, postCombineDilbertData, expectedPostCombineLevel);
|
||||
assertTrue(dilbert.canMint());
|
||||
assertTrue(dilbert.canMint(false));
|
||||
|
||||
// Orphan previous block
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
@@ -130,12 +130,12 @@ public class TransferPrivsTests extends Common {
|
||||
// Sender checks
|
||||
AccountData orphanedAliceData = repository.getAccountRepository().getAccount(alice.getAddress());
|
||||
checkAccountDataRestored("sender", preCombineAliceData, orphanedAliceData);
|
||||
assertTrue(alice.canMint());
|
||||
assertTrue(alice.canMint(false));
|
||||
|
||||
// Recipient checks
|
||||
AccountData orphanedDilbertData = repository.getAccountRepository().getAccount(dilbert.getAddress());
|
||||
checkAccountDataRestored("recipient", preCombineDilbertData, orphanedDilbertData);
|
||||
assertTrue(dilbert.canMint());
|
||||
assertTrue(dilbert.canMint(false));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,8 +145,8 @@ public class TransferPrivsTests extends Common {
|
||||
TestAccount alice = Common.getTestAccount(repository, "alice");
|
||||
TestAccount dilbert = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
assertTrue(dilbert.canMint());
|
||||
assertTrue(alice.canMint());
|
||||
assertTrue(dilbert.canMint(false));
|
||||
assertTrue(alice.canMint(false));
|
||||
|
||||
// Dilbert has level, Alice does not so we need Alice to mint enough blocks to surpass Dilbert's level post-combine
|
||||
final int expectedPostCombineLevel = dilbert.getLevel() + 1;
|
||||
@@ -166,11 +166,11 @@ public class TransferPrivsTests extends Common {
|
||||
|
||||
// Post-combine sender checks
|
||||
checkSenderPostTransfer(postCombineDilbertData);
|
||||
assertFalse(dilbert.canMint());
|
||||
assertFalse(dilbert.canMint(false));
|
||||
|
||||
// Post-combine recipient checks
|
||||
checkRecipientPostTransfer(preCombineDilbertData, preCombineAliceData, postCombineAliceData, expectedPostCombineLevel);
|
||||
assertTrue(alice.canMint());
|
||||
assertTrue(alice.canMint(false));
|
||||
|
||||
// Orphan previous block
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
@@ -178,12 +178,12 @@ public class TransferPrivsTests extends Common {
|
||||
// Sender checks
|
||||
AccountData orphanedDilbertData = repository.getAccountRepository().getAccount(dilbert.getAddress());
|
||||
checkAccountDataRestored("sender", preCombineDilbertData, orphanedDilbertData);
|
||||
assertTrue(dilbert.canMint());
|
||||
assertTrue(dilbert.canMint(false));
|
||||
|
||||
// Recipient checks
|
||||
AccountData orphanedAliceData = repository.getAccountRepository().getAccount(alice.getAddress());
|
||||
checkAccountDataRestored("recipient", preCombineAliceData, orphanedAliceData);
|
||||
assertTrue(alice.canMint());
|
||||
assertTrue(alice.canMint(false));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,8 +202,8 @@ public class TransferPrivsTests extends Common {
|
||||
TestAccount chloe = Common.getTestAccount(repository, "chloe");
|
||||
TestAccount dilbert = Common.getTestAccount(repository, "dilbert");
|
||||
|
||||
assertTrue(dilbert.canMint());
|
||||
assertFalse(chloe.canMint());
|
||||
assertTrue(dilbert.canMint(false));
|
||||
assertFalse(chloe.canMint(false));
|
||||
|
||||
// COMBINE DILBERT INTO CHLOE
|
||||
|
||||
@@ -225,16 +225,16 @@ public class TransferPrivsTests extends Common {
|
||||
|
||||
// Post-combine sender checks
|
||||
checkSenderPostTransfer(post1stCombineDilbertData);
|
||||
assertFalse(dilbert.canMint());
|
||||
assertFalse(dilbert.canMint(false));
|
||||
|
||||
// Post-combine recipient checks
|
||||
checkRecipientPostTransfer(pre1stCombineDilbertData, pre1stCombineChloeData, post1stCombineChloeData, expectedPost1stCombineLevel);
|
||||
assertTrue(chloe.canMint());
|
||||
assertTrue(chloe.canMint(false));
|
||||
|
||||
// COMBINE ALICE INTO CHLOE
|
||||
|
||||
assertTrue(alice.canMint());
|
||||
assertTrue(chloe.canMint());
|
||||
assertTrue(alice.canMint(false));
|
||||
assertTrue(chloe.canMint(false));
|
||||
|
||||
// Alice needs to mint enough blocks to surpass Chloe's level post-combine
|
||||
final int expectedPost2ndCombineLevel = chloe.getLevel() + 1;
|
||||
@@ -254,11 +254,11 @@ public class TransferPrivsTests extends Common {
|
||||
|
||||
// Post-combine sender checks
|
||||
checkSenderPostTransfer(post2ndCombineAliceData);
|
||||
assertFalse(alice.canMint());
|
||||
assertFalse(alice.canMint(false));
|
||||
|
||||
// Post-combine recipient checks
|
||||
checkRecipientPostTransfer(pre2ndCombineAliceData, pre2ndCombineChloeData, post2ndCombineChloeData, expectedPost2ndCombineLevel);
|
||||
assertTrue(chloe.canMint());
|
||||
assertTrue(chloe.canMint(false));
|
||||
|
||||
// Orphan 2nd combine
|
||||
BlockUtils.orphanLastBlock(repository);
|
||||
@@ -266,12 +266,12 @@ public class TransferPrivsTests extends Common {
|
||||
// Sender checks
|
||||
AccountData orphanedAliceData = repository.getAccountRepository().getAccount(alice.getAddress());
|
||||
checkAccountDataRestored("sender", pre2ndCombineAliceData, orphanedAliceData);
|
||||
assertTrue(alice.canMint());
|
||||
assertTrue(alice.canMint(false));
|
||||
|
||||
// Recipient checks
|
||||
AccountData orphanedChloeData = repository.getAccountRepository().getAccount(chloe.getAddress());
|
||||
checkAccountDataRestored("recipient", pre2ndCombineChloeData, orphanedChloeData);
|
||||
assertTrue(chloe.canMint());
|
||||
assertTrue(chloe.canMint(false));
|
||||
|
||||
// Orphan 1nd combine
|
||||
BlockUtils.orphanToBlock(repository, pre1stCombineBlockHeight);
|
||||
@@ -279,7 +279,7 @@ public class TransferPrivsTests extends Common {
|
||||
// Sender checks
|
||||
AccountData orphanedDilbertData = repository.getAccountRepository().getAccount(dilbert.getAddress());
|
||||
checkAccountDataRestored("sender", pre1stCombineDilbertData, orphanedDilbertData);
|
||||
assertTrue(dilbert.canMint());
|
||||
assertTrue(dilbert.canMint(false));
|
||||
|
||||
// Recipient checks
|
||||
orphanedChloeData = repository.getAccountRepository().getAccount(chloe.getAddress());
|
||||
@@ -287,7 +287,7 @@ public class TransferPrivsTests extends Common {
|
||||
|
||||
// Chloe canMint() would return true here due to Alice-Chloe reward-share minting at top of method, so undo that minting by orphaning back to block 1
|
||||
BlockUtils.orphanToBlock(repository, 1);
|
||||
assertFalse(chloe.canMint());
|
||||
assertFalse(chloe.canMint(false));
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user