forked from Qortal/qortal
Work on granting forging rights
Move hard-coded forging tiers to blockchain config. Tests for granting forging rights. Added API call to list top block forgers. Fixed typo with Reward[s]ByHeight class name.
This commit is contained in:
parent
93230e9704
commit
d33ffee3ba
@ -21,11 +21,6 @@ public class Account {
|
|||||||
|
|
||||||
private static final Logger LOGGER = LogManager.getLogger(Account.class);
|
private static final Logger LOGGER = LogManager.getLogger(Account.class);
|
||||||
|
|
||||||
public static final int TIER1_FORGING_MASK = 0x1;
|
|
||||||
public static final int TIER2_FORGING_MASK = 0x2;
|
|
||||||
public static final int TIER3_FORGING_MASK = 0x4;
|
|
||||||
public static final int FORGING_MASK = TIER1_FORGING_MASK | TIER2_FORGING_MASK | TIER3_FORGING_MASK;
|
|
||||||
|
|
||||||
public static final int ADDRESS_LENGTH = 25;
|
public static final int ADDRESS_LENGTH = 25;
|
||||||
|
|
||||||
protected Repository repository;
|
protected Repository repository;
|
||||||
|
19
src/main/java/org/qora/account/Forging.java
Normal file
19
src/main/java/org/qora/account/Forging.java
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package org.qora.account;
|
||||||
|
|
||||||
|
import org.qora.block.BlockChain;
|
||||||
|
import org.qora.repository.DataException;
|
||||||
|
|
||||||
|
/** Relating to whether accounts can forge. */
|
||||||
|
public class Forging {
|
||||||
|
|
||||||
|
/** Returns mask for account flags for forging bits. */
|
||||||
|
public static int getForgingMask() {
|
||||||
|
return (1 << BlockChain.getInstance().getForgingTiers().size()) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean canForge(Account account) throws DataException {
|
||||||
|
Integer flags = account.getFlags();
|
||||||
|
return flags != null && (flags & getForgingMask()) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
20
src/main/java/org/qora/api/model/BlockForgeSummary.java
Normal file
20
src/main/java/org/qora/api/model/BlockForgeSummary.java
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package org.qora.api.model;
|
||||||
|
|
||||||
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
|
|
||||||
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
|
public class BlockForgeSummary {
|
||||||
|
|
||||||
|
public String address;
|
||||||
|
public int blockCount;
|
||||||
|
|
||||||
|
protected BlockForgeSummary() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public BlockForgeSummary(String address, int blockCount) {
|
||||||
|
this.address = address;
|
||||||
|
this.blockCount = blockCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -28,6 +28,7 @@ import org.qora.api.ApiError;
|
|||||||
import org.qora.api.ApiErrors;
|
import org.qora.api.ApiErrors;
|
||||||
import org.qora.api.ApiException;
|
import org.qora.api.ApiException;
|
||||||
import org.qora.api.ApiExceptionFactory;
|
import org.qora.api.ApiExceptionFactory;
|
||||||
|
import org.qora.api.model.BlockForgeSummary;
|
||||||
import org.qora.block.Block;
|
import org.qora.block.Block;
|
||||||
import org.qora.crypto.Crypto;
|
import org.qora.crypto.Crypto;
|
||||||
import org.qora.data.account.AccountData;
|
import org.qora.data.account.AccountData;
|
||||||
@ -570,6 +571,36 @@ public class BlocksResource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/forgers")
|
||||||
|
@Operation(
|
||||||
|
summary = "Show summary of block forgers",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
content = @Content(
|
||||||
|
array = @ArraySchema(
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = BlockForgeSummary.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public List<BlockForgeSummary> getBlockForgers(@Parameter(
|
||||||
|
ref = "limit"
|
||||||
|
) @QueryParam("limit") Integer limit, @Parameter(
|
||||||
|
ref = "offset"
|
||||||
|
) @QueryParam("offset") Integer offset, @Parameter(
|
||||||
|
ref = "reverse"
|
||||||
|
) @QueryParam("reverse") Boolean reverse) {
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
return repository.getBlockRepository().getBlockForgers(limit, offset, reverse);
|
||||||
|
} catch (DataException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("/enableforging")
|
@Path("/enableforging")
|
||||||
@Operation(
|
@Operation(
|
||||||
|
@ -19,7 +19,7 @@ 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.RewardsByHeight;
|
import org.qora.block.BlockChain.RewardByHeight;
|
||||||
import org.qora.crypto.Crypto;
|
import org.qora.crypto.Crypto;
|
||||||
import org.qora.data.account.ProxyForgerData;
|
import org.qora.data.account.ProxyForgerData;
|
||||||
import org.qora.data.at.ATData;
|
import org.qora.data.at.ATData;
|
||||||
@ -1191,7 +1191,7 @@ public class Block {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected BigDecimal getRewardAtHeight(int ourHeight) {
|
protected BigDecimal getRewardAtHeight(int ourHeight) {
|
||||||
List<RewardsByHeight> rewardsByHeight = BlockChain.getInstance().getBlockRewardsByHeight();
|
List<RewardByHeight> rewardsByHeight = BlockChain.getInstance().getBlockRewardsByHeight();
|
||||||
|
|
||||||
// No rewards configured?
|
// No rewards configured?
|
||||||
if (rewardsByHeight == null)
|
if (rewardsByHeight == null)
|
||||||
|
@ -90,11 +90,20 @@ public class BlockChain {
|
|||||||
private boolean oneNamePerAccount = false;
|
private boolean oneNamePerAccount = false;
|
||||||
|
|
||||||
/** Block rewards by block height */
|
/** Block rewards by block height */
|
||||||
public static class RewardsByHeight {
|
public static class RewardByHeight {
|
||||||
public int height;
|
public int height;
|
||||||
public BigDecimal reward;
|
public BigDecimal reward;
|
||||||
}
|
}
|
||||||
List<RewardsByHeight> rewardsByHeight;
|
List<RewardByHeight> rewardsByHeight;
|
||||||
|
|
||||||
|
/** 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;
|
||||||
|
|
||||||
// Constructors, etc.
|
// Constructors, etc.
|
||||||
|
|
||||||
@ -230,10 +239,14 @@ public class BlockChain {
|
|||||||
return this.oneNamePerAccount;
|
return this.oneNamePerAccount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<RewardsByHeight> getBlockRewardsByHeight() {
|
public List<RewardByHeight> getBlockRewardsByHeight() {
|
||||||
return this.rewardsByHeight;
|
return this.rewardsByHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<ForgingTier> getForgingTiers() {
|
||||||
|
return this.forgingTiers;
|
||||||
|
}
|
||||||
|
|
||||||
// Convenience methods for specific blockchain feature triggers
|
// Convenience methods for specific blockchain feature triggers
|
||||||
|
|
||||||
public long getMessageReleaseHeight() {
|
public long getMessageReleaseHeight() {
|
||||||
|
@ -30,6 +30,10 @@ public class EnableForgingTransactionData extends TransactionData {
|
|||||||
this.target = target;
|
this.target = target;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public EnableForgingTransactionData(long timestamp, int groupId, byte[] reference, byte[] creatorPublicKey, String target, BigDecimal fee) {
|
||||||
|
this(timestamp, groupId, reference, creatorPublicKey, target, fee, null);
|
||||||
|
}
|
||||||
|
|
||||||
// Getters / setters
|
// Getters / setters
|
||||||
|
|
||||||
public String getTarget() {
|
public String getTarget() {
|
||||||
|
@ -2,6 +2,7 @@ package org.qora.repository;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.qora.api.model.BlockForgeSummary;
|
||||||
import org.qora.data.block.BlockData;
|
import org.qora.data.block.BlockData;
|
||||||
import org.qora.data.block.BlockTransactionData;
|
import org.qora.data.block.BlockTransactionData;
|
||||||
import org.qora.data.transaction.TransactionData;
|
import org.qora.data.transaction.TransactionData;
|
||||||
@ -101,6 +102,11 @@ public interface BlockRepository {
|
|||||||
*/
|
*/
|
||||||
public int countForgedBlocks(byte[] publicKey) throws DataException;
|
public int countForgedBlocks(byte[] publicKey) throws DataException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns summaries of block forgers.
|
||||||
|
*/
|
||||||
|
public List<BlockForgeSummary> getBlockForgers(Integer limit, Integer offset, Boolean reverse) throws DataException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns blocks with passed generator public key.
|
* Returns blocks with passed generator public key.
|
||||||
*/
|
*/
|
||||||
|
@ -6,6 +6,8 @@ import java.sql.SQLException;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.qora.api.model.BlockForgeSummary;
|
||||||
|
import org.qora.crypto.Crypto;
|
||||||
import org.qora.data.block.BlockData;
|
import org.qora.data.block.BlockData;
|
||||||
import org.qora.data.block.BlockTransactionData;
|
import org.qora.data.block.BlockTransactionData;
|
||||||
import org.qora.data.transaction.TransactionData;
|
import org.qora.data.transaction.TransactionData;
|
||||||
@ -158,6 +160,33 @@ public class HSQLDBBlockRepository implements BlockRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<BlockForgeSummary> getBlockForgers(Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
|
String sql = "SELECT generator, COUNT(signature) FROM Blocks GROUP BY generator ORDER BY COUNT(signature) ";
|
||||||
|
if (reverse != null && reverse)
|
||||||
|
sql += " DESC";
|
||||||
|
|
||||||
|
sql += HSQLDBRepository.limitOffsetSql(limit, offset);
|
||||||
|
|
||||||
|
List<BlockForgeSummary> summaries = new ArrayList<>();
|
||||||
|
|
||||||
|
try (ResultSet resultSet = this.repository.checkedExecute(sql)) {
|
||||||
|
if (resultSet == null)
|
||||||
|
return summaries;
|
||||||
|
|
||||||
|
do {
|
||||||
|
byte[] generator = resultSet.getBytes(1);
|
||||||
|
int count = resultSet.getInt(2);
|
||||||
|
|
||||||
|
summaries.add(new BlockForgeSummary(Crypto.toAddress(generator), count));
|
||||||
|
} while (resultSet.next());
|
||||||
|
|
||||||
|
return summaries;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new DataException("Unable to fetch generator's blocks from repository", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<BlockData> getBlocksWithGenerator(byte[] generatorPublicKey, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
public List<BlockData> getBlocksWithGenerator(byte[] generatorPublicKey, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||||
String sql = "SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE generator = ? ORDER BY height ";
|
String sql = "SELECT " + BLOCK_DB_COLUMNS + " FROM Blocks WHERE generator = ? ORDER BY height ";
|
||||||
|
@ -6,8 +6,11 @@ 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.ForgingTier;
|
||||||
import org.qora.data.transaction.EnableForgingTransactionData;
|
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;
|
||||||
@ -15,12 +18,6 @@ import org.qora.repository.Repository;
|
|||||||
|
|
||||||
public class EnableForgingTransaction extends Transaction {
|
public class EnableForgingTransaction extends Transaction {
|
||||||
|
|
||||||
public static final int TIER1_MIN_FORGED_BLOCKS = 50;
|
|
||||||
public static final int TIER1_MAX_ENABLED_ACCOUNTS = 5;
|
|
||||||
|
|
||||||
public static final int TIER2_MIN_FORGED_BLOCKS = 5;
|
|
||||||
public static final int TIER2_MAX_ENABLED_ACCOUNTS = 5;
|
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
private EnableForgingTransactionData enableForgingTransactionData;
|
private EnableForgingTransactionData enableForgingTransactionData;
|
||||||
|
|
||||||
@ -80,39 +77,45 @@ public class EnableForgingTransaction extends Transaction {
|
|||||||
if (creatorFlags == null)
|
if (creatorFlags == null)
|
||||||
return ValidationResult.INVALID_ADDRESS;
|
return ValidationResult.INVALID_ADDRESS;
|
||||||
|
|
||||||
if ((creatorFlags & Account.FORGING_MASK) == 0)
|
if ((creatorFlags & Forging.getForgingMask()) == 0)
|
||||||
return ValidationResult.NO_FORGING_PERMISSION;
|
return ValidationResult.NO_FORGING_PERMISSION;
|
||||||
|
|
||||||
// Tier3 forgers can't enable further accounts
|
int forgingTierLevel = 0;
|
||||||
if ((creatorFlags & Account.TIER3_FORGING_MASK) != 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;
|
return ValidationResult.FORGING_ENABLE_LIMIT;
|
||||||
|
|
||||||
Account target = getTarget();
|
Account target = getTarget();
|
||||||
|
|
||||||
// Target needs to NOT have ANY forging-enabled account flags set
|
// Target needs to NOT have ANY forging-enabled account flags set
|
||||||
Integer targetFlags = target.getFlags();
|
if (Forging.canForge(target))
|
||||||
if (targetFlags != null && (targetFlags & Account.FORGING_MASK) != 0)
|
|
||||||
return ValidationResult.FORGING_ALREADY_ENABLED;
|
return ValidationResult.FORGING_ALREADY_ENABLED;
|
||||||
|
|
||||||
// Has creator reached minimum requirements?
|
// Has creator reached minimum requirements?
|
||||||
int numberForgedBlocks = this.repository.getBlockRepository().countForgedBlocks(creator.getPublicKey());
|
|
||||||
|
// Already gifted maximum number of forging rights?
|
||||||
int numberEnabledAccounts = this.repository.getAccountRepository().countForgingAccountsEnabledByAddress(creator.getAddress());
|
int numberEnabledAccounts = this.repository.getAccountRepository().countForgingAccountsEnabledByAddress(creator.getAddress());
|
||||||
|
if (numberEnabledAccounts >= forgingTier.maxSubAccounts)
|
||||||
|
return ValidationResult.FORGING_ENABLE_LIMIT;
|
||||||
|
|
||||||
if ((creatorFlags & Account.TIER1_FORGING_MASK) != 0) {
|
// Not enough forged blocks to gift forging rights?
|
||||||
// Tier1: minimum 2,500 forged blocks & max 50 accounts
|
int numberForgedBlocks = this.repository.getBlockRepository().countForgedBlocks(creator.getPublicKey());
|
||||||
if (numberForgedBlocks < TIER1_MIN_FORGED_BLOCKS)
|
if (numberForgedBlocks < forgingTier.minBlocks)
|
||||||
return ValidationResult.FORGE_MORE_BLOCKS;
|
return ValidationResult.FORGE_MORE_BLOCKS;
|
||||||
|
|
||||||
if (numberEnabledAccounts >= TIER1_MAX_ENABLED_ACCOUNTS)
|
|
||||||
return ValidationResult.FORGING_ENABLE_LIMIT;
|
|
||||||
} else if ((creatorFlags & Account.TIER2_FORGING_MASK) != 0) {
|
|
||||||
// Tier2: minimum 50 forged blocks & max 50 accounts
|
|
||||||
if (numberForgedBlocks < TIER2_MIN_FORGED_BLOCKS)
|
|
||||||
return ValidationResult.FORGE_MORE_BLOCKS;
|
|
||||||
|
|
||||||
if (numberEnabledAccounts >= TIER2_MAX_ENABLED_ACCOUNTS)
|
|
||||||
return ValidationResult.FORGING_ENABLE_LIMIT;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check fee is zero or positive
|
// Check fee is zero or positive
|
||||||
if (enableForgingTransactionData.getFee().compareTo(BigDecimal.ZERO) < 0)
|
if (enableForgingTransactionData.getFee().compareTo(BigDecimal.ZERO) < 0)
|
||||||
@ -135,19 +138,16 @@ public class EnableForgingTransaction extends Transaction {
|
|||||||
|
|
||||||
int creatorFlags = creator.getFlags();
|
int creatorFlags = creator.getFlags();
|
||||||
|
|
||||||
int forgeBit = 0;
|
int forgeBit = creatorFlags & Forging.getForgingMask();
|
||||||
|
// Target's forging bit is next level from creator's
|
||||||
if ((creatorFlags & Account.TIER1_FORGING_MASK) != 0)
|
int targetForgeBit = forgeBit << 1;
|
||||||
forgeBit = Account.TIER2_FORGING_MASK;
|
|
||||||
else
|
|
||||||
forgeBit = Account.TIER3_FORGING_MASK;
|
|
||||||
|
|
||||||
Account target = getTarget();
|
Account target = getTarget();
|
||||||
Integer targetFlags = target.getFlags();
|
Integer targetFlags = target.getFlags();
|
||||||
if (targetFlags == null)
|
if (targetFlags == null)
|
||||||
targetFlags = 0;
|
targetFlags = 0;
|
||||||
|
|
||||||
targetFlags |= forgeBit;
|
targetFlags |= targetForgeBit;
|
||||||
|
|
||||||
target.setFlags(targetFlags);
|
target.setFlags(targetFlags);
|
||||||
target.setForgingEnabler(creator.getAddress());
|
target.setForgingEnabler(creator.getAddress());
|
||||||
@ -169,18 +169,15 @@ public class EnableForgingTransaction extends Transaction {
|
|||||||
|
|
||||||
int creatorFlags = creator.getFlags();
|
int creatorFlags = creator.getFlags();
|
||||||
|
|
||||||
int forgeBit = 0;
|
int forgeBit = creatorFlags & Forging.getForgingMask();
|
||||||
|
// Target's forging bit is next level from creator's
|
||||||
if ((creatorFlags & Account.TIER1_FORGING_MASK) != 0)
|
int targetForgeBit = forgeBit << 1;
|
||||||
forgeBit = Account.TIER2_FORGING_MASK;
|
|
||||||
else
|
|
||||||
forgeBit = Account.TIER3_FORGING_MASK;
|
|
||||||
|
|
||||||
Account target = getTarget();
|
Account target = getTarget();
|
||||||
|
|
||||||
int targetFlags = target.getFlags();
|
int targetFlags = target.getFlags();
|
||||||
|
|
||||||
targetFlags &= ~forgeBit;
|
targetFlags &= ~targetForgeBit;
|
||||||
|
|
||||||
target.setFlags(targetFlags);
|
target.setFlags(targetFlags);
|
||||||
target.setForgingEnabler(null);
|
target.setForgingEnabler(null);
|
||||||
|
@ -6,6 +6,7 @@ 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.crypto.Crypto;
|
import org.qora.crypto.Crypto;
|
||||||
@ -84,12 +85,8 @@ public class ProxyForgingTransaction extends Transaction {
|
|||||||
|
|
||||||
PublicKeyAccount creator = getCreator();
|
PublicKeyAccount creator = getCreator();
|
||||||
|
|
||||||
// Creator needs to have at least one forging-enabled account flag set
|
// Creator themselves needs to be allowed to forge
|
||||||
Integer creatorFlags = creator.getFlags();
|
if (!Forging.canForge(creator))
|
||||||
if (creatorFlags == null)
|
|
||||||
return ValidationResult.INVALID_ADDRESS;
|
|
||||||
|
|
||||||
if ((creatorFlags & Account.FORGING_MASK) == 0)
|
|
||||||
return ValidationResult.NO_FORGING_PERMISSION;
|
return ValidationResult.NO_FORGING_PERMISSION;
|
||||||
|
|
||||||
// Check proxy public key is correct length
|
// Check proxy public key is correct length
|
||||||
|
@ -5,6 +5,8 @@ import java.util.HashMap;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.qora.account.PrivateKeyAccount;
|
import org.qora.account.PrivateKeyAccount;
|
||||||
|
import org.qora.crypto.Crypto;
|
||||||
|
import org.qora.data.transaction.EnableForgingTransactionData;
|
||||||
import org.qora.data.transaction.PaymentTransactionData;
|
import org.qora.data.transaction.PaymentTransactionData;
|
||||||
import org.qora.data.transaction.ProxyForgingTransactionData;
|
import org.qora.data.transaction.ProxyForgingTransactionData;
|
||||||
import org.qora.data.transaction.TransactionData;
|
import org.qora.data.transaction.TransactionData;
|
||||||
@ -46,6 +48,30 @@ public class AccountUtils {
|
|||||||
return proxyPrivateKey;
|
return proxyPrivateKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static TransactionData createEnableForging(Repository repository, String forger, byte[] recipientPublicKey) throws DataException {
|
||||||
|
PrivateKeyAccount forgingAccount = Common.getTestAccount(repository, forger);
|
||||||
|
|
||||||
|
byte[] reference = forgingAccount.getLastReference();
|
||||||
|
long timestamp = repository.getTransactionRepository().fromSignature(reference).getTimestamp() + 1000;
|
||||||
|
|
||||||
|
return new EnableForgingTransactionData(timestamp, txGroupId, reference, forgingAccount.getPublicKey(), Crypto.toAddress(recipientPublicKey), fee);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TransactionData createEnableForging(Repository repository, String forger, String recipient) throws DataException {
|
||||||
|
PrivateKeyAccount recipientAccount = Common.getTestAccount(repository, recipient);
|
||||||
|
|
||||||
|
return createEnableForging(repository, forger, recipientAccount.getPublicKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TransactionData enableForging(Repository repository, String forger, String recipient) throws DataException {
|
||||||
|
TransactionData transactionData = createEnableForging(repository, forger, recipient);
|
||||||
|
|
||||||
|
PrivateKeyAccount forgingAccount = Common.getTestAccount(repository, forger);
|
||||||
|
TransactionUtils.signAndForge(repository, transactionData, forgingAccount);
|
||||||
|
|
||||||
|
return transactionData;
|
||||||
|
}
|
||||||
|
|
||||||
public static Map<String, Map<Long, BigDecimal>> getBalances(Repository repository, long... assetIds) throws DataException {
|
public static Map<String, Map<Long, BigDecimal>> getBalances(Repository repository, long... assetIds) throws DataException {
|
||||||
Map<String, Map<Long, BigDecimal>> balances = new HashMap<>();
|
Map<String, Map<Long, BigDecimal>> balances = new HashMap<>();
|
||||||
|
|
||||||
|
163
src/test/java/org/qora/test/forging/GrantForgingTests.java
Normal file
163
src/test/java/org/qora/test/forging/GrantForgingTests.java
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -11,7 +11,7 @@ import org.junit.Test;
|
|||||||
import org.qora.account.PrivateKeyAccount;
|
import org.qora.account.PrivateKeyAccount;
|
||||||
import org.qora.asset.Asset;
|
import org.qora.asset.Asset;
|
||||||
import org.qora.block.BlockChain;
|
import org.qora.block.BlockChain;
|
||||||
import org.qora.block.BlockChain.RewardsByHeight;
|
import org.qora.block.BlockChain.RewardByHeight;
|
||||||
import org.qora.block.BlockGenerator;
|
import org.qora.block.BlockGenerator;
|
||||||
import org.qora.repository.DataException;
|
import org.qora.repository.DataException;
|
||||||
import org.qora.repository.Repository;
|
import org.qora.repository.Repository;
|
||||||
@ -54,11 +54,11 @@ public class RewardTests extends Common {
|
|||||||
|
|
||||||
PrivateKeyAccount forgingAccount = Common.getTestAccount(repository, "alice");
|
PrivateKeyAccount forgingAccount = Common.getTestAccount(repository, "alice");
|
||||||
|
|
||||||
List<RewardsByHeight> rewards = BlockChain.getInstance().getBlockRewardsByHeight();
|
List<RewardByHeight> rewards = BlockChain.getInstance().getBlockRewardsByHeight();
|
||||||
|
|
||||||
int rewardIndex = rewards.size() - 1;
|
int rewardIndex = rewards.size() - 1;
|
||||||
|
|
||||||
RewardsByHeight rewardInfo = rewards.get(rewardIndex);
|
RewardByHeight rewardInfo = rewards.get(rewardIndex);
|
||||||
BigDecimal expectedBalance = initialBalances.get("alice").get(Asset.QORA);
|
BigDecimal expectedBalance = initialBalances.get("alice").get(Asset.QORA);
|
||||||
|
|
||||||
for (int height = rewardInfo.height; height > 1; --height) {
|
for (int height = rewardInfo.height; height > 1; --height) {
|
||||||
|
@ -30,6 +30,11 @@
|
|||||||
{ "height": 11, "reward": 10 },
|
{ "height": 11, "reward": 10 },
|
||||||
{ "height": 21, "reward": 1 }
|
{ "height": 21, "reward": 1 }
|
||||||
],
|
],
|
||||||
|
"forgingTiers": [
|
||||||
|
{ "minBlocks": 5, "maxSubAccounts": 5 },
|
||||||
|
{ "minBlocks": 4, "maxSubAccounts": 3 },
|
||||||
|
{ "minBlocks": 0, "maxSubAccounts": 0 }
|
||||||
|
],
|
||||||
"featureTriggers": {
|
"featureTriggers": {
|
||||||
"messageHeight": 0,
|
"messageHeight": 0,
|
||||||
"atHeight": 0,
|
"atHeight": 0,
|
||||||
|
Loading…
Reference in New Issue
Block a user