forked from Qortal/qortal
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);
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -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.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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<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
|
||||
|
||||
@ -440,6 +510,31 @@ public class Block {
|
||||
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
|
||||
|
||||
/**
|
||||
@ -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<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 {
|
||||
@ -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<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();
|
||||
|
||||
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);
|
||||
}
|
||||
List<ExpandedAccount> 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<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())
|
||||
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<AccountInfo> qoraHolderAccounts = new ArrayList<>();
|
||||
List<ExpandedAccount> 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<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 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);
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
* <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 */
|
||||
public static class BlockTimingByHeight {
|
||||
public int height;
|
||||
@ -118,15 +127,6 @@ public class BlockChain {
|
||||
}
|
||||
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;
|
||||
|
||||
/** 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<Integer> getBlocksNeededByLevel() {
|
||||
return this.blocksNeededByLevel;
|
||||
}
|
||||
|
||||
public List<ForgingTier> 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");
|
||||
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
* <p>
|
||||
* 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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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 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
|
||||
|
@ -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,
|
||||
"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 }
|
||||
],
|
||||
|
@ -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));
|
||||
|
@ -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