diff --git a/src/main/java/org/qora/account/Account.java b/src/main/java/org/qora/account/Account.java index 17c0cf37..60ea6a8f 100644 --- a/src/main/java/org/qora/account/Account.java +++ b/src/main/java/org/qora/account/Account.java @@ -203,24 +203,22 @@ public class Account { this.repository.getAccountRepository().setFlags(accountData); } - public boolean isFounder() throws DataException { - Integer flags = this.getFlags(); + public static boolean isFounder(Integer flags) { return flags != null && (flags & FOUNDER_FLAG) != 0; } - // Forging Enabler + public boolean isFounder() throws DataException { + Integer flags = this.getFlags(); + return Account.isFounder(flags); + } + + // Forging public boolean canForge() throws DataException { Integer level = this.getLevel(); return level != null && level > 0; } - public void setForgingEnabler(String address) throws DataException { - AccountData accountData = this.buildAccountData(); - accountData.setForgingEnabler(address); - this.repository.getAccountRepository().setForgingEnabler(accountData); - } - // Account level public Integer getLevel() throws DataException { diff --git a/src/main/java/org/qora/account/Forging.java b/src/main/java/org/qora/account/Forging.java deleted file mode 100644 index 4c44bdc5..00000000 --- a/src/main/java/org/qora/account/Forging.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.qora.account; - -import org.qora.block.BlockChain; -import org.qora.repository.DataException; -import org.qora.utils.BitTwiddling; - -/** Relating to whether accounts can forge. */ -public class Forging { - - /** Returns mask for account flags for forging bits. */ - public static int getForgingMask() { - return BitTwiddling.calcMask(BlockChain.getInstance().getForgingTiers().size() - 1); - } - - public static boolean canForge(Account account) throws DataException { - Integer level = account.getLevel(); - return level != null && level > 0; - } - -} diff --git a/src/main/java/org/qora/api/resource/AdminResource.java b/src/main/java/org/qora/api/resource/AdminResource.java index 4350afec..2d36202f 100644 --- a/src/main/java/org/qora/api/resource/AdminResource.java +++ b/src/main/java/org/qora/api/resource/AdminResource.java @@ -36,7 +36,6 @@ import javax.ws.rs.core.MediaType; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.appender.RollingFileAppender; -import org.qora.account.Forging; import org.qora.account.PrivateKeyAccount; import org.qora.api.ApiError; import org.qora.api.ApiErrors; @@ -255,8 +254,8 @@ public class AdminResource { // Check seed is valid PrivateKeyAccount forgingAccount = new PrivateKeyAccount(repository, seed); - // Account must derive to known proxy forging public key or have minting flag set - if (!Forging.canForge(forgingAccount) && !repository.getAccountRepository().isProxyPublicKey(forgingAccount.getPublicKey())) + // Account must derive to known proxy forging public key + if (!repository.getAccountRepository().isProxyPublicKey(forgingAccount.getPublicKey())) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY); ForgingAccountData forgingAccountData = new ForgingAccountData(seed); diff --git a/src/main/java/org/qora/api/resource/BlocksResource.java b/src/main/java/org/qora/api/resource/BlocksResource.java index 32ffe2c0..ef6d1d6b 100644 --- a/src/main/java/org/qora/api/resource/BlocksResource.java +++ b/src/main/java/org/qora/api/resource/BlocksResource.java @@ -5,7 +5,6 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.parameters.RequestBody; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; @@ -15,7 +14,6 @@ import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.GET; -import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.QueryParam; @@ -30,16 +28,10 @@ import org.qora.api.model.BlockForgerSummary; import org.qora.crypto.Crypto; import org.qora.data.account.AccountData; import org.qora.data.block.BlockData; -import org.qora.data.transaction.EnableForgingTransactionData; import org.qora.data.transaction.TransactionData; import org.qora.repository.DataException; import org.qora.repository.Repository; import org.qora.repository.RepositoryManager; -import org.qora.settings.Settings; -import org.qora.transaction.Transaction; -import org.qora.transaction.Transaction.ValidationResult; -import org.qora.transform.TransformationException; -import org.qora.transform.transaction.EnableForgingTransactionTransformer; import org.qora.utils.Base58; @Path("/blocks") @@ -506,50 +498,4 @@ public class BlocksResource { } } - @POST - @Path("/enableforging") - @Operation( - summary = "Build raw, unsigned, ENABLE_FORGING transaction", - requestBody = @RequestBody( - required = true, - content = @Content( - mediaType = MediaType.APPLICATION_JSON, - schema = @Schema( - implementation = EnableForgingTransactionData.class - ) - ) - ), - responses = { - @ApiResponse( - description = "raw, unsigned, ENABLE_FORGING transaction encoded in Base58", - content = @Content( - mediaType = MediaType.TEXT_PLAIN, - schema = @Schema( - type = "string" - ) - ) - ) - } - ) - @ApiErrors({ApiError.NON_PRODUCTION, ApiError.TRANSACTION_INVALID, ApiError.TRANSFORMATION_ERROR, ApiError.REPOSITORY_ISSUE}) - public String enableForging(EnableForgingTransactionData transactionData) { - if (Settings.getInstance().isApiRestricted()) - throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION); - - try (final Repository repository = RepositoryManager.getRepository()) { - Transaction transaction = Transaction.fromData(repository, transactionData); - - ValidationResult result = transaction.isValidUnconfirmed(); - if (result != ValidationResult.OK) - throw TransactionsResource.createTransactionInvalidException(request, result); - - byte[] bytes = EnableForgingTransactionTransformer.toBytes(transactionData); - return Base58.encode(bytes); - } catch (TransformationException e) { - throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSFORMATION_ERROR, e); - } catch (DataException e) { - throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); - } - } - } diff --git a/src/main/java/org/qora/block/Block.java b/src/main/java/org/qora/block/Block.java index 0e01bb2d..98e19bf3 100644 --- a/src/main/java/org/qora/block/Block.java +++ b/src/main/java/org/qora/block/Block.java @@ -11,22 +11,23 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Function; +import java.util.function.Predicate; import java.util.stream.Collectors; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.qora.account.Account; -import org.qora.account.Forging; import org.qora.account.PrivateKeyAccount; import org.qora.account.PublicKeyAccount; import org.qora.asset.Asset; import org.qora.at.AT; -import org.qora.block.BlockChain; import org.qora.block.BlockChain.BlockTimingByHeight; import org.qora.block.BlockChain.ShareByLevel; import org.qora.controller.Controller; import org.qora.crypto.Crypto; import org.qora.data.account.AccountBalanceData; +import org.qora.data.account.AccountData; import org.qora.data.account.ProxyForgerData; import org.qora.data.at.ATData; import org.qora.data.at.ATStateData; @@ -122,8 +123,77 @@ public class Block { /** Locally-generated AT fees */ protected BigDecimal ourAtFees; // Generated locally - /** Minimum Qora balance for use in calculations. */ - public static final BigDecimal MIN_BALANCE = BigDecimal.valueOf(1L).setScale(8); + /** Lazy-instantiated expanded info on block's online accounts. */ + class ExpandedAccount { + final ProxyForgerData proxyForgerData; + final boolean isRecipientAlsoForger; + + final Account forgerAccount; + final AccountData forgerAccountData; + final boolean isForgerFounder; + final BigDecimal forgerQoraAmount; + final int shareBin; + + final Account recipientAccount; + final AccountData recipientAccountData; + final boolean isRecipientFounder; + + ExpandedAccount(Repository repository, int accountIndex) throws DataException { + final List sharesByLevel = BlockChain.getInstance().getBlockSharesByLevel(); + + this.proxyForgerData = repository.getAccountRepository().getProxyAccountByIndex(accountIndex); + + this.forgerAccount = new PublicKeyAccount(repository, this.proxyForgerData.getForgerPublicKey()); + this.recipientAccount = new Account(repository, this.proxyForgerData.getRecipient()); + + AccountBalanceData qoraBalanceData = repository.getAccountRepository().getBalance(this.forgerAccount.getAddress(), Asset.LEGACY_QORA); + if (qoraBalanceData != null && qoraBalanceData.getBalance() != null && qoraBalanceData.getBalance().compareTo(BigDecimal.ZERO) > 0) + this.forgerQoraAmount = qoraBalanceData.getBalance(); + else + this.forgerQoraAmount = null; + + this.forgerAccountData = repository.getAccountRepository().getAccount(this.forgerAccount.getAddress()); + this.isForgerFounder = Account.isFounder(forgerAccountData.getFlags()); + + int currentShareBin = -1; + + if (!this.isForgerFounder) + for (int s = 0; s < sharesByLevel.size(); ++s) + if (sharesByLevel.get(s).levels.contains(this.forgerAccountData.getLevel())) { + currentShareBin = s; + break; + } + + this.shareBin = currentShareBin; + + this.recipientAccountData = repository.getAccountRepository().getAccount(this.recipientAccount.getAddress()); + this.isRecipientFounder = Account.isFounder(recipientAccountData.getFlags()); + + this.isRecipientAlsoForger = this.forgerAccountData.getAddress().equals(this.recipientAccountData.getAddress()); + } + + void distribute(BigDecimal accountAmount) throws DataException { + final BigDecimal oneHundred = BigDecimal.valueOf(100L); + + if (this.forgerAccount.getAddress().equals(this.recipientAccount.getAddress())) { + // forger & recipient the same - simpler case + LOGGER.trace(() -> String.format("Forger/recipient account %s share: %s", this.forgerAccount.getAddress(), accountAmount.toPlainString())); + this.forgerAccount.setConfirmedBalance(Asset.QORT, this.forgerAccount.getConfirmedBalance(Asset.QORT).add(accountAmount)); + } else { + // forger & recipient different - extra work needed + BigDecimal recipientAmount = accountAmount.multiply(this.proxyForgerData.getShare()).divide(oneHundred, RoundingMode.DOWN); + BigDecimal forgerAmount = accountAmount.subtract(recipientAmount); + + LOGGER.trace(() -> String.format("Forger account %s share: %s", this.forgerAccount.getAddress(), forgerAmount.toPlainString())); + this.forgerAccount.setConfirmedBalance(Asset.QORT, this.forgerAccount.getConfirmedBalance(Asset.QORT).add(forgerAmount)); + + LOGGER.trace(() -> String.format("Recipient account %s share: %s", this.recipientAccount.getAddress(), recipientAmount.toPlainString())); + this.recipientAccount.setConfirmedBalance(Asset.QORT, this.recipientAccount.getConfirmedBalance(Asset.QORT).add(recipientAmount)); + } + } + } + /** Always use getExpandedAccounts() to access this, as it's lazy-instantiated. */ + private List cachedExpandedAccounts = null; // Other useful constants @@ -440,6 +510,31 @@ public class Block { return this.atStates; } + /** + * Return expanded info on block's online accounts. + *

+ * @throws DataException + */ + public List getExpandedAccounts() throws DataException { + if (this.cachedExpandedAccounts != null) + return this.cachedExpandedAccounts; + + ConciseSet accountIndexes = BlockTransformer.decodeOnlineAccounts(this.blockData.getEncodedOnlineAccounts()); + List expandedAccounts = new ArrayList(); + + IntIterator iterator = accountIndexes.iterator(); + while (iterator.hasNext()) { + int accountIndex = iterator.next(); + + ExpandedAccount accountInfo = new ExpandedAccount(repository, accountIndex); + expandedAccounts.add(accountInfo); + } + + this.cachedExpandedAccounts = expandedAccounts; + + return this.cachedExpandedAccounts; + } + // Navigation /** @@ -1032,21 +1127,13 @@ public class Block { /** Returns whether block's generator is actually allowed to forge this block. */ protected boolean isGeneratorValidToForge(Block parentBlock) throws DataException { - // Generator must have forging flag enabled - Account generator = new PublicKeyAccount(repository, this.blockData.getGeneratorPublicKey()); - if (Forging.canForge(generator)) - return true; - - // Check whether generator public key could be a proxy forge account + // Block's generator public key must be known proxy forging public key ProxyForgerData proxyForgerData = this.repository.getAccountRepository().getProxyForgeData(this.blockData.getGeneratorPublicKey()); - if (proxyForgerData != null) { - Account forger = new PublicKeyAccount(this.repository, proxyForgerData.getForgerPublicKey()); + if (proxyForgerData == null) + return false; - if (Forging.canForge(forger)) - return true; - } - - return false; + Account forger = new PublicKeyAccount(this.repository, proxyForgerData.getForgerPublicKey()); + return forger.canForge(); } /** @@ -1059,11 +1146,13 @@ public class Block { int blockchainHeight = this.repository.getBlockRepository().getBlockchainHeight(); this.blockData.setHeight(blockchainHeight + 1); - // Increase account levels - increaseAccountLevels(); + if (this.blockData.getHeight() > 1) { + // Increase account levels + increaseAccountLevels(); - // Block rewards go before transactions processed - processBlockRewards(); + // Block rewards go before transactions processed + processBlockRewards(); + } // Process transactions (we'll link them to this block after saving the block itself) processTransactions(); @@ -1071,8 +1160,9 @@ public class Block { // Group-approval transactions processGroupApprovalTransactions(); - // Give transaction fees to generator/proxy - rewardTransactionFees(); + if (this.blockData.getHeight() > 1) + // Give transaction fees to generator/proxy + rewardTransactionFees(); // Process AT fees and save AT states into repository processAtFeesAndStates(); @@ -1091,7 +1181,71 @@ public class Block { } protected void increaseAccountLevels() throws DataException { - // TODO! + List blocksNeededByLevel = BlockChain.getInstance().getBlocksNeededByLevel(); + + // Pre-calculate cumulative blocks required for each level + int cumulativeBlocks = 0; + int[] cumulativeBlocksByLevel = new int[blocksNeededByLevel.size() + 1]; + for (int level = 0; level < cumulativeBlocksByLevel.length; ++level) { + cumulativeBlocksByLevel[level] = cumulativeBlocks; + + if (level < blocksNeededByLevel.size()) + cumulativeBlocks += blocksNeededByLevel.get(level); + } + + List expandedAccounts = this.getExpandedAccounts(); + + // We need to do this for both forgers and recipients + this.increaseAccountLevels(expandedAccounts, cumulativeBlocksByLevel, + expandedAccount -> expandedAccount.isForgerFounder, + expandedAccount -> expandedAccount.forgerAccountData); + + this.increaseAccountLevels(expandedAccounts, cumulativeBlocksByLevel, + expandedAccount -> expandedAccount.isRecipientFounder, + expandedAccount -> expandedAccount.recipientAccountData); + } + + private void increaseAccountLevels(List expandedAccounts, int[] cumulativeBlocksByLevel, + Predicate isFounder, Function getAccountData) throws DataException { + final boolean isProcessingRecipients = getAccountData.apply(expandedAccounts.get(0)) == expandedAccounts.get(0).recipientAccountData; + + // Increase blocks generated count for all accounts + for (int a = 0; a < expandedAccounts.size(); ++a) { + ExpandedAccount expandedAccount = expandedAccounts.get(a); + + // Don't increase twice if recipient is also forger. + if (isProcessingRecipients && expandedAccount.isRecipientAlsoForger) + continue; + + AccountData accountData = getAccountData.apply(expandedAccount); + + accountData.setBlocksGenerated(accountData.getBlocksGenerated() + 1); + repository.getAccountRepository().setBlocksGenerated(accountData); + LOGGER.trace(() -> String.format("Block generator %s has generated %d block%s", accountData.getAddress(), accountData.getBlocksGenerated(), (accountData.getBlocksGenerated() != 1 ? "s" : ""))); + } + + // We are only interested in accounts that are NOT founders and NOT already highest level + final int maximumLevel = cumulativeBlocksByLevel.length - 1; + List candidateAccounts = expandedAccounts.stream().filter(expandedAccount -> !isFounder.test(expandedAccount) && getAccountData.apply(expandedAccount).getLevel() < maximumLevel).collect(Collectors.toList()); + + for (int c = 0; c < candidateAccounts.size(); ++c) { + ExpandedAccount expandedAccount = candidateAccounts.get(c); + final AccountData accountData = getAccountData.apply(expandedAccount); + + final int effectiveBlocksGenerated = cumulativeBlocksByLevel[accountData.getInitialLevel()] + accountData.getBlocksGenerated(); + + for (int newLevel = cumulativeBlocksByLevel.length - 1; newLevel > 0; --newLevel) + if (effectiveBlocksGenerated >= cumulativeBlocksByLevel[newLevel]) { + if (newLevel > accountData.getLevel()) { + // Account has increased in level! + accountData.setLevel(newLevel); + repository.getAccountRepository().setLevel(accountData); + LOGGER.trace(() -> String.format("Block generator %s bumped to level %d", accountData.getAddress(), accountData.getLevel())); + } + + break; + } + } } protected void processBlockRewards() throws DataException { @@ -1233,8 +1387,9 @@ public class Block { * @throws DataException */ public void orphan() throws DataException { - // Deduct any transaction fees from generator/proxy - deductTransactionFees(); + if (this.blockData.getHeight() > 1) + // Deduct any transaction fees from generator/proxy + deductTransactionFees(); // Orphan, and unlink, transactions from this block orphanTransactionsFromBlock(); @@ -1242,11 +1397,13 @@ public class Block { // Undo any group-approval decisions that happen at this block orphanGroupApprovalTransactions(); - // Block rewards removed after transactions undone - orphanBlockRewards(); + if (this.blockData.getHeight() > 1) { + // Block rewards removed after transactions undone + orphanBlockRewards(); - // Decrease account levels - decreaseAccountLevels(); + // Decrease account levels + decreaseAccountLevels(); + } // Return AT fees and delete AT states from repository orphanAtFeesAndStates(); @@ -1354,81 +1511,8 @@ public class Block { } protected void distributeByAccountLevel(BigDecimal totalAmount) throws DataException { - class AccountInfo { - final ProxyForgerData proxyForgerData; - final Account forgerAccount; - final boolean isFounder; - final int level; - final int shareBin; - final BigDecimal qoraAmount; - final Account recipientAccount; - - AccountInfo(Repository repository, int accountIndex, List sharesByLevel) throws DataException { - this.proxyForgerData = repository.getAccountRepository().getProxyAccountByIndex(accountIndex); - - this.forgerAccount = new PublicKeyAccount(repository, this.proxyForgerData.getForgerPublicKey()); - this.recipientAccount = new Account(repository, this.proxyForgerData.getRecipient()); - - AccountBalanceData qoraBalanceData = repository.getAccountRepository().getBalance(this.forgerAccount.getAddress(), Asset.LEGACY_QORA); - if (qoraBalanceData != null && qoraBalanceData.getBalance() != null && qoraBalanceData.getBalance().compareTo(BigDecimal.ZERO) > 0) - this.qoraAmount = qoraBalanceData.getBalance(); - else - this.qoraAmount = null; - - if (this.forgerAccount.isFounder()) { - this.isFounder = true; - this.level = 0; - this.shareBin = -1; - return; - } - - this.isFounder = false; - this.level = this.forgerAccount.getLevel(); - - for (int s = 0; s < sharesByLevel.size(); ++s) - if (sharesByLevel.get(s).levels.contains(this.level)) { - this.shareBin = s; - return; - } - - this.shareBin = -1; - } - - void distribute(BigDecimal accountAmount) throws DataException { - final BigDecimal ONE_HUNDRED = BigDecimal.valueOf(100L); - - Account forgerAccount = this.forgerAccount; - Account recipientAccount = this.recipientAccount; - - if (forgerAccount.getAddress().equals(recipientAccount.getAddress())) { - // forger & recipient the same - simpler case - LOGGER.trace(() -> String.format("Forger/recipient account %s share: %s", forgerAccount.getAddress(), accountAmount.toPlainString())); - forgerAccount.setConfirmedBalance(Asset.QORT, forgerAccount.getConfirmedBalance(Asset.QORT).add(accountAmount)); - } else { - // forger & recipient different - extra work needed - BigDecimal recipientAmount = accountAmount.multiply(this.proxyForgerData.getShare()).divide(ONE_HUNDRED, RoundingMode.DOWN); - BigDecimal forgerAmount = accountAmount.subtract(recipientAmount); - - LOGGER.trace(() -> String.format("Forger account %s share: %s", forgerAccount.getAddress(), forgerAmount.toPlainString())); - forgerAccount.setConfirmedBalance(Asset.QORT, forgerAccount.getConfirmedBalance(Asset.QORT).add(forgerAmount)); - - LOGGER.trace(() -> String.format("Recipient account %s share: %s", recipientAccount.getAddress(), recipientAmount.toPlainString())); - recipientAccount.setConfirmedBalance(Asset.QORT, recipientAccount.getConfirmedBalance(Asset.QORT).add(recipientAmount)); - } - } - } - List sharesByLevel = BlockChain.getInstance().getBlockSharesByLevel(); - - ConciseSet accountIndexes = BlockTransformer.decodeOnlineAccounts(this.blockData.getEncodedOnlineAccounts()); - List expandedAccounts = new ArrayList(); - - IntIterator iterator = accountIndexes.iterator(); - while (iterator.hasNext()) { - int accountIndex = iterator.next(); - AccountInfo accountInfo = new AccountInfo(repository, accountIndex, sharesByLevel); - expandedAccounts.add(accountInfo); - } + List expandedAccounts = this.getExpandedAccounts(); // Distribute amount across bins BigDecimal sharedAmount = BigDecimal.ZERO; @@ -1439,7 +1523,7 @@ public class Block { LOGGER.trace(() -> String.format("Bin %d share of %s: %s", binIndex, totalAmount.toPlainString(), binAmount.toPlainString())); // Spread across all accounts in bin - List binnedAccounts = expandedAccounts.stream().filter(accountInfo -> !accountInfo.isFounder && accountInfo.shareBin == binIndex).collect(Collectors.toList()); + List binnedAccounts = expandedAccounts.stream().filter(accountInfo -> !accountInfo.isForgerFounder && accountInfo.shareBin == binIndex).collect(Collectors.toList()); if (binnedAccounts.isEmpty()) continue; @@ -1447,8 +1531,8 @@ public class Block { BigDecimal accountAmount = binAmount.divide(binSize, RoundingMode.DOWN); for (int a = 0; a < binnedAccounts.size(); ++a) { - AccountInfo accountInfo = binnedAccounts.get(a); - accountInfo.distribute(accountAmount); + ExpandedAccount expandedAccount = binnedAccounts.get(a); + expandedAccount.distribute(accountAmount); sharedAmount = sharedAmount.add(accountAmount); } } @@ -1457,27 +1541,27 @@ public class Block { BigDecimal qoraHoldersAmount = BlockChain.getInstance().getQoraHoldersShare().multiply(totalAmount).setScale(8, RoundingMode.DOWN); LOGGER.trace(() -> String.format("Legacy QORA holders share of %s: %s", totalAmount.toPlainString(), qoraHoldersAmount.toPlainString())); - List qoraHolderAccounts = new ArrayList<>(); + List qoraHolderAccounts = new ArrayList<>(); BigDecimal totalQoraHeld = BigDecimal.ZERO; for (int i = 0; i < expandedAccounts.size(); ++i) { - AccountInfo accountInfo = expandedAccounts.get(i); - if (accountInfo.qoraAmount == null) + ExpandedAccount expandedAccount = expandedAccounts.get(i); + if (expandedAccount.forgerQoraAmount == null) continue; - qoraHolderAccounts.add(accountInfo); - totalQoraHeld = totalQoraHeld.add(accountInfo.qoraAmount); + qoraHolderAccounts.add(expandedAccount); + totalQoraHeld = totalQoraHeld.add(expandedAccount.forgerQoraAmount); } final BigDecimal finalTotalQoraHeld = totalQoraHeld; LOGGER.trace(() -> String.format("Total legacy QORA held: %s", finalTotalQoraHeld.toPlainString())); for (int h = 0; h < qoraHolderAccounts.size(); ++h) { - AccountInfo accountInfo = qoraHolderAccounts.get(h); - final BigDecimal holderAmount = qoraHoldersAmount.multiply(totalQoraHeld).divide(accountInfo.qoraAmount, RoundingMode.DOWN); + ExpandedAccount expandedAccount = qoraHolderAccounts.get(h); + final BigDecimal holderAmount = qoraHoldersAmount.multiply(totalQoraHeld).divide(expandedAccount.forgerQoraAmount, RoundingMode.DOWN); LOGGER.trace(() -> String.format("Forger account %s has %s / %s QORA so share: %s", - accountInfo.forgerAccount.getAddress(), accountInfo.qoraAmount, finalTotalQoraHeld, holderAmount.toPlainString())); + expandedAccount.forgerAccount.getAddress(), expandedAccount.forgerQoraAmount, finalTotalQoraHeld, holderAmount.toPlainString())); - accountInfo.distribute(holderAmount); + expandedAccount.distribute(holderAmount); sharedAmount = sharedAmount.add(holderAmount); } @@ -1485,13 +1569,16 @@ public class Block { BigDecimal foundersAmount = totalAmount.subtract(sharedAmount); LOGGER.debug(String.format("Shared %s of %s, remaining %s to founders", sharedAmount.toPlainString(), totalAmount.toPlainString(), foundersAmount.toPlainString())); - List founderAccounts = expandedAccounts.stream().filter(accountInfo -> accountInfo.isFounder).collect(Collectors.toList()); + List founderAccounts = expandedAccounts.stream().filter(accountInfo -> accountInfo.isForgerFounder).collect(Collectors.toList()); + if (founderAccounts.isEmpty()) + return; + BigDecimal foundersCount = BigDecimal.valueOf(founderAccounts.size()); BigDecimal accountAmount = foundersAmount.divide(foundersCount, RoundingMode.DOWN); for (int a = 0; a < founderAccounts.size(); ++a) { - AccountInfo accountInfo = founderAccounts.get(a); - accountInfo.distribute(accountAmount); + ExpandedAccount expandedAccount = founderAccounts.get(a); + expandedAccount.distribute(accountAmount); sharedAmount = sharedAmount.add(accountAmount); } } diff --git a/src/main/java/org/qora/block/BlockChain.java b/src/main/java/org/qora/block/BlockChain.java index 3faa3dff..37739533 100644 --- a/src/main/java/org/qora/block/BlockChain.java +++ b/src/main/java/org/qora/block/BlockChain.java @@ -51,6 +51,7 @@ public class BlockChain { // Properties private boolean isTestChain = false; + /** Transaction expiry period, starting from transaction's timestamp, in milliseconds. */ private long transactionExpiryPeriod; @@ -109,6 +110,14 @@ public class BlockChain { /** Share of block reward/fees to legacy QORA coin holders */ BigDecimal qoraHoldersShare; + /** + * Number of generated blocks required to reach next level. + *

+ * Use account's current level as index.
+ * If account's level isn't valid as an index, then account's level is at maximum. + */ + List blocksNeededByLevel; + /** Block times by block height */ public static class BlockTimingByHeight { public int height; @@ -118,15 +127,6 @@ public class BlockChain { } List blockTimingsByHeight; - /** Forging right tiers */ - public static class ForgingTier { - /** Minimum number of blocks forged before account can enable minting on other accounts. */ - public int minBlocks; - /** Maximum number of other accounts that can be enabled. */ - public int maxSubAccounts; - } - List forgingTiers; - private int maxProxyRelationships; /** Minimum time to retain online account signatures (ms) for block validity checks. */ @@ -287,12 +287,12 @@ public class BlockChain { return this.sharesByLevel; } - public BigDecimal getQoraHoldersShare() { - return this.qoraHoldersShare; + public List getBlocksNeededByLevel() { + return this.blocksNeededByLevel; } - public List getForgingTiers() { - return this.forgingTiers; + public BigDecimal getQoraHoldersShare() { + return this.qoraHoldersShare; } public int getMaxProxyRelationships() { @@ -378,6 +378,9 @@ public class BlockChain { if (this.qoraHoldersShare == null) Settings.throwValidationError("No \"qoraHoldersShare\" entry found in blockchain config"); + if (this.blocksNeededByLevel == null) + Settings.throwValidationError("No \"blocksNeededByLevel\" entry found in blockchain config"); + if (this.blockTimingsByHeight == null) Settings.throwValidationError("No \"blockTimingsByHeight\" entry found in blockchain config"); diff --git a/src/main/java/org/qora/data/account/AccountData.java b/src/main/java/org/qora/data/account/AccountData.java index fd3d89d8..6098b087 100644 --- a/src/main/java/org/qora/data/account/AccountData.java +++ b/src/main/java/org/qora/data/account/AccountData.java @@ -15,9 +15,9 @@ public class AccountData { protected byte[] publicKey; protected int defaultGroupId; protected int flags; - protected String forgingEnabler; protected int initialLevel; protected int level; + protected int blocksGenerated; // Constructors @@ -25,19 +25,19 @@ public class AccountData { protected AccountData() { } - public AccountData(String address, byte[] reference, byte[] publicKey, int defaultGroupId, int flags, String forgingEnabler, int initialLevel, int level) { + public AccountData(String address, byte[] reference, byte[] publicKey, int defaultGroupId, int flags, int initialLevel, int level, int blocksGenerated) { this.address = address; this.reference = reference; this.publicKey = publicKey; this.defaultGroupId = defaultGroupId; this.flags = flags; - this.forgingEnabler = forgingEnabler; this.initialLevel = initialLevel; this.level = level; + this.blocksGenerated = blocksGenerated; } public AccountData(String address) { - this(address, null, null, Group.NO_GROUP, 0, null, 0, 0); + this(address, null, null, Group.NO_GROUP, 0, 0, 0, 0); } // Getters/Setters @@ -78,14 +78,6 @@ public class AccountData { this.flags = flags; } - public String getForgingEnabler() { - return this.forgingEnabler; - } - - public void setForgingEnabler(String forgingEnabler) { - this.forgingEnabler = forgingEnabler; - } - public int getInitialLevel() { return this.initialLevel; } @@ -102,6 +94,14 @@ public class AccountData { this.level = level; } + public int getBlocksGenerated() { + return this.blocksGenerated; + } + + public void setBlocksGenerated(int blocksGenerated) { + this.blocksGenerated = blocksGenerated; + } + // Comparison @Override diff --git a/src/main/java/org/qora/repository/AccountRepository.java b/src/main/java/org/qora/repository/AccountRepository.java index 730b5b03..fe24328d 100644 --- a/src/main/java/org/qora/repository/AccountRepository.java +++ b/src/main/java/org/qora/repository/AccountRepository.java @@ -75,11 +75,11 @@ public interface AccountRepository { public void setInitialLevel(AccountData accountData) throws DataException; /** - * Saves account's forging enabler, and public key if present, in repository. + * Saves account's generated block count and public key if present, in repository. *

* Note: ignores other fields like last reference, default groupID. */ - public void setForgingEnabler(AccountData accountData) throws DataException; + public void setBlocksGenerated(AccountData accountData) throws DataException; /** Delete account from repository. */ public void delete(String address) throws DataException; diff --git a/src/main/java/org/qora/repository/hsqldb/HSQLDBAccountRepository.java b/src/main/java/org/qora/repository/hsqldb/HSQLDBAccountRepository.java index c748541d..30991fcc 100644 --- a/src/main/java/org/qora/repository/hsqldb/HSQLDBAccountRepository.java +++ b/src/main/java/org/qora/repository/hsqldb/HSQLDBAccountRepository.java @@ -26,7 +26,7 @@ public class HSQLDBAccountRepository implements AccountRepository { @Override public AccountData getAccount(String address) throws DataException { - String sql = "SELECT reference, public_key, default_group_id, flags, forging_enabler, initial_level, level FROM Accounts WHERE account = ?"; + String sql = "SELECT reference, public_key, default_group_id, flags, initial_level, level, blocks_generated FROM Accounts WHERE account = ?"; try (ResultSet resultSet = this.repository.checkedExecute(sql, address)) { if (resultSet == null) @@ -36,11 +36,11 @@ public class HSQLDBAccountRepository implements AccountRepository { byte[] publicKey = resultSet.getBytes(2); int defaultGroupId = resultSet.getInt(3); int flags = resultSet.getInt(4); - String forgingEnabler = resultSet.getString(5); - int initialLevel = resultSet.getInt(6); - int level = resultSet.getInt(7); + int initialLevel = resultSet.getInt(5); + int level = resultSet.getInt(6); + int blocksGenerated = resultSet.getInt(7); - return new AccountData(address, reference, publicKey, defaultGroupId, flags, forgingEnabler, initialLevel, level); + return new AccountData(address, reference, publicKey, defaultGroupId, flags, initialLevel, level, blocksGenerated); } catch (SQLException e) { throw new DataException("Unable to fetch account info from repository", e); } @@ -236,10 +236,10 @@ public class HSQLDBAccountRepository implements AccountRepository { } @Override - public void setForgingEnabler(AccountData accountData) throws DataException { + public void setBlocksGenerated(AccountData accountData) throws DataException { HSQLDBSaver saveHelper = new HSQLDBSaver("Accounts"); - saveHelper.bind("account", accountData.getAddress()).bind("forging_enabler", accountData.getForgingEnabler()); + saveHelper.bind("account", accountData.getAddress()).bind("blocks_generated", accountData.getBlocksGenerated()); byte[] publicKey = accountData.getPublicKey(); if (publicKey != null) @@ -248,7 +248,7 @@ public class HSQLDBAccountRepository implements AccountRepository { try { saveHelper.execute(this.repository); } catch (SQLException e) { - throw new DataException("Unable to save account's forging enabler into repository", e); + throw new DataException("Unable to save account's generated block count into repository", e); } } diff --git a/src/main/java/org/qora/repository/hsqldb/HSQLDBDatabaseUpdates.java b/src/main/java/org/qora/repository/hsqldb/HSQLDBDatabaseUpdates.java index 61ba018c..b1013cf1 100644 --- a/src/main/java/org/qora/repository/hsqldb/HSQLDBDatabaseUpdates.java +++ b/src/main/java/org/qora/repository/hsqldb/HSQLDBDatabaseUpdates.java @@ -799,6 +799,15 @@ public class HSQLDBDatabaseUpdates { stmt.execute("ALTER TABLE IssueAssetTransactions ADD COLUMN is_unspendable BOOLEAN NOT NULL DEFAULT FALSE BEFORE asset_id"); break; + case 57: + // Modify accounts to keep track of how many blocks generated + stmt.execute("ALTER TABLE Accounts ADD COLUMN blocks_generated INT NOT NULL DEFAULT 0"); + // Remove forging_enabler + stmt.execute("ALTER TABLE Accounts DROP COLUMN forging_enabler"); + // Remove corresponding ENABLE_FORGING transaction + stmt.execute("DROP TABLE EnableForgingTransactions"); + break; + default: // nothing to do return false; diff --git a/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBAccountLevelTransactionRepository.java b/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBAccountLevelTransactionRepository.java index 29c00c23..682c01c7 100644 --- a/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBAccountLevelTransactionRepository.java +++ b/src/main/java/org/qora/repository/hsqldb/transaction/HSQLDBAccountLevelTransactionRepository.java @@ -43,8 +43,7 @@ public class HSQLDBAccountLevelTransactionRepository extends HSQLDBTransactionRe HSQLDBSaver saveHelper = new HSQLDBSaver("AccountLevelTransactions"); saveHelper.bind("signature", accountLevelTransactionData.getSignature()).bind("creator", accountLevelTransactionData.getCreatorPublicKey()) - .bind("target", accountLevelTransactionData.getTarget()).bind("level", accountLevelTransactionData.getLevel()) - .bind("previous_level", accountLevelTransactionData.getPreviousLevel()); + .bind("target", accountLevelTransactionData.getTarget()).bind("level", accountLevelTransactionData.getLevel()); try { saveHelper.execute(this.repository); diff --git a/src/main/java/org/qora/transaction/EnableForgingTransaction.java b/src/main/java/org/qora/transaction/EnableForgingTransaction.java deleted file mode 100644 index f0f96f9a..00000000 --- a/src/main/java/org/qora/transaction/EnableForgingTransaction.java +++ /dev/null @@ -1,172 +0,0 @@ -package org.qora.transaction; - -import java.math.BigDecimal; -import java.util.Collections; -import java.util.List; - -import org.qora.account.Account; -import org.qora.account.Forging; -import org.qora.account.PublicKeyAccount; -import org.qora.asset.Asset; -import org.qora.block.BlockChain; -import org.qora.block.BlockChain.ForgingTier; -import org.qora.data.transaction.EnableForgingTransactionData; -import org.qora.data.transaction.TransactionData; -import org.qora.repository.DataException; -import org.qora.repository.Repository; - -public class EnableForgingTransaction extends Transaction { - - // Properties - private EnableForgingTransactionData enableForgingTransactionData; - - // Constructors - - public EnableForgingTransaction(Repository repository, TransactionData transactionData) { - super(repository, transactionData); - - this.enableForgingTransactionData = (EnableForgingTransactionData) this.transactionData; - } - - // More information - - @Override - public List getRecipientAccounts() throws DataException { - return Collections.emptyList(); - } - - @Override - public boolean isInvolved(Account account) throws DataException { - String address = account.getAddress(); - - if (address.equals(this.getCreator().getAddress())) - return true; - - if (address.equals(this.getTarget().getAddress())) - return true; - - return false; - } - - @Override - public BigDecimal getAmount(Account account) throws DataException { - String address = account.getAddress(); - BigDecimal amount = BigDecimal.ZERO.setScale(8); - - if (address.equals(this.getCreator().getAddress())) - amount = amount.subtract(this.transactionData.getFee()); - - return amount; - } - - // Navigation - - public Account getTarget() { - return new Account(this.repository, this.enableForgingTransactionData.getTarget()); - } - - // Processing - - @Override - public ValidationResult isValid() throws DataException { - PublicKeyAccount creator = getCreator(); - - // Creator needs to have at least one forging-enabled account flag set - Integer creatorFlags = creator.getFlags(); - if (creatorFlags == null) - return ValidationResult.INVALID_ADDRESS; - - if ((creatorFlags & Forging.getForgingMask()) == 0) - return ValidationResult.NO_FORGING_PERMISSION; - - int forgingTierLevel = 0; - ForgingTier forgingTier = null; - - List forgingTiers = BlockChain.getInstance().getForgingTiers(); - for (forgingTierLevel = 0; forgingTierLevel < forgingTiers.size(); ++forgingTierLevel) - if ((creatorFlags & (1 << forgingTierLevel)) != 0) { - forgingTier = forgingTiers.get(forgingTierLevel); - break; - } - - // forgingTier should not be null at this point - if (forgingTier == null) - return ValidationResult.NO_FORGING_PERMISSION; - - // Final tier forgers can't enable further accounts - if (forgingTierLevel == forgingTiers.size() - 1) - return ValidationResult.FORGING_ENABLE_LIMIT; - - Account target = getTarget(); - - // Target needs to NOT have ANY forging-enabled account flags set - if (Forging.canForge(target)) - return ValidationResult.FORGING_ALREADY_ENABLED; - - // Has creator reached minimum requirements? - - // Already gifted maximum number of forging rights? - int numberEnabledAccounts = this.repository.getAccountRepository().countForgingAccountsEnabledByAddress(creator.getAddress()); - if (numberEnabledAccounts >= forgingTier.maxSubAccounts) - return ValidationResult.FORGING_ENABLE_LIMIT; - - // Not enough forged blocks to gift forging rights? - int numberForgedBlocks = this.repository.getBlockRepository().countForgedBlocks(creator.getPublicKey()); - if (numberForgedBlocks < forgingTier.minBlocks) - return ValidationResult.FORGE_MORE_BLOCKS; - - - // Check fee is zero or positive - if (enableForgingTransactionData.getFee().compareTo(BigDecimal.ZERO) < 0) - return ValidationResult.NEGATIVE_FEE; - - // Check creator has enough funds - if (creator.getConfirmedBalance(Asset.QORT).compareTo(enableForgingTransactionData.getFee()) < 0) - return ValidationResult.NO_BALANCE; - - return ValidationResult.OK; - } - - @Override - public void process() throws DataException { - Account creator = getCreator(); - - int creatorFlags = creator.getFlags(); - - int forgeBit = creatorFlags & Forging.getForgingMask(); - // Target's forging bit is next level from creator's - int targetForgeBit = forgeBit << 1; - - Account target = getTarget(); - Integer targetFlags = target.getFlags(); - if (targetFlags == null) - targetFlags = 0; - - targetFlags |= targetForgeBit; - - target.setFlags(targetFlags); - target.setForgingEnabler(creator.getAddress()); - } - - @Override - public void orphan() throws DataException { - // Revert - Account creator = getCreator(); - - int creatorFlags = creator.getFlags(); - - int forgeBit = creatorFlags & Forging.getForgingMask(); - // Target's forging bit is next level from creator's - int targetForgeBit = forgeBit << 1; - - Account target = getTarget(); - - int targetFlags = target.getFlags(); - - targetFlags &= ~targetForgeBit; - - target.setFlags(targetFlags); - target.setForgingEnabler(null); - } - -} diff --git a/src/main/java/org/qora/transaction/ProxyForgingTransaction.java b/src/main/java/org/qora/transaction/ProxyForgingTransaction.java index f11fe5ab..1600eb4f 100644 --- a/src/main/java/org/qora/transaction/ProxyForgingTransaction.java +++ b/src/main/java/org/qora/transaction/ProxyForgingTransaction.java @@ -6,7 +6,6 @@ import java.util.Collections; import java.util.List; import org.qora.account.Account; -import org.qora.account.Forging; import org.qora.account.PublicKeyAccount; import org.qora.asset.Asset; import org.qora.block.BlockChain; @@ -86,7 +85,7 @@ public class ProxyForgingTransaction extends Transaction { PublicKeyAccount creator = getCreator(); // Creator themselves needs to be allowed to forge - if (!Forging.canForge(creator)) + if (!creator.canForge()) return ValidationResult.NO_FORGING_PERMISSION; // Check proxy public key is correct length diff --git a/src/main/java/org/qora/transform/transaction/EnableForgingTransactionTransformer.java b/src/main/java/org/qora/transform/transaction/EnableForgingTransactionTransformer.java deleted file mode 100644 index eb00826a..00000000 --- a/src/main/java/org/qora/transform/transaction/EnableForgingTransactionTransformer.java +++ /dev/null @@ -1,86 +0,0 @@ -package org.qora.transform.transaction; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.math.BigDecimal; -import java.nio.ByteBuffer; - -import org.qora.block.BlockChain; -import org.qora.data.transaction.BaseTransactionData; -import org.qora.data.transaction.EnableForgingTransactionData; -import org.qora.data.transaction.TransactionData; -import org.qora.transaction.Transaction.TransactionType; -import org.qora.transform.TransformationException; -import org.qora.utils.Serialization; - -public class EnableForgingTransactionTransformer extends TransactionTransformer { - - // Property lengths - private static final int TARGET_LENGTH = ADDRESS_LENGTH; - - private static final int EXTRAS_LENGTH = TARGET_LENGTH; - - protected static final TransactionLayout layout; - - static { - layout = new TransactionLayout(); - layout.add("txType: " + TransactionType.GROUP_INVITE.valueString, TransformationType.INT); - layout.add("timestamp", TransformationType.TIMESTAMP); - layout.add("transaction's groupID", TransformationType.INT); - layout.add("reference", TransformationType.SIGNATURE); - layout.add("account's public key", TransformationType.PUBLIC_KEY); - layout.add("target account's address", TransformationType.ADDRESS); - layout.add("fee", TransformationType.AMOUNT); - layout.add("signature", TransformationType.SIGNATURE); - } - - public static TransactionData fromByteBuffer(ByteBuffer byteBuffer) throws TransformationException { - long timestamp = byteBuffer.getLong(); - - int txGroupId = 0; - if (timestamp >= BlockChain.getInstance().getQoraV2Timestamp()) - txGroupId = byteBuffer.getInt(); - - byte[] reference = new byte[REFERENCE_LENGTH]; - byteBuffer.get(reference); - - byte[] creatorPublicKey = Serialization.deserializePublicKey(byteBuffer); - - String target = Serialization.deserializeAddress(byteBuffer); - - BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer); - - byte[] signature = new byte[SIGNATURE_LENGTH]; - byteBuffer.get(signature); - - BaseTransactionData baseTransactionData = new BaseTransactionData(timestamp, txGroupId, reference, creatorPublicKey, fee, signature); - - return new EnableForgingTransactionData(baseTransactionData, target); - } - - public static int getDataLength(TransactionData transactionData) throws TransformationException { - return getBaseLength(transactionData) + EXTRAS_LENGTH; - } - - public static byte[] toBytes(TransactionData transactionData) throws TransformationException { - try { - EnableForgingTransactionData enableForgingTransactionData = (EnableForgingTransactionData) transactionData; - - ByteArrayOutputStream bytes = new ByteArrayOutputStream(); - - transformCommonBytes(transactionData, bytes); - - Serialization.serializeAddress(bytes, enableForgingTransactionData.getTarget()); - - Serialization.serializeBigDecimal(bytes, enableForgingTransactionData.getFee()); - - if (enableForgingTransactionData.getSignature() != null) - bytes.write(enableForgingTransactionData.getSignature()); - - return bytes.toByteArray(); - } catch (IOException | ClassCastException e) { - throw new TransformationException(e); - } - } - -} diff --git a/src/main/resources/blockchain.json b/src/main/resources/blockchain.json index 30c94617..a4de4015 100644 --- a/src/main/resources/blockchain.json +++ b/src/main/resources/blockchain.json @@ -1,13 +1,9 @@ { - "maxBalance": "10000000", - "blockDifficultyInterval": 10, - "minBlockTime": 60, - "maxBlockTime": 300, "blockTimestampMargin": 2000, "transactionExpiryPeriod": 86400000, - "maxBlockSize": 1048576, + "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, - "unitFee": "1.0", + "unitFee": "0.001", "useBrokenMD160ForAddresses": false, "requireGroupForApproval": false, "defaultGroupId": 0, @@ -18,9 +14,9 @@ "genesisInfo": { "version": 4, "timestamp": "1569510000000", - "generatingBalance": "100000", "transactions": [ - { "type": "ISSUE_ASSET", "owner": "QUwGVHPPxJNJ2dq95abQNe79EyBN2K26zM", "assetName": "QORT", "description": "QORTAL coin", "quantity": 10000000, "isDivisible": true, "fee": 0, "reference": "28u54WRcMfGujtQMZ9dNKFXVqucY7XfPihXAqPFsnx853NPUwfDJy1sMH5boCkahFgjUNYqc5fkduxdBhQTKgUsC", "data": "{}" }, + { "type": "ISSUE_ASSET", "owner": "QUwGVHPPxJNJ2dq95abQNe79EyBN2K26zM", "assetName": "QORT", "description": "QORTAL coin", "quantity": 10000000, "isDivisible": true, "reference": "28u54WRcMfGujtQMZ9dNKFXVqucY7XfPihXAqPFsnx853NPUwfDJy1sMH5boCkahFgjUNYqc5fkduxdBhQTKgUsC", "data": "{}" }, + { "type": "ISSUE_ASSET", "owner": "QUwGVHPPxJNJ2dq95abQNe79EyBN2K26zM", "assetName": "QORA", "description": "Representative legacy QORA", "quantity": 10000000000, "isDivisible": true, "data": "{}", "isUnspendable": true }, { "type": "GENESIS", "recipient": "QcatTpaU1UneBs3fVHo8QN6mUmuceRVzFY", "amount": "1000000" }, { "type": "GENESIS", "recipient": "QcatoCyyp7dVfMtJ92sgUUPDoBJevaemRX", "amount": "1000000" }, { "type": "GENESIS", "recipient": "QTiga19sttbf6CLQLT83mhCSWEaCvjk8th", "amount": "1000000" }, @@ -39,17 +35,26 @@ ] }, "rewardsByHeight": [ - { "height": 1, "reward": 0 }, - { "height": 100, "reward": 100 }, - { "height": 200, "reward": 20 }, - { "height": 1000, "reward": 1 }, - { "height": 2000, "reward": 0 } + { "height": 2, "reward": 5.0000 }, + { "height": 259204, "reward": 4.7500 }, + { "height": 518406, "reward": 4.5125 }, + { "height": 777608, "reward": 4.2869 }, + { "height": 1036810, "reward": 4.0725 }, + { "height": 1296012, "reward": 3.8689 }, + { "height": 1555214, "reward": 3.6755 }, + { "height": 1814416, "reward": 3.4917 }, + { "height": 2073618, "reward": 3.3171 }, + { "height": 2332820, "reward": 3.1512 } ], - "forgingTiers": [ - { "minBlocks": 50, "maxSubAccounts": 5 }, - { "minBlocks": 50, "maxSubAccounts": 1 }, - { "minBlocks": 0, "maxSubAccounts": 0 } + "sharesByLevel": [ + { "levels": [ 1, 2 ], "share": 0.05 }, + { "levels": [ 3, 4 ], "share": 0.10 }, + { "levels": [ 5, 6 ], "share": 0.15 }, + { "levels": [ 7, 8 ], "share": 0.20 }, + { "levels": [ 9, 10 ], "share": 0.25 } ], + "qoraHoldersShare": 0.20, + "blocksNeededByLevel": [ 7200, 64800, 129600, 172800, 244000, 345600, 518400, 691200, 864000, 1036800 ], "blockTimingsByHeight": [ { "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 } ], diff --git a/src/test/java/org/qora/test/TransactionTests.java b/src/test/java/org/qora/test/TransactionTests.java index cdc6d61f..1e9b09f9 100644 --- a/src/test/java/org/qora/test/TransactionTests.java +++ b/src/test/java/org/qora/test/TransactionTests.java @@ -105,7 +105,7 @@ public class TransactionTests extends Common { // Create test generator account generator = new PrivateKeyAccount(repository, generatorSeed); - accountRepository.setLastReference(new AccountData(generator.getAddress(), generatorSeed, generator.getPublicKey(), Group.NO_GROUP, 0, null, 0, 0)); + accountRepository.setLastReference(new AccountData(generator.getAddress(), generatorSeed, generator.getPublicKey(), Group.NO_GROUP, 0, 0, 0, 0)); accountRepository.save(new AccountBalanceData(generator.getAddress(), Asset.QORT, initialGeneratorBalance)); // Create test sender account @@ -113,7 +113,7 @@ public class TransactionTests extends Common { // Mock account reference = senderSeed; - accountRepository.setLastReference(new AccountData(sender.getAddress(), reference, sender.getPublicKey(), Group.NO_GROUP, 0, null, 0, 0)); + accountRepository.setLastReference(new AccountData(sender.getAddress(), reference, sender.getPublicKey(), Group.NO_GROUP, 0, 0, 0, 0)); // Mock balance accountRepository.save(new AccountBalanceData(sender.getAddress(), Asset.QORT, initialSenderBalance)); diff --git a/src/test/java/org/qora/test/forging/GrantForgingTests.java b/src/test/java/org/qora/test/forging/GrantForgingTests.java deleted file mode 100644 index 76b91b37..00000000 --- a/src/test/java/org/qora/test/forging/GrantForgingTests.java +++ /dev/null @@ -1,162 +0,0 @@ -package org.qora.test.forging; - -import static org.junit.Assert.*; - -import java.util.Random; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.qora.account.PrivateKeyAccount; -import org.qora.block.BlockChain; -import org.qora.block.BlockGenerator; -import org.qora.data.transaction.TransactionData; -import org.qora.repository.DataException; -import org.qora.repository.Repository; -import org.qora.repository.RepositoryManager; -import org.qora.test.common.AccountUtils; -import org.qora.test.common.Common; -import org.qora.test.common.TransactionUtils; -import org.qora.transaction.Transaction; -import org.qora.transaction.Transaction.ValidationResult; - -public class GrantForgingTests extends Common { - - @Before - public void beforeTest() throws DataException { - Common.useDefaultSettings(); - } - - @After - public void afterTest() throws DataException { - Common.orphanCheck(); - } - - @Test - public void testSimpleGrant() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount forgingAccount = Common.getTestAccount(repository, "alice"); - - TransactionData transactionData = AccountUtils.createEnableForging(repository, "alice", "bob"); - Transaction transaction = Transaction.fromData(repository, transactionData); - transaction.sign(forgingAccount); - - ValidationResult result = transaction.isValidUnconfirmed(); - // Alice can't grant without forging minimum number of blocks - assertEquals(ValidationResult.FORGE_MORE_BLOCKS, result); - - // Forge a load of blocks - int blocksNeeded = BlockChain.getInstance().getForgingTiers().get(0).minBlocks; - for (int i = 0; i < blocksNeeded; ++i) - BlockGenerator.generateTestingBlock(repository, forgingAccount); - - // Alice should be able to grant now - result = transaction.isValidUnconfirmed(); - assertEquals(ValidationResult.OK, result); - - TransactionUtils.signAndForge(repository, transactionData, forgingAccount); - } - } - - @Test - public void testMaxGrant() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount forgingAccount = Common.getTestAccount(repository, "alice"); - - TransactionData transactionData = AccountUtils.createEnableForging(repository, "alice", "bob"); - Transaction transaction = Transaction.fromData(repository, transactionData); - transaction.sign(forgingAccount); - - ValidationResult result = transaction.isValidUnconfirmed(); - // Alice can't grant without forging minimum number of blocks - assertEquals(ValidationResult.FORGE_MORE_BLOCKS, result); - - // Forge a load of blocks - int blocksNeeded = BlockChain.getInstance().getForgingTiers().get(0).minBlocks; - for (int i = 0; i < blocksNeeded; ++i) - BlockGenerator.generateTestingBlock(repository, forgingAccount); - - // Alice should be able to grant up to 5 now - - // Gift to random accounts - Random random = new Random(); - for (int i = 0; i < 5; ++i) { - byte[] publicKey = new byte[32]; - random.nextBytes(publicKey); - - transactionData = AccountUtils.createEnableForging(repository, "alice", publicKey); - transaction = Transaction.fromData(repository, transactionData); - transaction.sign(forgingAccount); - - result = transaction.isValidUnconfirmed(); - assertEquals("Couldn't enable account #" + i, ValidationResult.OK, result); - - TransactionUtils.signAndForge(repository, transactionData, forgingAccount); - } - - // Alice's allocation used up - byte[] publicKey = new byte[32]; - random.nextBytes(publicKey); - - transactionData = AccountUtils.createEnableForging(repository, "alice", publicKey); - transaction = Transaction.fromData(repository, transactionData); - transaction.sign(forgingAccount); - - result = transaction.isValidUnconfirmed(); - assertEquals(ValidationResult.FORGING_ENABLE_LIMIT, result); - } - } - - @Test - public void testFinalTier() throws DataException { - try (final Repository repository = RepositoryManager.getRepository()) { - PrivateKeyAccount aliceAccount = Common.getTestAccount(repository, "alice"); - - TransactionData transactionData = AccountUtils.createEnableForging(repository, "alice", "bob"); - Transaction transaction = Transaction.fromData(repository, transactionData); - transaction.sign(aliceAccount); - - ValidationResult result = transaction.isValidUnconfirmed(); - // Alice can't grant without forging minimum number of blocks - assertEquals(ValidationResult.FORGE_MORE_BLOCKS, result); - - // Forge a load of blocks - int blocksNeeded = BlockChain.getInstance().getForgingTiers().get(0).minBlocks; - for (int i = 0; i < blocksNeeded; ++i) - BlockGenerator.generateTestingBlock(repository, aliceAccount); - - // Alice should be able to grant now - AccountUtils.enableForging(repository, "alice", "bob"); - - // Bob can't grant without forging minimum number of blocks - transactionData = AccountUtils.createEnableForging(repository, "bob", "chloe"); - transaction = Transaction.fromData(repository, transactionData); - transaction.sign(aliceAccount); - - result = transaction.isValidUnconfirmed(); - assertEquals(ValidationResult.FORGE_MORE_BLOCKS, result); - - // Bob needs to forge a load of blocks - PrivateKeyAccount bobAccount = Common.getTestAccount(repository, "bob"); - blocksNeeded = BlockChain.getInstance().getForgingTiers().get(1).minBlocks; - for (int i = 0; i < blocksNeeded; ++i) - BlockGenerator.generateTestingBlock(repository, bobAccount); - - // Bob should be able to grant now - AccountUtils.enableForging(repository, "bob", "chloe"); - - // Chloe is final tier so shouldn't be able to grant - Random random = new Random(); - byte[] publicKey = new byte[32]; - random.nextBytes(publicKey); - - transactionData = AccountUtils.createEnableForging(repository, "chloe", publicKey); - transaction = Transaction.fromData(repository, transactionData); - transaction.sign(aliceAccount); - - result = transaction.isValidUnconfirmed(); - assertEquals(ValidationResult.FORGING_ENABLE_LIMIT, result); - } - } - -}