mirror of
https://github.com/Qortal/qortal.git
synced 2025-03-13 19:12:33 +00:00
Add account level change due to blocks generated.
Removed ENABLE_FORGING and related. Still to do: 'orphan' version of Block.increaseAccountLevels
This commit is contained in:
parent
54d49e0f1d
commit
319bfc8d75
@ -203,24 +203,22 @@ public class Account {
|
|||||||
this.repository.getAccountRepository().setFlags(accountData);
|
this.repository.getAccountRepository().setFlags(accountData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isFounder() throws DataException {
|
public static boolean isFounder(Integer flags) {
|
||||||
Integer flags = this.getFlags();
|
|
||||||
return flags != null && (flags & FOUNDER_FLAG) != 0;
|
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 {
|
public boolean canForge() throws DataException {
|
||||||
Integer level = this.getLevel();
|
Integer level = this.getLevel();
|
||||||
return level != null && level > 0;
|
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
|
// Account level
|
||||||
|
|
||||||
public Integer getLevel() throws DataException {
|
public Integer getLevel() throws DataException {
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -36,7 +36,6 @@ import javax.ws.rs.core.MediaType;
|
|||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.core.LoggerContext;
|
import org.apache.logging.log4j.core.LoggerContext;
|
||||||
import org.apache.logging.log4j.core.appender.RollingFileAppender;
|
import org.apache.logging.log4j.core.appender.RollingFileAppender;
|
||||||
import org.qora.account.Forging;
|
|
||||||
import org.qora.account.PrivateKeyAccount;
|
import org.qora.account.PrivateKeyAccount;
|
||||||
import org.qora.api.ApiError;
|
import org.qora.api.ApiError;
|
||||||
import org.qora.api.ApiErrors;
|
import org.qora.api.ApiErrors;
|
||||||
@ -255,8 +254,8 @@ public class AdminResource {
|
|||||||
// Check seed is valid
|
// Check seed is valid
|
||||||
PrivateKeyAccount forgingAccount = new PrivateKeyAccount(repository, seed);
|
PrivateKeyAccount forgingAccount = new PrivateKeyAccount(repository, seed);
|
||||||
|
|
||||||
// Account must derive to known proxy forging public key or have minting flag set
|
// Account must derive to known proxy forging public key
|
||||||
if (!Forging.canForge(forgingAccount) && !repository.getAccountRepository().isProxyPublicKey(forgingAccount.getPublicKey()))
|
if (!repository.getAccountRepository().isProxyPublicKey(forgingAccount.getPublicKey()))
|
||||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY);
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_PRIVATE_KEY);
|
||||||
|
|
||||||
ForgingAccountData forgingAccountData = new ForgingAccountData(seed);
|
ForgingAccountData forgingAccountData = new ForgingAccountData(seed);
|
||||||
|
@ -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.ArraySchema;
|
||||||
import io.swagger.v3.oas.annotations.media.Content;
|
import io.swagger.v3.oas.annotations.media.Content;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
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.responses.ApiResponse;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
@ -15,7 +14,6 @@ import java.util.List;
|
|||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.POST;
|
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.PathParam;
|
import javax.ws.rs.PathParam;
|
||||||
import javax.ws.rs.QueryParam;
|
import javax.ws.rs.QueryParam;
|
||||||
@ -30,16 +28,10 @@ import org.qora.api.model.BlockForgerSummary;
|
|||||||
import org.qora.crypto.Crypto;
|
import org.qora.crypto.Crypto;
|
||||||
import org.qora.data.account.AccountData;
|
import org.qora.data.account.AccountData;
|
||||||
import org.qora.data.block.BlockData;
|
import org.qora.data.block.BlockData;
|
||||||
import org.qora.data.transaction.EnableForgingTransactionData;
|
|
||||||
import org.qora.data.transaction.TransactionData;
|
import org.qora.data.transaction.TransactionData;
|
||||||
import org.qora.repository.DataException;
|
import org.qora.repository.DataException;
|
||||||
import org.qora.repository.Repository;
|
import org.qora.repository.Repository;
|
||||||
import org.qora.repository.RepositoryManager;
|
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;
|
import org.qora.utils.Base58;
|
||||||
|
|
||||||
@Path("/blocks")
|
@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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -11,22 +11,23 @@ import java.util.Arrays;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.qora.account.Account;
|
import org.qora.account.Account;
|
||||||
import org.qora.account.Forging;
|
|
||||||
import org.qora.account.PrivateKeyAccount;
|
import org.qora.account.PrivateKeyAccount;
|
||||||
import org.qora.account.PublicKeyAccount;
|
import org.qora.account.PublicKeyAccount;
|
||||||
import org.qora.asset.Asset;
|
import org.qora.asset.Asset;
|
||||||
import org.qora.at.AT;
|
import org.qora.at.AT;
|
||||||
import org.qora.block.BlockChain;
|
|
||||||
import org.qora.block.BlockChain.BlockTimingByHeight;
|
import org.qora.block.BlockChain.BlockTimingByHeight;
|
||||||
import org.qora.block.BlockChain.ShareByLevel;
|
import org.qora.block.BlockChain.ShareByLevel;
|
||||||
import org.qora.controller.Controller;
|
import org.qora.controller.Controller;
|
||||||
import org.qora.crypto.Crypto;
|
import org.qora.crypto.Crypto;
|
||||||
import org.qora.data.account.AccountBalanceData;
|
import org.qora.data.account.AccountBalanceData;
|
||||||
|
import org.qora.data.account.AccountData;
|
||||||
import org.qora.data.account.ProxyForgerData;
|
import org.qora.data.account.ProxyForgerData;
|
||||||
import org.qora.data.at.ATData;
|
import org.qora.data.at.ATData;
|
||||||
import org.qora.data.at.ATStateData;
|
import org.qora.data.at.ATStateData;
|
||||||
@ -122,8 +123,77 @@ public class Block {
|
|||||||
/** Locally-generated AT fees */
|
/** Locally-generated AT fees */
|
||||||
protected BigDecimal ourAtFees; // Generated locally
|
protected BigDecimal ourAtFees; // Generated locally
|
||||||
|
|
||||||
/** Minimum Qora balance for use in calculations. */
|
/** Lazy-instantiated expanded info on block's online accounts. */
|
||||||
public static final BigDecimal MIN_BALANCE = BigDecimal.valueOf(1L).setScale(8);
|
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<ShareByLevel> 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<ExpandedAccount> cachedExpandedAccounts = null;
|
||||||
|
|
||||||
// Other useful constants
|
// Other useful constants
|
||||||
|
|
||||||
@ -440,6 +510,31 @@ public class Block {
|
|||||||
return this.atStates;
|
return this.atStates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return expanded info on block's online accounts.
|
||||||
|
* <p>
|
||||||
|
* @throws DataException
|
||||||
|
*/
|
||||||
|
public List<ExpandedAccount> getExpandedAccounts() throws DataException {
|
||||||
|
if (this.cachedExpandedAccounts != null)
|
||||||
|
return this.cachedExpandedAccounts;
|
||||||
|
|
||||||
|
ConciseSet accountIndexes = BlockTransformer.decodeOnlineAccounts(this.blockData.getEncodedOnlineAccounts());
|
||||||
|
List<ExpandedAccount> expandedAccounts = new ArrayList<ExpandedAccount>();
|
||||||
|
|
||||||
|
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
|
// Navigation
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1032,21 +1127,13 @@ public class Block {
|
|||||||
|
|
||||||
/** Returns whether block's generator is actually allowed to forge this block. */
|
/** Returns whether block's generator is actually allowed to forge this block. */
|
||||||
protected boolean isGeneratorValidToForge(Block parentBlock) throws DataException {
|
protected boolean isGeneratorValidToForge(Block parentBlock) throws DataException {
|
||||||
// Generator must have forging flag enabled
|
// Block's generator public key must be known proxy forging public key
|
||||||
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
|
|
||||||
ProxyForgerData proxyForgerData = this.repository.getAccountRepository().getProxyForgeData(this.blockData.getGeneratorPublicKey());
|
ProxyForgerData proxyForgerData = this.repository.getAccountRepository().getProxyForgeData(this.blockData.getGeneratorPublicKey());
|
||||||
if (proxyForgerData != null) {
|
if (proxyForgerData == null)
|
||||||
Account forger = new PublicKeyAccount(this.repository, proxyForgerData.getForgerPublicKey());
|
|
||||||
|
|
||||||
if (Forging.canForge(forger))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
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();
|
int blockchainHeight = this.repository.getBlockRepository().getBlockchainHeight();
|
||||||
this.blockData.setHeight(blockchainHeight + 1);
|
this.blockData.setHeight(blockchainHeight + 1);
|
||||||
|
|
||||||
|
if (this.blockData.getHeight() > 1) {
|
||||||
// Increase account levels
|
// Increase account levels
|
||||||
increaseAccountLevels();
|
increaseAccountLevels();
|
||||||
|
|
||||||
// Block rewards go before transactions processed
|
// Block rewards go before transactions processed
|
||||||
processBlockRewards();
|
processBlockRewards();
|
||||||
|
}
|
||||||
|
|
||||||
// Process transactions (we'll link them to this block after saving the block itself)
|
// Process transactions (we'll link them to this block after saving the block itself)
|
||||||
processTransactions();
|
processTransactions();
|
||||||
@ -1071,6 +1160,7 @@ public class Block {
|
|||||||
// Group-approval transactions
|
// Group-approval transactions
|
||||||
processGroupApprovalTransactions();
|
processGroupApprovalTransactions();
|
||||||
|
|
||||||
|
if (this.blockData.getHeight() > 1)
|
||||||
// Give transaction fees to generator/proxy
|
// Give transaction fees to generator/proxy
|
||||||
rewardTransactionFees();
|
rewardTransactionFees();
|
||||||
|
|
||||||
@ -1091,7 +1181,71 @@ public class Block {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void increaseAccountLevels() throws DataException {
|
protected void increaseAccountLevels() throws DataException {
|
||||||
// TODO!
|
List<Integer> 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<ExpandedAccount> 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<ExpandedAccount> expandedAccounts, int[] cumulativeBlocksByLevel,
|
||||||
|
Predicate<ExpandedAccount> isFounder, Function<ExpandedAccount, AccountData> 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<ExpandedAccount> 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 {
|
protected void processBlockRewards() throws DataException {
|
||||||
@ -1233,6 +1387,7 @@ public class Block {
|
|||||||
* @throws DataException
|
* @throws DataException
|
||||||
*/
|
*/
|
||||||
public void orphan() throws DataException {
|
public void orphan() throws DataException {
|
||||||
|
if (this.blockData.getHeight() > 1)
|
||||||
// Deduct any transaction fees from generator/proxy
|
// Deduct any transaction fees from generator/proxy
|
||||||
deductTransactionFees();
|
deductTransactionFees();
|
||||||
|
|
||||||
@ -1242,11 +1397,13 @@ public class Block {
|
|||||||
// Undo any group-approval decisions that happen at this block
|
// Undo any group-approval decisions that happen at this block
|
||||||
orphanGroupApprovalTransactions();
|
orphanGroupApprovalTransactions();
|
||||||
|
|
||||||
|
if (this.blockData.getHeight() > 1) {
|
||||||
// Block rewards removed after transactions undone
|
// Block rewards removed after transactions undone
|
||||||
orphanBlockRewards();
|
orphanBlockRewards();
|
||||||
|
|
||||||
// Decrease account levels
|
// Decrease account levels
|
||||||
decreaseAccountLevels();
|
decreaseAccountLevels();
|
||||||
|
}
|
||||||
|
|
||||||
// Return AT fees and delete AT states from repository
|
// Return AT fees and delete AT states from repository
|
||||||
orphanAtFeesAndStates();
|
orphanAtFeesAndStates();
|
||||||
@ -1354,81 +1511,8 @@ public class Block {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void distributeByAccountLevel(BigDecimal totalAmount) throws DataException {
|
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<ShareByLevel> 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<ShareByLevel> sharesByLevel = BlockChain.getInstance().getBlockSharesByLevel();
|
List<ShareByLevel> sharesByLevel = BlockChain.getInstance().getBlockSharesByLevel();
|
||||||
|
List<ExpandedAccount> expandedAccounts = this.getExpandedAccounts();
|
||||||
ConciseSet accountIndexes = BlockTransformer.decodeOnlineAccounts(this.blockData.getEncodedOnlineAccounts());
|
|
||||||
List<AccountInfo> expandedAccounts = new ArrayList<AccountInfo>();
|
|
||||||
|
|
||||||
IntIterator iterator = accountIndexes.iterator();
|
|
||||||
while (iterator.hasNext()) {
|
|
||||||
int accountIndex = iterator.next();
|
|
||||||
AccountInfo accountInfo = new AccountInfo(repository, accountIndex, sharesByLevel);
|
|
||||||
expandedAccounts.add(accountInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Distribute amount across bins
|
// Distribute amount across bins
|
||||||
BigDecimal sharedAmount = BigDecimal.ZERO;
|
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()));
|
LOGGER.trace(() -> String.format("Bin %d share of %s: %s", binIndex, totalAmount.toPlainString(), binAmount.toPlainString()));
|
||||||
|
|
||||||
// Spread across all accounts in bin
|
// Spread across all accounts in bin
|
||||||
List<AccountInfo> binnedAccounts = expandedAccounts.stream().filter(accountInfo -> !accountInfo.isFounder && accountInfo.shareBin == binIndex).collect(Collectors.toList());
|
List<ExpandedAccount> binnedAccounts = expandedAccounts.stream().filter(accountInfo -> !accountInfo.isForgerFounder && accountInfo.shareBin == binIndex).collect(Collectors.toList());
|
||||||
if (binnedAccounts.isEmpty())
|
if (binnedAccounts.isEmpty())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@ -1447,8 +1531,8 @@ public class Block {
|
|||||||
BigDecimal accountAmount = binAmount.divide(binSize, RoundingMode.DOWN);
|
BigDecimal accountAmount = binAmount.divide(binSize, RoundingMode.DOWN);
|
||||||
|
|
||||||
for (int a = 0; a < binnedAccounts.size(); ++a) {
|
for (int a = 0; a < binnedAccounts.size(); ++a) {
|
||||||
AccountInfo accountInfo = binnedAccounts.get(a);
|
ExpandedAccount expandedAccount = binnedAccounts.get(a);
|
||||||
accountInfo.distribute(accountAmount);
|
expandedAccount.distribute(accountAmount);
|
||||||
sharedAmount = sharedAmount.add(accountAmount);
|
sharedAmount = sharedAmount.add(accountAmount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1457,27 +1541,27 @@ public class Block {
|
|||||||
BigDecimal qoraHoldersAmount = BlockChain.getInstance().getQoraHoldersShare().multiply(totalAmount).setScale(8, RoundingMode.DOWN);
|
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()));
|
LOGGER.trace(() -> String.format("Legacy QORA holders share of %s: %s", totalAmount.toPlainString(), qoraHoldersAmount.toPlainString()));
|
||||||
|
|
||||||
List<AccountInfo> qoraHolderAccounts = new ArrayList<>();
|
List<ExpandedAccount> qoraHolderAccounts = new ArrayList<>();
|
||||||
BigDecimal totalQoraHeld = BigDecimal.ZERO;
|
BigDecimal totalQoraHeld = BigDecimal.ZERO;
|
||||||
for (int i = 0; i < expandedAccounts.size(); ++i) {
|
for (int i = 0; i < expandedAccounts.size(); ++i) {
|
||||||
AccountInfo accountInfo = expandedAccounts.get(i);
|
ExpandedAccount expandedAccount = expandedAccounts.get(i);
|
||||||
if (accountInfo.qoraAmount == null)
|
if (expandedAccount.forgerQoraAmount == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
qoraHolderAccounts.add(accountInfo);
|
qoraHolderAccounts.add(expandedAccount);
|
||||||
totalQoraHeld = totalQoraHeld.add(accountInfo.qoraAmount);
|
totalQoraHeld = totalQoraHeld.add(expandedAccount.forgerQoraAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
final BigDecimal finalTotalQoraHeld = totalQoraHeld;
|
final BigDecimal finalTotalQoraHeld = totalQoraHeld;
|
||||||
LOGGER.trace(() -> String.format("Total legacy QORA held: %s", finalTotalQoraHeld.toPlainString()));
|
LOGGER.trace(() -> String.format("Total legacy QORA held: %s", finalTotalQoraHeld.toPlainString()));
|
||||||
|
|
||||||
for (int h = 0; h < qoraHolderAccounts.size(); ++h) {
|
for (int h = 0; h < qoraHolderAccounts.size(); ++h) {
|
||||||
AccountInfo accountInfo = qoraHolderAccounts.get(h);
|
ExpandedAccount expandedAccount = qoraHolderAccounts.get(h);
|
||||||
final BigDecimal holderAmount = qoraHoldersAmount.multiply(totalQoraHeld).divide(accountInfo.qoraAmount, RoundingMode.DOWN);
|
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",
|
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);
|
sharedAmount = sharedAmount.add(holderAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1485,13 +1569,16 @@ public class Block {
|
|||||||
BigDecimal foundersAmount = totalAmount.subtract(sharedAmount);
|
BigDecimal foundersAmount = totalAmount.subtract(sharedAmount);
|
||||||
LOGGER.debug(String.format("Shared %s of %s, remaining %s to founders", sharedAmount.toPlainString(), totalAmount.toPlainString(), foundersAmount.toPlainString()));
|
LOGGER.debug(String.format("Shared %s of %s, remaining %s to founders", sharedAmount.toPlainString(), totalAmount.toPlainString(), foundersAmount.toPlainString()));
|
||||||
|
|
||||||
List<AccountInfo> founderAccounts = expandedAccounts.stream().filter(accountInfo -> accountInfo.isFounder).collect(Collectors.toList());
|
List<ExpandedAccount> founderAccounts = expandedAccounts.stream().filter(accountInfo -> accountInfo.isForgerFounder).collect(Collectors.toList());
|
||||||
|
if (founderAccounts.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
BigDecimal foundersCount = BigDecimal.valueOf(founderAccounts.size());
|
BigDecimal foundersCount = BigDecimal.valueOf(founderAccounts.size());
|
||||||
BigDecimal accountAmount = foundersAmount.divide(foundersCount, RoundingMode.DOWN);
|
BigDecimal accountAmount = foundersAmount.divide(foundersCount, RoundingMode.DOWN);
|
||||||
|
|
||||||
for (int a = 0; a < founderAccounts.size(); ++a) {
|
for (int a = 0; a < founderAccounts.size(); ++a) {
|
||||||
AccountInfo accountInfo = founderAccounts.get(a);
|
ExpandedAccount expandedAccount = founderAccounts.get(a);
|
||||||
accountInfo.distribute(accountAmount);
|
expandedAccount.distribute(accountAmount);
|
||||||
sharedAmount = sharedAmount.add(accountAmount);
|
sharedAmount = sharedAmount.add(accountAmount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,7 @@ public class BlockChain {
|
|||||||
// Properties
|
// Properties
|
||||||
|
|
||||||
private boolean isTestChain = false;
|
private boolean isTestChain = false;
|
||||||
|
|
||||||
/** Transaction expiry period, starting from transaction's timestamp, in milliseconds. */
|
/** Transaction expiry period, starting from transaction's timestamp, in milliseconds. */
|
||||||
private long transactionExpiryPeriod;
|
private long transactionExpiryPeriod;
|
||||||
|
|
||||||
@ -109,6 +110,14 @@ public class BlockChain {
|
|||||||
/** Share of block reward/fees to legacy QORA coin holders */
|
/** Share of block reward/fees to legacy QORA coin holders */
|
||||||
BigDecimal qoraHoldersShare;
|
BigDecimal qoraHoldersShare;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of generated blocks required to reach next level.
|
||||||
|
* <p>
|
||||||
|
* Use account's current level as index.<br>
|
||||||
|
* If account's level isn't valid as an index, then account's level is at maximum.
|
||||||
|
*/
|
||||||
|
List<Integer> blocksNeededByLevel;
|
||||||
|
|
||||||
/** Block times by block height */
|
/** Block times by block height */
|
||||||
public static class BlockTimingByHeight {
|
public static class BlockTimingByHeight {
|
||||||
public int height;
|
public int height;
|
||||||
@ -118,15 +127,6 @@ public class BlockChain {
|
|||||||
}
|
}
|
||||||
List<BlockTimingByHeight> blockTimingsByHeight;
|
List<BlockTimingByHeight> 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<ForgingTier> forgingTiers;
|
|
||||||
|
|
||||||
private int maxProxyRelationships;
|
private int maxProxyRelationships;
|
||||||
|
|
||||||
/** Minimum time to retain online account signatures (ms) for block validity checks. */
|
/** Minimum time to retain online account signatures (ms) for block validity checks. */
|
||||||
@ -287,12 +287,12 @@ public class BlockChain {
|
|||||||
return this.sharesByLevel;
|
return this.sharesByLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BigDecimal getQoraHoldersShare() {
|
public List<Integer> getBlocksNeededByLevel() {
|
||||||
return this.qoraHoldersShare;
|
return this.blocksNeededByLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ForgingTier> getForgingTiers() {
|
public BigDecimal getQoraHoldersShare() {
|
||||||
return this.forgingTiers;
|
return this.qoraHoldersShare;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getMaxProxyRelationships() {
|
public int getMaxProxyRelationships() {
|
||||||
@ -378,6 +378,9 @@ public class BlockChain {
|
|||||||
if (this.qoraHoldersShare == null)
|
if (this.qoraHoldersShare == null)
|
||||||
Settings.throwValidationError("No \"qoraHoldersShare\" entry found in blockchain config");
|
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)
|
if (this.blockTimingsByHeight == null)
|
||||||
Settings.throwValidationError("No \"blockTimingsByHeight\" entry found in blockchain config");
|
Settings.throwValidationError("No \"blockTimingsByHeight\" entry found in blockchain config");
|
||||||
|
|
||||||
|
@ -15,9 +15,9 @@ public class AccountData {
|
|||||||
protected byte[] publicKey;
|
protected byte[] publicKey;
|
||||||
protected int defaultGroupId;
|
protected int defaultGroupId;
|
||||||
protected int flags;
|
protected int flags;
|
||||||
protected String forgingEnabler;
|
|
||||||
protected int initialLevel;
|
protected int initialLevel;
|
||||||
protected int level;
|
protected int level;
|
||||||
|
protected int blocksGenerated;
|
||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
@ -25,19 +25,19 @@ public class AccountData {
|
|||||||
protected 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.address = address;
|
||||||
this.reference = reference;
|
this.reference = reference;
|
||||||
this.publicKey = publicKey;
|
this.publicKey = publicKey;
|
||||||
this.defaultGroupId = defaultGroupId;
|
this.defaultGroupId = defaultGroupId;
|
||||||
this.flags = flags;
|
this.flags = flags;
|
||||||
this.forgingEnabler = forgingEnabler;
|
|
||||||
this.initialLevel = initialLevel;
|
this.initialLevel = initialLevel;
|
||||||
this.level = level;
|
this.level = level;
|
||||||
|
this.blocksGenerated = blocksGenerated;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccountData(String address) {
|
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
|
// Getters/Setters
|
||||||
@ -78,14 +78,6 @@ public class AccountData {
|
|||||||
this.flags = flags;
|
this.flags = flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getForgingEnabler() {
|
|
||||||
return this.forgingEnabler;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setForgingEnabler(String forgingEnabler) {
|
|
||||||
this.forgingEnabler = forgingEnabler;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getInitialLevel() {
|
public int getInitialLevel() {
|
||||||
return this.initialLevel;
|
return this.initialLevel;
|
||||||
}
|
}
|
||||||
@ -102,6 +94,14 @@ public class AccountData {
|
|||||||
this.level = level;
|
this.level = level;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getBlocksGenerated() {
|
||||||
|
return this.blocksGenerated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBlocksGenerated(int blocksGenerated) {
|
||||||
|
this.blocksGenerated = blocksGenerated;
|
||||||
|
}
|
||||||
|
|
||||||
// Comparison
|
// Comparison
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -75,11 +75,11 @@ public interface AccountRepository {
|
|||||||
public void setInitialLevel(AccountData accountData) throws DataException;
|
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.
|
||||||
* <p>
|
* <p>
|
||||||
* Note: ignores other fields like last reference, default groupID.
|
* 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. */
|
/** Delete account from repository. */
|
||||||
public void delete(String address) throws DataException;
|
public void delete(String address) throws DataException;
|
||||||
|
@ -26,7 +26,7 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AccountData getAccount(String address) throws DataException {
|
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)) {
|
try (ResultSet resultSet = this.repository.checkedExecute(sql, address)) {
|
||||||
if (resultSet == null)
|
if (resultSet == null)
|
||||||
@ -36,11 +36,11 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
|||||||
byte[] publicKey = resultSet.getBytes(2);
|
byte[] publicKey = resultSet.getBytes(2);
|
||||||
int defaultGroupId = resultSet.getInt(3);
|
int defaultGroupId = resultSet.getInt(3);
|
||||||
int flags = resultSet.getInt(4);
|
int flags = resultSet.getInt(4);
|
||||||
String forgingEnabler = resultSet.getString(5);
|
int initialLevel = resultSet.getInt(5);
|
||||||
int initialLevel = resultSet.getInt(6);
|
int level = resultSet.getInt(6);
|
||||||
int level = resultSet.getInt(7);
|
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) {
|
} catch (SQLException e) {
|
||||||
throw new DataException("Unable to fetch account info from repository", e);
|
throw new DataException("Unable to fetch account info from repository", e);
|
||||||
}
|
}
|
||||||
@ -236,10 +236,10 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setForgingEnabler(AccountData accountData) throws DataException {
|
public void setBlocksGenerated(AccountData accountData) throws DataException {
|
||||||
HSQLDBSaver saveHelper = new HSQLDBSaver("Accounts");
|
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();
|
byte[] publicKey = accountData.getPublicKey();
|
||||||
if (publicKey != null)
|
if (publicKey != null)
|
||||||
@ -248,7 +248,7 @@ public class HSQLDBAccountRepository implements AccountRepository {
|
|||||||
try {
|
try {
|
||||||
saveHelper.execute(this.repository);
|
saveHelper.execute(this.repository);
|
||||||
} catch (SQLException e) {
|
} 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -799,6 +799,15 @@ public class HSQLDBDatabaseUpdates {
|
|||||||
stmt.execute("ALTER TABLE IssueAssetTransactions ADD COLUMN is_unspendable BOOLEAN NOT NULL DEFAULT FALSE BEFORE asset_id");
|
stmt.execute("ALTER TABLE IssueAssetTransactions ADD COLUMN is_unspendable BOOLEAN NOT NULL DEFAULT FALSE BEFORE asset_id");
|
||||||
break;
|
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:
|
default:
|
||||||
// nothing to do
|
// nothing to do
|
||||||
return false;
|
return false;
|
||||||
|
@ -43,8 +43,7 @@ public class HSQLDBAccountLevelTransactionRepository extends HSQLDBTransactionRe
|
|||||||
HSQLDBSaver saveHelper = new HSQLDBSaver("AccountLevelTransactions");
|
HSQLDBSaver saveHelper = new HSQLDBSaver("AccountLevelTransactions");
|
||||||
|
|
||||||
saveHelper.bind("signature", accountLevelTransactionData.getSignature()).bind("creator", accountLevelTransactionData.getCreatorPublicKey())
|
saveHelper.bind("signature", accountLevelTransactionData.getSignature()).bind("creator", accountLevelTransactionData.getCreatorPublicKey())
|
||||||
.bind("target", accountLevelTransactionData.getTarget()).bind("level", accountLevelTransactionData.getLevel())
|
.bind("target", accountLevelTransactionData.getTarget()).bind("level", accountLevelTransactionData.getLevel());
|
||||||
.bind("previous_level", accountLevelTransactionData.getPreviousLevel());
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
saveHelper.execute(this.repository);
|
saveHelper.execute(this.repository);
|
||||||
|
@ -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<Account> 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<ForgingTier> 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -6,7 +6,6 @@ import java.util.Collections;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.qora.account.Account;
|
import org.qora.account.Account;
|
||||||
import org.qora.account.Forging;
|
|
||||||
import org.qora.account.PublicKeyAccount;
|
import org.qora.account.PublicKeyAccount;
|
||||||
import org.qora.asset.Asset;
|
import org.qora.asset.Asset;
|
||||||
import org.qora.block.BlockChain;
|
import org.qora.block.BlockChain;
|
||||||
@ -86,7 +85,7 @@ public class ProxyForgingTransaction extends Transaction {
|
|||||||
PublicKeyAccount creator = getCreator();
|
PublicKeyAccount creator = getCreator();
|
||||||
|
|
||||||
// Creator themselves needs to be allowed to forge
|
// Creator themselves needs to be allowed to forge
|
||||||
if (!Forging.canForge(creator))
|
if (!creator.canForge())
|
||||||
return ValidationResult.NO_FORGING_PERMISSION;
|
return ValidationResult.NO_FORGING_PERMISSION;
|
||||||
|
|
||||||
// Check proxy public key is correct length
|
// Check proxy public key is correct length
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,13 +1,9 @@
|
|||||||
{
|
{
|
||||||
"maxBalance": "10000000",
|
|
||||||
"blockDifficultyInterval": 10,
|
|
||||||
"minBlockTime": 60,
|
|
||||||
"maxBlockTime": 300,
|
|
||||||
"blockTimestampMargin": 2000,
|
"blockTimestampMargin": 2000,
|
||||||
"transactionExpiryPeriod": 86400000,
|
"transactionExpiryPeriod": 86400000,
|
||||||
"maxBlockSize": 1048576,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "1.0",
|
"unitFee": "0.001",
|
||||||
"useBrokenMD160ForAddresses": false,
|
"useBrokenMD160ForAddresses": false,
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
"defaultGroupId": 0,
|
"defaultGroupId": 0,
|
||||||
@ -18,9 +14,9 @@
|
|||||||
"genesisInfo": {
|
"genesisInfo": {
|
||||||
"version": 4,
|
"version": 4,
|
||||||
"timestamp": "1569510000000",
|
"timestamp": "1569510000000",
|
||||||
"generatingBalance": "100000",
|
|
||||||
"transactions": [
|
"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": "QcatTpaU1UneBs3fVHo8QN6mUmuceRVzFY", "amount": "1000000" },
|
||||||
{ "type": "GENESIS", "recipient": "QcatoCyyp7dVfMtJ92sgUUPDoBJevaemRX", "amount": "1000000" },
|
{ "type": "GENESIS", "recipient": "QcatoCyyp7dVfMtJ92sgUUPDoBJevaemRX", "amount": "1000000" },
|
||||||
{ "type": "GENESIS", "recipient": "QTiga19sttbf6CLQLT83mhCSWEaCvjk8th", "amount": "1000000" },
|
{ "type": "GENESIS", "recipient": "QTiga19sttbf6CLQLT83mhCSWEaCvjk8th", "amount": "1000000" },
|
||||||
@ -39,17 +35,26 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"rewardsByHeight": [
|
"rewardsByHeight": [
|
||||||
{ "height": 1, "reward": 0 },
|
{ "height": 2, "reward": 5.0000 },
|
||||||
{ "height": 100, "reward": 100 },
|
{ "height": 259204, "reward": 4.7500 },
|
||||||
{ "height": 200, "reward": 20 },
|
{ "height": 518406, "reward": 4.5125 },
|
||||||
{ "height": 1000, "reward": 1 },
|
{ "height": 777608, "reward": 4.2869 },
|
||||||
{ "height": 2000, "reward": 0 }
|
{ "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": [
|
"sharesByLevel": [
|
||||||
{ "minBlocks": 50, "maxSubAccounts": 5 },
|
{ "levels": [ 1, 2 ], "share": 0.05 },
|
||||||
{ "minBlocks": 50, "maxSubAccounts": 1 },
|
{ "levels": [ 3, 4 ], "share": 0.10 },
|
||||||
{ "minBlocks": 0, "maxSubAccounts": 0 }
|
{ "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": [
|
"blockTimingsByHeight": [
|
||||||
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
|
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
|
||||||
],
|
],
|
||||||
|
@ -105,7 +105,7 @@ public class TransactionTests extends Common {
|
|||||||
|
|
||||||
// Create test generator account
|
// Create test generator account
|
||||||
generator = new PrivateKeyAccount(repository, generatorSeed);
|
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));
|
accountRepository.save(new AccountBalanceData(generator.getAddress(), Asset.QORT, initialGeneratorBalance));
|
||||||
|
|
||||||
// Create test sender account
|
// Create test sender account
|
||||||
@ -113,7 +113,7 @@ public class TransactionTests extends Common {
|
|||||||
|
|
||||||
// Mock account
|
// Mock account
|
||||||
reference = senderSeed;
|
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
|
// Mock balance
|
||||||
accountRepository.save(new AccountBalanceData(sender.getAddress(), Asset.QORT, initialSenderBalance));
|
accountRepository.save(new AccountBalanceData(sender.getAddress(), Asset.QORT, initialSenderBalance));
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user