mirror of
https://github.com/Qortal/qortal.git
synced 2025-05-03 16:27:50 +00:00
Higher account levels more likely to win blocks
Also: RewardShareKeys app now supports only one arg (minter private key) in self-reward-share mode, where recipient public key is derived from minter private key. Added methods to Account for returning 'effective' minting level where minting level for founders is read from blockchain config. (Or returns zero if unable to mint). Changed two Block constructors into static methods that return a new Block as there was way too much work being done to really be called a constructor, especially with all the opportunities to throw an exception too. Main blockchain config updated to reflect near-launch version. Added/changed blockchain weight tests to check block winning based on higher account levels.
This commit is contained in:
parent
ebc2ee6ea9
commit
00aee1458e
@ -11,26 +11,29 @@ import org.qora.utils.Base58;
|
|||||||
public class RewardShareKeys {
|
public class RewardShareKeys {
|
||||||
|
|
||||||
private static void usage() {
|
private static void usage() {
|
||||||
System.err.println("Usage: RewardShareKeys <private-key> <public-key>");
|
System.err.println("Usage: RewardShareKeys <minter-private-key> [<recipient-public-key>]");
|
||||||
System.err.println("Example: RewardShareKeys pYQ6DpQBJ2n72TCLJLScEvwhf3boxWy2kQEPynakwpj 6rNn9b3pYRrG9UKqzMWYZ9qa8F3Zgv2mVWrULGHUusb");
|
System.err.println("Example: RewardShareKeys pYQ6DpQBJ2n72TCLJLScEvwhf3boxWy2kQEPynakwpj 6rNn9b3pYRrG9UKqzMWYZ9qa8F3Zgv2mVWrULGHUusb");
|
||||||
|
System.err.println("Example (self-share): RewardShareKeys pYQ6DpQBJ2n72TCLJLScEvwhf3boxWy2kQEPynakwpj");
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
if (args.length != 2)
|
if (args.length < 1 || args.length > 2)
|
||||||
usage();
|
usage();
|
||||||
|
|
||||||
Security.insertProviderAt(new BouncyCastleProvider(), 0);
|
Security.insertProviderAt(new BouncyCastleProvider(), 0);
|
||||||
Security.insertProviderAt(new BouncyCastleJsseProvider(), 1);
|
Security.insertProviderAt(new BouncyCastleJsseProvider(), 1);
|
||||||
|
|
||||||
PrivateKeyAccount privateAccount = new PrivateKeyAccount(null, Base58.decode(args[0]));
|
PrivateKeyAccount minterAccount = new PrivateKeyAccount(null, Base58.decode(args[0]));
|
||||||
PublicKeyAccount publicAccount = new PublicKeyAccount(null, Base58.decode(args[1]));
|
PublicKeyAccount recipientAccount = new PublicKeyAccount(null, args.length > 1 ? Base58.decode(args[1]) : minterAccount.getPublicKey());
|
||||||
|
|
||||||
byte[] rewardSharePrivateKey = privateAccount.getRewardSharePrivateKey(publicAccount.getPublicKey());
|
byte[] rewardSharePrivateKey = minterAccount.getRewardSharePrivateKey(recipientAccount.getPublicKey());
|
||||||
byte[] rewardSharePublicKey = PrivateKeyAccount.toPublicKey(rewardSharePrivateKey);
|
byte[] rewardSharePublicKey = PrivateKeyAccount.toPublicKey(rewardSharePrivateKey);
|
||||||
|
|
||||||
System.out.println(String.format("Private key account: %s", privateAccount.getAddress()));
|
System.out.println(String.format("Minter account: %s", minterAccount.getAddress()));
|
||||||
System.out.println(String.format("Public key account: %s", publicAccount.getAddress()));
|
System.out.println(String.format("Minter's public key: %s", Base58.encode(minterAccount.getPublicKey())));
|
||||||
|
|
||||||
|
System.out.println(String.format("Recipient account: %s", recipientAccount.getAddress()));
|
||||||
|
|
||||||
System.out.println(String.format("Reward-share private key: %s", Base58.encode(rewardSharePrivateKey)));
|
System.out.println(String.format("Reward-share private key: %s", Base58.encode(rewardSharePrivateKey)));
|
||||||
System.out.println(String.format("Reward-share public key: %s", Base58.encode(rewardSharePublicKey)));
|
System.out.println(String.format("Reward-share public key: %s", Base58.encode(rewardSharePublicKey)));
|
||||||
|
@ -9,6 +9,7 @@ import org.qora.block.Block;
|
|||||||
import org.qora.block.BlockChain;
|
import org.qora.block.BlockChain;
|
||||||
import org.qora.data.account.AccountBalanceData;
|
import org.qora.data.account.AccountBalanceData;
|
||||||
import org.qora.data.account.AccountData;
|
import org.qora.data.account.AccountData;
|
||||||
|
import org.qora.data.account.RewardShareData;
|
||||||
import org.qora.data.block.BlockData;
|
import org.qora.data.block.BlockData;
|
||||||
import org.qora.data.transaction.TransactionData;
|
import org.qora.data.transaction.TransactionData;
|
||||||
import org.qora.repository.BlockRepository;
|
import org.qora.repository.BlockRepository;
|
||||||
@ -239,6 +240,7 @@ public class Account {
|
|||||||
|
|
||||||
// Account level
|
// Account level
|
||||||
|
|
||||||
|
/** Returns account's level (0+) or null if account not found in repository. */
|
||||||
public Integer getLevel() throws DataException {
|
public Integer getLevel() throws DataException {
|
||||||
return this.repository.getAccountRepository().getLevel(this.address);
|
return this.repository.getAccountRepository().getLevel(this.address);
|
||||||
}
|
}
|
||||||
@ -256,4 +258,43 @@ public class Account {
|
|||||||
this.repository.getAccountRepository().setInitialLevel(accountData);
|
this.repository.getAccountRepository().setInitialLevel(accountData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns 'effective' minting level, or zero if account does not exist/cannot mint.
|
||||||
|
* <p>
|
||||||
|
* For founder accounts, this returns "founderEffectiveMintingLevel" from blockchain config.
|
||||||
|
*
|
||||||
|
* @return 0+
|
||||||
|
* @throws DataException
|
||||||
|
*/
|
||||||
|
public int getEffectiveMintingLevel() throws DataException {
|
||||||
|
if (this.isFounder())
|
||||||
|
return BlockChain.getInstance().getFounderEffectiveMintingLevel();
|
||||||
|
|
||||||
|
Integer level = this.getLevel();
|
||||||
|
if (level == null)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns 'effective' minting level, or zero if reward-share does not exist.
|
||||||
|
* <p>
|
||||||
|
* For founder accounts, this returns "founderEffectiveMintingLevel" from blockchain config.
|
||||||
|
*
|
||||||
|
* @param repository
|
||||||
|
* @param rewardSharePublicKey
|
||||||
|
* @return 0+
|
||||||
|
* @throws DataException
|
||||||
|
*/
|
||||||
|
public static int getRewardShareEffectiveMintingLevel(Repository repository, byte[] rewardSharePublicKey) throws DataException {
|
||||||
|
// Find actual minter and get their effective minting level
|
||||||
|
RewardShareData rewardShareData = repository.getAccountRepository().getRewardShare(rewardSharePublicKey);
|
||||||
|
if (rewardShareData == null)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
PublicKeyAccount rewardShareMinter = new PublicKeyAccount(repository, rewardShareData.getMinterPublicKey());
|
||||||
|
return rewardShareMinter.getEffectiveMintingLevel();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -206,22 +206,21 @@ public class Block {
|
|||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs Block-handling object without loading transactions and AT states.
|
* Constructs new Block without loading transactions and AT states.
|
||||||
* <p>
|
* <p>
|
||||||
* Transactions and AT states are loaded on first call to getTransactions() or getATStates() respectively.
|
* Transactions and AT states are loaded on first call to getTransactions() or getATStates() respectively.
|
||||||
*
|
*
|
||||||
* @param repository
|
* @param repository
|
||||||
* @param blockData
|
* @param blockData
|
||||||
* @throws DataException
|
|
||||||
*/
|
*/
|
||||||
public Block(Repository repository, BlockData blockData) throws DataException {
|
public Block(Repository repository, BlockData blockData) {
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
this.blockData = blockData;
|
this.blockData = blockData;
|
||||||
this.minter = new PublicKeyAccount(repository, blockData.getMinterPublicKey());
|
this.minter = new PublicKeyAccount(repository, blockData.getMinterPublicKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs Block-handling object using passed transaction and AT states.
|
* Constructs new Block using passed transaction and AT states.
|
||||||
* <p>
|
* <p>
|
||||||
* This constructor typically used when receiving a serialized block over the network.
|
* This constructor typically used when receiving a serialized block over the network.
|
||||||
*
|
*
|
||||||
@ -229,9 +228,8 @@ public class Block {
|
|||||||
* @param blockData
|
* @param blockData
|
||||||
* @param transactions
|
* @param transactions
|
||||||
* @param atStates
|
* @param atStates
|
||||||
* @throws DataException
|
|
||||||
*/
|
*/
|
||||||
public Block(Repository repository, BlockData blockData, List<TransactionData> transactions, List<ATStateData> atStates) throws DataException {
|
public Block(Repository repository, BlockData blockData, List<TransactionData> transactions, List<ATStateData> atStates) {
|
||||||
this(repository, blockData);
|
this(repository, blockData);
|
||||||
|
|
||||||
this.transactions = new ArrayList<>();
|
this.transactions = new ArrayList<>();
|
||||||
@ -252,7 +250,21 @@ public class Block {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs Block-handling object with basic, initial values.
|
* Constructs new Block with empty transaction list, using passed minter account.
|
||||||
|
*
|
||||||
|
* @param repository
|
||||||
|
* @param blockData
|
||||||
|
* @param minter
|
||||||
|
*/
|
||||||
|
private Block(Repository repository, BlockData blockData, PrivateKeyAccount minter) {
|
||||||
|
this(repository, blockData);
|
||||||
|
|
||||||
|
this.minter = minter;
|
||||||
|
this.transactions = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mints new Block with basic, initial values.
|
||||||
* <p>
|
* <p>
|
||||||
* This constructor typically used when minting a new block.
|
* This constructor typically used when minting a new block.
|
||||||
* <p>
|
* <p>
|
||||||
@ -263,10 +275,7 @@ public class Block {
|
|||||||
* @param minter
|
* @param minter
|
||||||
* @throws DataException
|
* @throws DataException
|
||||||
*/
|
*/
|
||||||
public Block(Repository repository, BlockData parentBlockData, PrivateKeyAccount minter) throws DataException {
|
public static Block mint(Repository repository, BlockData parentBlockData, PrivateKeyAccount minter) throws DataException {
|
||||||
this.repository = repository;
|
|
||||||
this.minter = minter;
|
|
||||||
|
|
||||||
Block parentBlock = new Block(repository, parentBlockData);
|
Block parentBlock = new Block(repository, parentBlockData);
|
||||||
|
|
||||||
int version = parentBlock.getNextBlockVersion();
|
int version = parentBlock.getNextBlockVersion();
|
||||||
@ -318,39 +327,46 @@ public class Block {
|
|||||||
throw new DataException("Unable to calculate next block minter signature", e);
|
throw new DataException("Unable to calculate next block minter signature", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
long timestamp = calcTimestamp(parentBlockData, minter.getPublicKey());
|
// Qortal: minter is always a reward-share, so find actual minter and get their effective minting level
|
||||||
|
int minterLevel = Account.getRewardShareEffectiveMintingLevel(repository, minter.getPublicKey());
|
||||||
|
if (minterLevel == 0)
|
||||||
|
throw new IllegalStateException("Minter effective level returned zero?");
|
||||||
|
|
||||||
|
long timestamp = calcTimestamp(parentBlockData, minter.getPublicKey(), minterLevel);
|
||||||
|
|
||||||
int transactionCount = 0;
|
int transactionCount = 0;
|
||||||
byte[] transactionsSignature = null;
|
byte[] transactionsSignature = null;
|
||||||
int height = parentBlockData.getHeight() + 1;
|
int height = parentBlockData.getHeight() + 1;
|
||||||
|
|
||||||
this.transactions = new ArrayList<>();
|
|
||||||
|
|
||||||
int atCount = 0;
|
int atCount = 0;
|
||||||
BigDecimal atFees = BigDecimal.ZERO.setScale(8);
|
BigDecimal atFees = BigDecimal.ZERO.setScale(8);
|
||||||
BigDecimal totalFees = atFees;
|
BigDecimal totalFees = atFees;
|
||||||
|
|
||||||
// This instance used for AT processing
|
// This instance used for AT processing
|
||||||
this.blockData = new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp,
|
BlockData preAtBlockData = new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp,
|
||||||
minter.getPublicKey(), minterSignature, atCount, atFees,
|
minter.getPublicKey(), minterSignature, atCount, atFees,
|
||||||
encodedOnlineAccounts, onlineAccountsCount, onlineAccountsTimestamp, onlineAccountsSignatures);
|
encodedOnlineAccounts, onlineAccountsCount, onlineAccountsTimestamp, onlineAccountsSignatures);
|
||||||
|
|
||||||
// Requires this.blockData and this.transactions, sets this.ourAtStates and this.ourAtFees
|
Block newBlock = new Block(repository, preAtBlockData, minter);
|
||||||
this.executeATs();
|
|
||||||
|
|
||||||
atCount = this.ourAtStates.size();
|
// Requires blockData and transactions, sets ourAtStates and ourAtFees
|
||||||
this.atStates = this.ourAtStates;
|
newBlock.executeATs();
|
||||||
atFees = this.ourAtFees;
|
|
||||||
|
atCount = newBlock.ourAtStates.size();
|
||||||
|
newBlock.atStates = newBlock.ourAtStates;
|
||||||
|
atFees = newBlock.ourAtFees;
|
||||||
totalFees = atFees;
|
totalFees = atFees;
|
||||||
|
|
||||||
// Rebuild blockData using post-AT-execute data
|
// Rebuild blockData using post-AT-execute data
|
||||||
this.blockData = new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp,
|
newBlock.blockData = new BlockData(version, reference, transactionCount, totalFees, transactionsSignature, height, timestamp,
|
||||||
minter.getPublicKey(), minterSignature, atCount, atFees,
|
minter.getPublicKey(), minterSignature, atCount, atFees,
|
||||||
encodedOnlineAccounts, onlineAccountsCount, onlineAccountsTimestamp, onlineAccountsSignatures);
|
encodedOnlineAccounts, onlineAccountsCount, onlineAccountsTimestamp, onlineAccountsSignatures);
|
||||||
|
|
||||||
|
return newBlock;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct another block using this block as template, but with different minting account.
|
* Mints new block using this block as template, but with different minting account.
|
||||||
* <p>
|
* <p>
|
||||||
* NOTE: uses the same transactions list, AT states, etc.
|
* NOTE: uses the same transactions list, AT states, etc.
|
||||||
*
|
*
|
||||||
@ -358,7 +374,7 @@ public class Block {
|
|||||||
* @return
|
* @return
|
||||||
* @throws DataException
|
* @throws DataException
|
||||||
*/
|
*/
|
||||||
public Block newMinter(PrivateKeyAccount minter) throws DataException {
|
public Block remint(PrivateKeyAccount minter) throws DataException {
|
||||||
Block newBlock = new Block(this.repository, this.blockData);
|
Block newBlock = new Block(this.repository, this.blockData);
|
||||||
newBlock.minter = minter;
|
newBlock.minter = minter;
|
||||||
|
|
||||||
@ -380,7 +396,12 @@ public class Block {
|
|||||||
throw new DataException("Unable to calculate next block's minter signature", e);
|
throw new DataException("Unable to calculate next block's minter signature", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
long timestamp = calcTimestamp(parentBlockData, minter.getPublicKey());
|
// Qortal: minter is always a reward-share, so find actual minter and get their effective minting level
|
||||||
|
int minterLevel = Account.getRewardShareEffectiveMintingLevel(repository, minter.getPublicKey());
|
||||||
|
if (minterLevel == 0)
|
||||||
|
throw new IllegalStateException("Minter effective level returned zero?");
|
||||||
|
|
||||||
|
long timestamp = calcTimestamp(parentBlockData, minter.getPublicKey(), minterLevel);
|
||||||
|
|
||||||
newBlock.transactions = this.transactions;
|
newBlock.transactions = this.transactions;
|
||||||
int transactionCount = this.blockData.getTransactionCount();
|
int transactionCount = this.blockData.getTransactionCount();
|
||||||
@ -714,15 +735,15 @@ public class Block {
|
|||||||
return Crypto.digest(Bytes.concat(Longs.toByteArray(height), publicKey));
|
return Crypto.digest(Bytes.concat(Longs.toByteArray(height), publicKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BigInteger calcKeyDistance(int parentHeight, byte[] parentBlockSignature, byte[] publicKey) {
|
public static BigInteger calcKeyDistance(int parentHeight, byte[] parentBlockSignature, byte[] publicKey, int accountLevel) {
|
||||||
byte[] idealKey = calcIdealMinterPublicKey(parentHeight, parentBlockSignature);
|
byte[] idealKey = calcIdealMinterPublicKey(parentHeight, parentBlockSignature);
|
||||||
byte[] perturbedKey = calcHeightPerturbedPublicKey(parentHeight + 1, publicKey);
|
byte[] perturbedKey = calcHeightPerturbedPublicKey(parentHeight + 1, publicKey);
|
||||||
|
|
||||||
return MAX_DISTANCE.subtract(new BigInteger(idealKey).subtract(new BigInteger(perturbedKey)).abs());
|
return MAX_DISTANCE.subtract(new BigInteger(idealKey).subtract(new BigInteger(perturbedKey)).abs()).divide(BigInteger.valueOf(accountLevel));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BigInteger calcBlockWeight(int parentHeight, byte[] parentBlockSignature, BlockSummaryData blockSummaryData) {
|
public static BigInteger calcBlockWeight(int parentHeight, byte[] parentBlockSignature, BlockSummaryData blockSummaryData) {
|
||||||
BigInteger keyDistance = calcKeyDistance(parentHeight, parentBlockSignature, blockSummaryData.getMinterPublicKey());
|
BigInteger keyDistance = calcKeyDistance(parentHeight, parentBlockSignature, blockSummaryData.getMinterPublicKey(), blockSummaryData.getMinterLevel());
|
||||||
return BigInteger.valueOf(blockSummaryData.getOnlineAccountsCount()).shiftLeft(ACCOUNTS_COUNT_SHIFT).add(keyDistance);
|
return BigInteger.valueOf(blockSummaryData.getOnlineAccountsCount()).shiftLeft(ACCOUNTS_COUNT_SHIFT).add(keyDistance);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -753,8 +774,8 @@ public class Block {
|
|||||||
* 20% of (90s - 30s) is 12s<br>
|
* 20% of (90s - 30s) is 12s<br>
|
||||||
* So this block's timestamp is previous block's timestamp + 30s + 12s.
|
* So this block's timestamp is previous block's timestamp + 30s + 12s.
|
||||||
*/
|
*/
|
||||||
public static long calcTimestamp(BlockData parentBlockData, byte[] minterPublicKey) {
|
public static long calcTimestamp(BlockData parentBlockData, byte[] minterPublicKey, int minterAccountLevel) {
|
||||||
BigInteger distance = calcKeyDistance(parentBlockData.getHeight(), parentBlockData.getSignature(), minterPublicKey);
|
BigInteger distance = calcKeyDistance(parentBlockData.getHeight(), parentBlockData.getSignature(), minterPublicKey, minterAccountLevel);
|
||||||
final int thisHeight = parentBlockData.getHeight() + 1;
|
final int thisHeight = parentBlockData.getHeight() + 1;
|
||||||
BlockTimingByHeight blockTiming = BlockChain.getInstance().getBlockTimingByHeight(thisHeight);
|
BlockTimingByHeight blockTiming = BlockChain.getInstance().getBlockTimingByHeight(thisHeight);
|
||||||
|
|
||||||
@ -837,7 +858,12 @@ public class Block {
|
|||||||
if (this.blockData.getTimestamp() < Block.calcMinimumTimestamp(parentBlockData))
|
if (this.blockData.getTimestamp() < Block.calcMinimumTimestamp(parentBlockData))
|
||||||
return ValidationResult.TIMESTAMP_TOO_SOON;
|
return ValidationResult.TIMESTAMP_TOO_SOON;
|
||||||
|
|
||||||
long expectedTimestamp = calcTimestamp(parentBlockData, this.blockData.getMinterPublicKey());
|
// Qortal: minter is always a reward-share, so find actual minter and get their effective minting level
|
||||||
|
int minterLevel = Account.getRewardShareEffectiveMintingLevel(repository, this.blockData.getMinterPublicKey());
|
||||||
|
if (minterLevel == 0)
|
||||||
|
return ValidationResult.MINTER_NOT_ACCEPTED;
|
||||||
|
|
||||||
|
long expectedTimestamp = calcTimestamp(parentBlockData, this.blockData.getMinterPublicKey(), minterLevel);
|
||||||
if (this.blockData.getTimestamp() != expectedTimestamp)
|
if (this.blockData.getTimestamp() != expectedTimestamp)
|
||||||
return ValidationResult.TIMESTAMP_INCORRECT;
|
return ValidationResult.TIMESTAMP_INCORRECT;
|
||||||
|
|
||||||
@ -1112,7 +1138,7 @@ public class Block {
|
|||||||
throw new IllegalStateException("Attempted to execute ATs when block's local AT state data already exists");
|
throw new IllegalStateException("Attempted to execute ATs when block's local AT state data already exists");
|
||||||
|
|
||||||
// AT-Transactions generated by running ATs, to be prepended to block's transactions
|
// AT-Transactions generated by running ATs, to be prepended to block's transactions
|
||||||
List<AtTransaction> allATTransactions = new ArrayList<>();
|
List<AtTransaction> allAtTransactions = new ArrayList<>();
|
||||||
|
|
||||||
this.ourAtStates = new ArrayList<>();
|
this.ourAtStates = new ArrayList<>();
|
||||||
this.ourAtFees = BigDecimal.ZERO.setScale(8);
|
this.ourAtFees = BigDecimal.ZERO.setScale(8);
|
||||||
@ -1125,7 +1151,7 @@ public class Block {
|
|||||||
AT at = new AT(this.repository, atData);
|
AT at = new AT(this.repository, atData);
|
||||||
List<AtTransaction> atTransactions = at.run(this.blockData.getTimestamp());
|
List<AtTransaction> atTransactions = at.run(this.blockData.getTimestamp());
|
||||||
|
|
||||||
allATTransactions.addAll(atTransactions);
|
allAtTransactions.addAll(atTransactions);
|
||||||
|
|
||||||
ATStateData atStateData = at.getATStateData();
|
ATStateData atStateData = at.getATStateData();
|
||||||
this.ourAtStates.add(atStateData);
|
this.ourAtStates.add(atStateData);
|
||||||
@ -1134,7 +1160,7 @@ public class Block {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Prepend our entire AT-Transactions/states to block's transactions
|
// Prepend our entire AT-Transactions/states to block's transactions
|
||||||
this.transactions.addAll(0, allATTransactions);
|
this.transactions.addAll(0, allAtTransactions);
|
||||||
|
|
||||||
// Re-sort
|
// Re-sort
|
||||||
this.transactions.sort(Transaction.getComparator());
|
this.transactions.sort(Transaction.getComparator());
|
||||||
|
@ -149,6 +149,7 @@ public class BlockChain {
|
|||||||
private int minAccountLevelToMint = 1;
|
private int minAccountLevelToMint = 1;
|
||||||
private int minAccountLevelToRewardShare;
|
private int minAccountLevelToRewardShare;
|
||||||
private int maxRewardSharesPerMintingAccount;
|
private int maxRewardSharesPerMintingAccount;
|
||||||
|
private int founderEffectiveMintingLevel;
|
||||||
|
|
||||||
/** Minimum time to retain online account signatures (ms) for block validity checks. */
|
/** Minimum time to retain online account signatures (ms) for block validity checks. */
|
||||||
private long onlineAccountSignaturesMinLifetime;
|
private long onlineAccountSignaturesMinLifetime;
|
||||||
@ -330,6 +331,10 @@ public class BlockChain {
|
|||||||
return this.maxRewardSharesPerMintingAccount;
|
return this.maxRewardSharesPerMintingAccount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getFounderEffectiveMintingLevel() {
|
||||||
|
return this.founderEffectiveMintingLevel;
|
||||||
|
}
|
||||||
|
|
||||||
public long getOnlineAccountSignaturesMinLifetime() {
|
public long getOnlineAccountSignaturesMinLifetime() {
|
||||||
return this.onlineAccountSignaturesMinLifetime;
|
return this.onlineAccountSignaturesMinLifetime;
|
||||||
}
|
}
|
||||||
@ -430,6 +435,8 @@ public class BlockChain {
|
|||||||
if (this.minAccountLevelToRewardShare <= 0)
|
if (this.minAccountLevelToRewardShare <= 0)
|
||||||
Settings.throwValidationError("Invalid/missing \"minAccountLevelToRewardShare\" in blockchain config");
|
Settings.throwValidationError("Invalid/missing \"minAccountLevelToRewardShare\" in blockchain config");
|
||||||
|
|
||||||
|
if (this.founderEffectiveMintingLevel <= 0)
|
||||||
|
Settings.throwValidationError("Invalid/missing \"founderEffectiveMintingLevel\" in blockchain config");
|
||||||
|
|
||||||
if (this.featureTriggers == null)
|
if (this.featureTriggers == null)
|
||||||
Settings.throwValidationError("No \"featureTriggers\" entry found in blockchain config");
|
Settings.throwValidationError("No \"featureTriggers\" entry found in blockchain config");
|
||||||
|
@ -145,12 +145,12 @@ public class BlockMinter extends Thread {
|
|||||||
for (PrivateKeyAccount mintingAccount : mintingAccounts) {
|
for (PrivateKeyAccount mintingAccount : mintingAccounts) {
|
||||||
// First block does the AT heavy-lifting
|
// First block does the AT heavy-lifting
|
||||||
if (newBlocks.isEmpty()) {
|
if (newBlocks.isEmpty()) {
|
||||||
Block newBlock = new Block(repository, previousBlock.getBlockData(), mintingAccount);
|
Block newBlock = Block.mint(repository, previousBlock.getBlockData(), mintingAccount);
|
||||||
newBlocks.add(newBlock);
|
newBlocks.add(newBlock);
|
||||||
} else {
|
} else {
|
||||||
// The blocks for other minters require less effort...
|
// The blocks for other minters require less effort...
|
||||||
Block newBlock = newBlocks.get(0);
|
Block newBlock = newBlocks.get(0);
|
||||||
newBlocks.add(newBlock.newMinter(mintingAccount));
|
newBlocks.add(newBlock.remint(mintingAccount));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -338,7 +338,7 @@ public class BlockMinter extends Thread {
|
|||||||
|
|
||||||
BlockData previousBlockData = repository.getBlockRepository().getLastBlock();
|
BlockData previousBlockData = repository.getBlockRepository().getLastBlock();
|
||||||
|
|
||||||
Block newBlock = new Block(repository, previousBlockData, mintingAccount);
|
Block newBlock = Block.mint(repository, previousBlockData, mintingAccount);
|
||||||
|
|
||||||
// Make sure we're the only thread modifying the blockchain
|
// Make sure we're the only thread modifying the blockchain
|
||||||
ReentrantLock blockchainLock = Controller.getInstance().getBlockchainLock();
|
ReentrantLock blockchainLock = Controller.getInstance().getBlockchainLock();
|
||||||
|
@ -10,8 +10,11 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.qora.account.Account;
|
||||||
|
import org.qora.account.PublicKeyAccount;
|
||||||
import org.qora.block.Block;
|
import org.qora.block.Block;
|
||||||
import org.qora.block.Block.ValidationResult;
|
import org.qora.block.Block.ValidationResult;
|
||||||
|
import org.qora.data.account.RewardShareData;
|
||||||
import org.qora.data.block.BlockData;
|
import org.qora.data.block.BlockData;
|
||||||
import org.qora.data.block.BlockSummaryData;
|
import org.qora.data.block.BlockSummaryData;
|
||||||
import org.qora.data.network.PeerChainTipData;
|
import org.qora.data.network.PeerChainTipData;
|
||||||
@ -187,6 +190,10 @@ public class Synchronizer {
|
|||||||
// Fetch our corresponding block summaries
|
// Fetch our corresponding block summaries
|
||||||
List<BlockSummaryData> ourBlockSummaries = repository.getBlockRepository().getBlockSummaries(commonBlockHeight + 1, ourInitialHeight);
|
List<BlockSummaryData> ourBlockSummaries = repository.getBlockRepository().getBlockSummaries(commonBlockHeight + 1, ourInitialHeight);
|
||||||
|
|
||||||
|
// Populate minter account levels for both lists of block summaries
|
||||||
|
populateBlockSummariesMinterLevels(repository, peerBlockSummaries);
|
||||||
|
populateBlockSummariesMinterLevels(repository, ourBlockSummaries);
|
||||||
|
|
||||||
// Calculate cumulative chain weights of both blockchain subsets, from common block to highest mutual block.
|
// Calculate cumulative chain weights of both blockchain subsets, from common block to highest mutual block.
|
||||||
BigInteger ourChainWeight = Block.calcChainWeight(commonBlockHeight, commonBlockSig, ourBlockSummaries);
|
BigInteger ourChainWeight = Block.calcChainWeight(commonBlockHeight, commonBlockSig, ourBlockSummaries);
|
||||||
BigInteger peerChainWeight = Block.calcChainWeight(commonBlockHeight, commonBlockSig, peerBlockSummaries);
|
BigInteger peerChainWeight = Block.calcChainWeight(commonBlockHeight, commonBlockSig, peerBlockSummaries);
|
||||||
@ -418,11 +425,23 @@ public class Synchronizer {
|
|||||||
|
|
||||||
BlockMessage blockMessage = (BlockMessage) message;
|
BlockMessage blockMessage = (BlockMessage) message;
|
||||||
|
|
||||||
try {
|
return new Block(repository, blockMessage.getBlockData(), blockMessage.getTransactions(), blockMessage.getAtStates());
|
||||||
return new Block(repository, blockMessage.getBlockData(), blockMessage.getTransactions(), blockMessage.getAtStates());
|
}
|
||||||
} catch (DataException e) {
|
|
||||||
LOGGER.debug("Failed to create block", e);
|
private void populateBlockSummariesMinterLevels(Repository repository, List<BlockSummaryData> blockSummaries) throws DataException {
|
||||||
return null;
|
for (int i = 0; i < blockSummaries.size(); ++i) {
|
||||||
|
BlockSummaryData blockSummary = blockSummaries.get(i);
|
||||||
|
|
||||||
|
// Qortal: minter is always a reward-share, so find actual minter and get their effective minting level
|
||||||
|
int minterLevel = Account.getRewardShareEffectiveMintingLevel(repository, blockSummary.getMinterPublicKey());
|
||||||
|
if (minterLevel == 0) {
|
||||||
|
// We don't want to throw, or use zero, as this will kill Controller thread and make client unstable.
|
||||||
|
// So we log this but use 1 instead
|
||||||
|
LOGGER.warn(String.format("Unexpected zero effective minter level for reward-share %s - using 1 instead!", Base58.encode(blockSummary.getMinterPublicKey())));
|
||||||
|
minterLevel = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockSummary.setMinterLevel(minterLevel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,9 @@ public class BlockSummaryData {
|
|||||||
private byte[] minterPublicKey;
|
private byte[] minterPublicKey;
|
||||||
private int onlineAccountsCount;
|
private int onlineAccountsCount;
|
||||||
|
|
||||||
|
// Optional, set after construction
|
||||||
|
private Integer minterLevel;
|
||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
public BlockSummaryData(int height, byte[] signature, byte[] minterPublicKey, int onlineAccountsCount) {
|
public BlockSummaryData(int height, byte[] signature, byte[] minterPublicKey, int onlineAccountsCount) {
|
||||||
this.height = height;
|
this.height = height;
|
||||||
@ -49,4 +52,12 @@ public class BlockSummaryData {
|
|||||||
return this.onlineAccountsCount;
|
return this.onlineAccountsCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Integer getMinterLevel() {
|
||||||
|
return this.minterLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMinterLevel(Integer minterLevel) {
|
||||||
|
this.minterLevel = minterLevel;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
"oneNamePerAccount": true,
|
"oneNamePerAccount": true,
|
||||||
"minAccountLevelToRewardShare": 5,
|
"minAccountLevelToRewardShare": 5,
|
||||||
"maxRewardSharesPerMintingAccount": 20,
|
"maxRewardSharesPerMintingAccount": 20,
|
||||||
|
"founderEffectiveMintingLevel": 10,
|
||||||
"onlineAccountSignaturesMinLifetime": 2592000000,
|
"onlineAccountSignaturesMinLifetime": 2592000000,
|
||||||
"onlineAccountSignaturesMaxLifetime": 3196800000,
|
"onlineAccountSignaturesMaxLifetime": 3196800000,
|
||||||
"rewardsByHeight": [
|
"rewardsByHeight": [
|
||||||
@ -32,6 +33,7 @@
|
|||||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
{ "levels": [ 9, 10 ], "share": 0.25 }
|
||||||
],
|
],
|
||||||
"qoraHoldersShare": 0.20,
|
"qoraHoldersShare": 0.20,
|
||||||
|
"qoraPerQortReward": 250,
|
||||||
"blocksNeededByLevel": [ 7200, 64800, 129600, 172800, 244000, 345600, 518400, 691200, 864000, 1036800 ],
|
"blocksNeededByLevel": [ 7200, 64800, 129600, 172800, 244000, 345600, 518400, 691200, 864000, 1036800 ],
|
||||||
"blockTimingsByHeight": [
|
"blockTimingsByHeight": [
|
||||||
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
|
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
|
||||||
@ -56,26 +58,29 @@
|
|||||||
"transactions": [
|
"transactions": [
|
||||||
{ "type": "ISSUE_ASSET", "owner": "QUwGVHPPxJNJ2dq95abQNe79EyBN2K26zM", "assetName": "QORT", "description": "QORTAL coin", "quantity": 0, "isDivisible": true, "reference": "28u54WRcMfGujtQMZ9dNKFXVqucY7XfPihXAqPFsnx853NPUwfDJy1sMH5boCkahFgjUNYqc5fkduxdBhQTKgUsC", "data": "{}" },
|
{ "type": "ISSUE_ASSET", "owner": "QUwGVHPPxJNJ2dq95abQNe79EyBN2K26zM", "assetName": "QORT", "description": "QORTAL coin", "quantity": 0, "isDivisible": true, "reference": "28u54WRcMfGujtQMZ9dNKFXVqucY7XfPihXAqPFsnx853NPUwfDJy1sMH5boCkahFgjUNYqc5fkduxdBhQTKgUsC", "data": "{}" },
|
||||||
{ "type": "ISSUE_ASSET", "owner": "QUwGVHPPxJNJ2dq95abQNe79EyBN2K26zM", "assetName": "QORA", "description": "Representative legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true },
|
{ "type": "ISSUE_ASSET", "owner": "QUwGVHPPxJNJ2dq95abQNe79EyBN2K26zM", "assetName": "QORA", "description": "Representative legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true },
|
||||||
|
{ "type": "ISSUE_ASSET", "owner": "QUwGVHPPxJNJ2dq95abQNe79EyBN2K26zM", "assetName": "QORT-from-QORA", "description": "QORT gained from holding legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true },
|
||||||
|
|
||||||
{ "type": "GENESIS", "recipient": "QcatTpaU1UneBs3fVHo8QN6mUmuceRVzFY", "amount": "1000" },
|
|
||||||
{ "type": "ACCOUNT_FLAGS", "target": "QcatTpaU1UneBs3fVHo8QN6mUmuceRVzFY", "andMask": -1, "orMask": 1, "xorMask": 0 },
|
{ "type": "ACCOUNT_FLAGS", "target": "QcatTpaU1UneBs3fVHo8QN6mUmuceRVzFY", "andMask": -1, "orMask": 1, "xorMask": 0 },
|
||||||
{ "type": "ACCOUNT_LEVEL", "target": "QcatTpaU1UneBs3fVHo8QN6mUmuceRVzFY", "level": 8 },
|
|
||||||
{ "type": "REWARD_SHARE", "minterPublicKey": "6rNn9b3pYRrG9UKqzMWYZ9qa8F3Zgv2mVWrULGHUusb", "recipient": "QcatTpaU1UneBs3fVHo8QN6mUmuceRVzFY", "rewardSharePublicKey": "8X3w1521UNnnonieugAxhfbfvqoRpwPXJrwGQZb5JjQ3", "sharePercent": 100 },
|
{ "type": "REWARD_SHARE", "minterPublicKey": "6rNn9b3pYRrG9UKqzMWYZ9qa8F3Zgv2mVWrULGHUusb", "recipient": "QcatTpaU1UneBs3fVHo8QN6mUmuceRVzFY", "rewardSharePublicKey": "8X3w1521UNnnonieugAxhfbfvqoRpwPXJrwGQZb5JjQ3", "sharePercent": 100 },
|
||||||
|
|
||||||
{ "type": "GENESIS", "recipient": "QcatoCyyp7dVfMtJ92sgUUPDoBJevaemRX", "amount": "1000" },
|
|
||||||
{ "type": "ACCOUNT_FLAGS", "target": "QcatoCyyp7dVfMtJ92sgUUPDoBJevaemRX", "andMask": -1, "orMask": 1, "xorMask": 0 },
|
|
||||||
{ "type": "ACCOUNT_LEVEL", "target": "QcatoCyyp7dVfMtJ92sgUUPDoBJevaemRX", "level": 3 },
|
|
||||||
|
|
||||||
{ "type": "GENESIS", "recipient": "QTiga19sttbf6CLQLT83mhCSWEaCvjk8th", "amount": "1000" },
|
|
||||||
{ "type": "ACCOUNT_FLAGS", "target": "QTiga19sttbf6CLQLT83mhCSWEaCvjk8th", "andMask": -1, "orMask": 1, "xorMask": 0 },
|
{ "type": "ACCOUNT_FLAGS", "target": "QTiga19sttbf6CLQLT83mhCSWEaCvjk8th", "andMask": -1, "orMask": 1, "xorMask": 0 },
|
||||||
{ "type": "ACCOUNT_LEVEL", "target": "QTiga19sttbf6CLQLT83mhCSWEaCvjk8th", "level": 10 },
|
|
||||||
|
|
||||||
{ "type": "GENESIS", "recipient": "QcrowX39FuycKvMFFBsakyd5HSxe7bxFsn", "amount": "1000" },
|
|
||||||
{ "type": "ACCOUNT_FLAGS", "target": "QcrowX39FuycKvMFFBsakyd5HSxe7bxFsn", "andMask": -1, "orMask": 1, "xorMask": 0 },
|
{ "type": "ACCOUNT_FLAGS", "target": "QcrowX39FuycKvMFFBsakyd5HSxe7bxFsn", "andMask": -1, "orMask": 1, "xorMask": 0 },
|
||||||
{ "type": "ACCOUNT_LEVEL", "target": "QcrowX39FuycKvMFFBsakyd5HSxe7bxFsn", "level": 10 },
|
|
||||||
|
|
||||||
{ "type": "CREATE_GROUP", "creatorPublicKey": "6rNn9b3pYRrG9UKqzMWYZ9qa8F3Zgv2mVWrULGHUusb", "owner": "QcatTpaU1UneBs3fVHo8QN6mUmuceRVzFY", "groupName": "dev-group", "description": "developer group", "isOpen": false, "approvalThreshold": "PCT60", "minimumBlockDelay": 0, "maximumBlockDelay": 1440 },
|
{ "type": "CREATE_GROUP", "creatorPublicKey": "6rNn9b3pYRrG9UKqzMWYZ9qa8F3Zgv2mVWrULGHUusb", "owner": "QcatTpaU1UneBs3fVHo8QN6mUmuceRVzFY", "groupName": "dev-group", "description": "developer group", "isOpen": false, "approvalThreshold": "PCT60", "minimumBlockDelay": 0, "maximumBlockDelay": 1440 },
|
||||||
|
|
||||||
|
{ "type": "GENESIS", "recipient": "QMtx2UmUuRZckCmRJRyxdzSAazHP8hU5rA", "amount": "160672815.43629771", "assetId": 1 },
|
||||||
|
{ "type": "GENESIS", "recipient": "QMkgf9Y6Ac2TUrynDvyhX69ekpC3P3GQmN", "amount": "99008835.47860426", "assetId": 1 },
|
||||||
|
{ "type": "GENESIS", "recipient": "QWZm17rRXeUehcM4TprVNNRSTHWQmG2bME", "amount": "62663714.00000000", "assetId": 1 },
|
||||||
|
{ "type": "GENESIS", "recipient": "Qa95hURaNK4kPhDhbdmDFm2wMkkoWFZ4Zz", "amount": "40976709.97984710", "assetId": 1 },
|
||||||
|
{ "type": "GENESIS", "recipient": "QPEoMF2dA7NHrHhsSG9zczCFwx9wFdWvzT", "amount": "10033147.61257500", "assetId": 1 },
|
||||||
|
{ "type": "GENESIS", "recipient": "QjJjjBUJSZAMuYiwTyfJTFthH6SrofjG6d", "amount": "8871800.22712502", "assetId": 1 },
|
||||||
|
{ "type": "GENESIS", "recipient": "QRj4VNEthakckhYpCJMEBhEFk12pa7GPJT", "amount": "7810001.00000000", "assetId": 1 },
|
||||||
|
{ "type": "GENESIS", "recipient": "QMNdPz11XubtvxXLGeiG3PHKaQW67LkZMp", "amount": "4056950.00000000", "assetId": 1 },
|
||||||
|
{ "type": "GENESIS", "recipient": "QhUWSWWFt6vDy2qNFn68JPTPLjyDrzrh4D", "amount": "3220564.00000000", "assetId": 1 },
|
||||||
|
{ "type": "GENESIS", "recipient": "QMrECKgkohx6ZXEMdLzikqBmAkdyHeQDqL", "amount": "997498.00000000", "assetId": 1 },
|
||||||
|
{ "type": "GENESIS", "recipient": "QMQAG28pyv2aZVjWbKoRn39Ytir6rLPnTK", "amount": "100745.00000000", "assetId": 1 },
|
||||||
|
{ "type": "GENESIS", "recipient": "QPZTxWtCmH6Y6zwwntjnPDfKG6zNKRivqJ", "amount": "1282.61375000", "assetId": 1 },
|
||||||
|
|
||||||
{ "type": "ACCOUNT_LEVEL", "target": "QQKeokRiFCgAhBSdu1DUf5e1LCkgApvrxZ", "level": 5 },
|
{ "type": "ACCOUNT_LEVEL", "target": "QQKeokRiFCgAhBSdu1DUf5e1LCkgApvrxZ", "level": 5 },
|
||||||
{ "type": "ACCOUNT_LEVEL", "target": "QiaaoNZ54wKoaUMXxW72UsPt1MiPpeUTWm", "level": 5 },
|
{ "type": "ACCOUNT_LEVEL", "target": "QiaaoNZ54wKoaUMXxW72UsPt1MiPpeUTWm", "level": 5 },
|
||||||
{ "type": "ACCOUNT_LEVEL", "target": "QN5XF1YQUyVt3S1LNZtStXQCbtxyhkj2FR", "level": 5 },
|
{ "type": "ACCOUNT_LEVEL", "target": "QN5XF1YQUyVt3S1LNZtStXQCbtxyhkj2FR", "level": 5 },
|
||||||
|
@ -4,131 +4,182 @@ import static org.junit.Assert.*;
|
|||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import org.qora.crypto.Crypto;
|
import org.qora.account.Account;
|
||||||
|
import org.qora.block.Block;
|
||||||
import org.qora.data.block.BlockSummaryData;
|
import org.qora.data.block.BlockSummaryData;
|
||||||
|
import org.qora.repository.DataException;
|
||||||
|
import org.qora.repository.Repository;
|
||||||
|
import org.qora.repository.RepositoryManager;
|
||||||
|
import org.qora.test.common.Common;
|
||||||
|
import org.qora.test.common.TestAccount;
|
||||||
import org.qora.transform.Transformer;
|
import org.qora.transform.Transformer;
|
||||||
import org.qora.transform.block.BlockTransformer;
|
import org.qora.transform.block.BlockTransformer;
|
||||||
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import com.google.common.primitives.Bytes;
|
public class ChainWeightTests extends Common {
|
||||||
import com.google.common.primitives.Longs;
|
|
||||||
|
|
||||||
public class ChainWeightTests {
|
|
||||||
|
|
||||||
private static final int ACCOUNTS_COUNT_SHIFT = Transformer.PUBLIC_KEY_LENGTH * 8;
|
|
||||||
private static final int CHAIN_WEIGHT_SHIFT = 8;
|
|
||||||
private static final Random RANDOM = new Random();
|
private static final Random RANDOM = new Random();
|
||||||
|
|
||||||
private static final BigInteger MAX_DISTANCE;
|
@Before
|
||||||
static {
|
public void beforeTest() throws DataException {
|
||||||
byte[] maxValue = new byte[Transformer.PUBLIC_KEY_LENGTH];
|
Common.useSettings("test-settings-v2-minting.json");
|
||||||
Arrays.fill(maxValue, (byte) 0xFF);
|
|
||||||
MAX_DISTANCE = new BigInteger(1, maxValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static BlockSummaryData genBlockSummary(Repository repository, int height) {
|
||||||
private static byte[] perturbPublicKey(int height, byte[] publicKey) {
|
TestAccount testAccount = Common.getRandomTestAccount(repository, true);
|
||||||
return Crypto.digest(Bytes.concat(Longs.toByteArray(height), publicKey));
|
byte[] minterPublicKey = testAccount.getPublicKey();
|
||||||
}
|
|
||||||
|
|
||||||
private static BigInteger calcKeyDistance(int parentHeight, byte[] parentGeneratorKey, byte[] publicKey) {
|
|
||||||
byte[] idealKey = perturbPublicKey(parentHeight, parentGeneratorKey);
|
|
||||||
byte[] perturbedKey = perturbPublicKey(parentHeight + 1, publicKey);
|
|
||||||
|
|
||||||
BigInteger keyDistance = MAX_DISTANCE.subtract(new BigInteger(idealKey).subtract(new BigInteger(perturbedKey)).abs());
|
|
||||||
return keyDistance;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BigInteger calcBlockWeight(int parentHeight, byte[] parentGeneratorKey, BlockSummaryData blockSummaryData) {
|
|
||||||
BigInteger keyDistance = calcKeyDistance(parentHeight, parentGeneratorKey, blockSummaryData.getMinterPublicKey());
|
|
||||||
BigInteger weight = BigInteger.valueOf(blockSummaryData.getOnlineAccountsCount()).shiftLeft(ACCOUNTS_COUNT_SHIFT).add(keyDistance);
|
|
||||||
return weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BigInteger calcChainWeight(int commonBlockHeight, byte[] commonBlockGeneratorKey, List<BlockSummaryData> blockSummaries) {
|
|
||||||
BigInteger cumulativeWeight = BigInteger.ZERO;
|
|
||||||
int parentHeight = commonBlockHeight;
|
|
||||||
byte[] parentGeneratorKey = commonBlockGeneratorKey;
|
|
||||||
|
|
||||||
for (BlockSummaryData blockSummaryData : blockSummaries) {
|
|
||||||
cumulativeWeight = cumulativeWeight.shiftLeft(CHAIN_WEIGHT_SHIFT).add(calcBlockWeight(parentHeight, parentGeneratorKey, blockSummaryData));
|
|
||||||
parentHeight = blockSummaryData.getHeight();
|
|
||||||
parentGeneratorKey = blockSummaryData.getMinterPublicKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
return cumulativeWeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BlockSummaryData genBlockSummary(int height) {
|
|
||||||
byte[] generatorPublicKey = new byte[Transformer.PUBLIC_KEY_LENGTH];
|
|
||||||
RANDOM.nextBytes(generatorPublicKey);
|
|
||||||
|
|
||||||
byte[] signature = new byte[BlockTransformer.BLOCK_SIGNATURE_LENGTH];
|
byte[] signature = new byte[BlockTransformer.BLOCK_SIGNATURE_LENGTH];
|
||||||
RANDOM.nextBytes(signature);
|
RANDOM.nextBytes(signature);
|
||||||
|
|
||||||
int onlineAccountsCount = RANDOM.nextInt(1000);
|
int onlineAccountsCount = RANDOM.nextInt(1000);
|
||||||
|
|
||||||
return new BlockSummaryData(height, signature, generatorPublicKey, onlineAccountsCount);
|
return new BlockSummaryData(height, signature, minterPublicKey, onlineAccountsCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<BlockSummaryData> genBlockSummaries(int count, BlockSummaryData commonBlockSummary) {
|
private static List<BlockSummaryData> genBlockSummaries(Repository repository, int count, BlockSummaryData commonBlockSummary) {
|
||||||
List<BlockSummaryData> blockSummaries = new ArrayList<>();
|
List<BlockSummaryData> blockSummaries = new ArrayList<>();
|
||||||
blockSummaries.add(commonBlockSummary);
|
blockSummaries.add(commonBlockSummary);
|
||||||
|
|
||||||
final int commonBlockHeight = commonBlockSummary.getHeight();
|
final int commonBlockHeight = commonBlockSummary.getHeight();
|
||||||
|
|
||||||
for (int i = 1; i <= count; ++i)
|
for (int i = 1; i <= count; ++i)
|
||||||
blockSummaries.add(genBlockSummary(commonBlockHeight + i));
|
blockSummaries.add(genBlockSummary(repository, commonBlockHeight + i));
|
||||||
|
|
||||||
return blockSummaries;
|
return blockSummaries;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that more online accounts beats a better key
|
// Check that more online accounts beats a better key
|
||||||
@Test
|
@Test
|
||||||
public void testMoreAccountsBlock() {
|
public void testMoreAccountsBlock() throws DataException {
|
||||||
final int parentHeight = 1;
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
final byte[] parentGeneratorKey = new byte[Transformer.PUBLIC_KEY_LENGTH];
|
final int parentHeight = 1;
|
||||||
|
final byte[] parentMinterKey = new byte[Transformer.PUBLIC_KEY_LENGTH];
|
||||||
|
|
||||||
int betterAccountsCount = 100;
|
int betterAccountsCount = 100;
|
||||||
int worseAccountsCount = 20;
|
int worseAccountsCount = 20;
|
||||||
|
|
||||||
byte[] betterKey = new byte[Transformer.PUBLIC_KEY_LENGTH];
|
TestAccount betterAccount = Common.getTestAccount(repository, "bob-reward-share");
|
||||||
betterKey[0] = 0x41;
|
byte[] betterKey = betterAccount.getPublicKey();
|
||||||
|
int betterMinterLevel = Account.getRewardShareEffectiveMintingLevel(repository, betterKey);
|
||||||
|
|
||||||
byte[] worseKey = new byte[Transformer.PUBLIC_KEY_LENGTH];
|
TestAccount worseAccount = Common.getTestAccount(repository, "dilbert-reward-share");
|
||||||
worseKey[0] = 0x23;
|
byte[] worseKey = worseAccount.getPublicKey();
|
||||||
|
int worseMinterLevel = Account.getRewardShareEffectiveMintingLevel(repository, worseKey);
|
||||||
|
|
||||||
BigInteger betterKeyDistance = calcKeyDistance(parentHeight, parentGeneratorKey, betterKey);
|
// This is to check that the hard-coded keys ARE actually better/worse as expected, before moving on testing more online accounts
|
||||||
BigInteger worseKeyDistance = calcKeyDistance(parentHeight, parentGeneratorKey, worseKey);
|
BigInteger betterKeyDistance = Block.calcKeyDistance(parentHeight, parentMinterKey, betterKey, betterMinterLevel);
|
||||||
assertEquals("hard-coded keys are wrong", 1, betterKeyDistance.compareTo(worseKeyDistance));
|
BigInteger worseKeyDistance = Block.calcKeyDistance(parentHeight, parentMinterKey, worseKey, worseMinterLevel);
|
||||||
|
assertEquals("hard-coded keys are wrong", 1, betterKeyDistance.compareTo(worseKeyDistance));
|
||||||
|
|
||||||
BlockSummaryData betterBlockSummary = new BlockSummaryData(parentHeight + 1, null, worseKey, betterAccountsCount);
|
BlockSummaryData betterBlockSummary = new BlockSummaryData(parentHeight + 1, null, worseKey, betterAccountsCount);
|
||||||
BlockSummaryData worseBlockSummary = new BlockSummaryData(parentHeight + 1, null, betterKey, worseAccountsCount);
|
BlockSummaryData worseBlockSummary = new BlockSummaryData(parentHeight + 1, null, betterKey, worseAccountsCount);
|
||||||
|
|
||||||
BigInteger betterBlockWeight = calcBlockWeight(parentHeight, parentGeneratorKey, betterBlockSummary);
|
populateBlockSummaryMinterLevel(repository, betterBlockSummary);
|
||||||
BigInteger worseBlockWeight = calcBlockWeight(parentHeight, parentGeneratorKey, worseBlockSummary);
|
populateBlockSummaryMinterLevel(repository, worseBlockSummary);
|
||||||
|
|
||||||
assertEquals("block weights are wrong", 1, betterBlockWeight.compareTo(worseBlockWeight));
|
BigInteger betterBlockWeight = Block.calcBlockWeight(parentHeight, parentMinterKey, betterBlockSummary);
|
||||||
|
BigInteger worseBlockWeight = Block.calcBlockWeight(parentHeight, parentMinterKey, worseBlockSummary);
|
||||||
|
|
||||||
|
assertEquals("block weights are wrong", 1, betterBlockWeight.compareTo(worseBlockWeight));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that a longer chain beats a shorter chain
|
// Check that a longer chain beats a shorter chain
|
||||||
@Test
|
@Test
|
||||||
public void testLongerChain() {
|
public void testLongerChain() throws DataException {
|
||||||
final int commonBlockHeight = 1;
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
BlockSummaryData commonBlockSummary = genBlockSummary(commonBlockHeight);
|
final int commonBlockHeight = 1;
|
||||||
byte[] commonBlockGeneratorKey = commonBlockSummary.getMinterPublicKey();
|
BlockSummaryData commonBlockSummary = genBlockSummary(repository, commonBlockHeight);
|
||||||
|
byte[] commonBlockGeneratorKey = commonBlockSummary.getMinterPublicKey();
|
||||||
|
|
||||||
List<BlockSummaryData> shorterChain = genBlockSummaries(3, commonBlockSummary);
|
List<BlockSummaryData> shorterChain = genBlockSummaries(repository, 3, commonBlockSummary);
|
||||||
List<BlockSummaryData> longerChain = genBlockSummaries(shorterChain.size() + 1, commonBlockSummary);
|
List<BlockSummaryData> longerChain = genBlockSummaries(repository, shorterChain.size() + 1, commonBlockSummary);
|
||||||
|
|
||||||
BigInteger shorterChainWeight = calcChainWeight(commonBlockHeight, commonBlockGeneratorKey, shorterChain);
|
populateBlockSummariesMinterLevels(repository, shorterChain);
|
||||||
BigInteger longerChainWeight = calcChainWeight(commonBlockHeight, commonBlockGeneratorKey, longerChain);
|
populateBlockSummariesMinterLevels(repository, longerChain);
|
||||||
|
|
||||||
assertEquals("longer chain should have greater weight", 1, longerChainWeight.compareTo(shorterChainWeight));
|
BigInteger shorterChainWeight = Block.calcChainWeight(commonBlockHeight, commonBlockGeneratorKey, shorterChain);
|
||||||
|
BigInteger longerChainWeight = Block.calcChainWeight(commonBlockHeight, commonBlockGeneratorKey, longerChain);
|
||||||
|
|
||||||
|
assertEquals("longer chain should have greater weight", 1, longerChainWeight.compareTo(shorterChainWeight));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that a higher level account wins more blocks
|
||||||
|
@Test
|
||||||
|
public void testMinterLevel() throws DataException {
|
||||||
|
testMinterLevels("chloe-reward-share", "bob-reward-share");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testMinterLevels(String betterMinterName, String worseMinterName) throws DataException {
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
TestAccount betterAccount = Common.getTestAccount(repository, betterMinterName);
|
||||||
|
byte[] betterKey = betterAccount.getPublicKey();
|
||||||
|
int betterMinterLevel = Account.getRewardShareEffectiveMintingLevel(repository, betterKey);
|
||||||
|
|
||||||
|
TestAccount worseAccount = Common.getTestAccount(repository, worseMinterName);
|
||||||
|
byte[] worseKey = worseAccount.getPublicKey();
|
||||||
|
int worseMinterLevel = Account.getRewardShareEffectiveMintingLevel(repository, worseKey);
|
||||||
|
|
||||||
|
// Check hard-coded accounts have expected better/worse levels
|
||||||
|
assertTrue("hard-coded accounts have wrong relative minting levels", betterMinterLevel > worseMinterLevel);
|
||||||
|
|
||||||
|
Random random = new Random();
|
||||||
|
final int onlineAccountsCount = 100;
|
||||||
|
int betterAccountWins = 0;
|
||||||
|
int worseAccountWins = 0;
|
||||||
|
byte[] parentSignature = new byte[64];
|
||||||
|
random.nextBytes(parentSignature);
|
||||||
|
|
||||||
|
for (int parentHeight = 1; parentHeight < 1000; ++parentHeight) {
|
||||||
|
byte[] blockSignature = new byte[64];
|
||||||
|
random.nextBytes(blockSignature);
|
||||||
|
|
||||||
|
BlockSummaryData betterBlockSummary = new BlockSummaryData(parentHeight + 1, blockSignature, worseKey, onlineAccountsCount);
|
||||||
|
BlockSummaryData worseBlockSummary = new BlockSummaryData(parentHeight + 1, blockSignature, betterKey, onlineAccountsCount);
|
||||||
|
|
||||||
|
populateBlockSummaryMinterLevel(repository, betterBlockSummary);
|
||||||
|
populateBlockSummaryMinterLevel(repository, worseBlockSummary);
|
||||||
|
|
||||||
|
BigInteger betterBlockWeight = Block.calcBlockWeight(parentHeight, parentSignature, betterBlockSummary);
|
||||||
|
BigInteger worseBlockWeight = Block.calcBlockWeight(parentHeight, parentSignature, worseBlockSummary);
|
||||||
|
|
||||||
|
if (betterBlockWeight.compareTo(worseBlockWeight) >= 0)
|
||||||
|
++betterAccountWins;
|
||||||
|
else
|
||||||
|
++worseAccountWins;
|
||||||
|
|
||||||
|
parentSignature = blockSignature;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertTrue("Account with better minting level didn't win more blocks", betterAccountWins > worseAccountWins);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that a higher level account wins more blocks
|
||||||
|
@Test
|
||||||
|
public void testFounderMinterLevel() throws DataException {
|
||||||
|
testMinterLevels("alice-reward-share", "dilbert-reward-share");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void populateBlockSummariesMinterLevels(Repository repository, List<BlockSummaryData> blockSummaries) throws DataException {
|
||||||
|
for (int i = 0; i < blockSummaries.size(); ++i) {
|
||||||
|
BlockSummaryData blockSummary = blockSummaries.get(i);
|
||||||
|
|
||||||
|
populateBlockSummaryMinterLevel(repository, blockSummary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void populateBlockSummaryMinterLevel(Repository repository, BlockSummaryData blockSummary) throws DataException {
|
||||||
|
int minterLevel = Account.getRewardShareEffectiveMintingLevel(repository, blockSummary.getMinterPublicKey());
|
||||||
|
assertNotSame("effective minter level should not be zero", 0, minterLevel);
|
||||||
|
|
||||||
|
blockSummary.setMinterLevel(minterLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1155,7 +1155,7 @@ public class TransactionTests extends Common {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Block forgeBlock(TransactionData transactionData) throws DataException {
|
private Block forgeBlock(TransactionData transactionData) throws DataException {
|
||||||
Block block = new Block(repository, parentBlockData, generator);
|
Block block = Block.mint(repository, parentBlockData, generator);
|
||||||
block.addTransaction(transactionData);
|
block.addTransaction(transactionData);
|
||||||
block.sign();
|
block.sign();
|
||||||
return block;
|
return block;
|
||||||
|
@ -10,6 +10,7 @@ import java.util.Collections;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@ -71,12 +72,30 @@ public class Common {
|
|||||||
|
|
||||||
// Alice reward-share with herself. Private key is reward-share private key, derived from Alice's private and public keys.
|
// Alice reward-share with herself. Private key is reward-share private key, derived from Alice's private and public keys.
|
||||||
testAccountsByName.put("alice-reward-share", new TestAccount(null, "alice-reward-share", "1CeDCg9TSdBwJNGVTGG7pCKsvsyyoEcaVXYvDT1Xb9f", true));
|
testAccountsByName.put("alice-reward-share", new TestAccount(null, "alice-reward-share", "1CeDCg9TSdBwJNGVTGG7pCKsvsyyoEcaVXYvDT1Xb9f", true));
|
||||||
|
// Bob self-share
|
||||||
|
testAccountsByName.put("bob-reward-share", new TestAccount(null, "bob-reward-share", "975G6DJX2bhkq2dawxxDbNe5DcT33LbGto5tRueKVRDx", true));
|
||||||
|
// Chloe self-share
|
||||||
|
testAccountsByName.put("chloe-reward-share", new TestAccount(null, "chloe-reward-share", "2paayAXTbGmdLtJ7tNxY93bhPnWZwNYwk15KA37Sw5yS", true));
|
||||||
|
// Dilbert self-share
|
||||||
|
testAccountsByName.put("dilbert-reward-share", new TestAccount(null, "dilbert-reward-share", "C3DqD3K9bZDqxwLBroXc2NgL2SRJrif1mcAW7zNMUg9", true));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TestAccount getTestAccount(Repository repository, String name) {
|
public static TestAccount getTestAccount(Repository repository, String name) {
|
||||||
return new TestAccount(repository, testAccountsByName.get(name));
|
return new TestAccount(repository, testAccountsByName.get(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static TestAccount getRandomTestAccount(Repository repository, Boolean includeRewardShare) {
|
||||||
|
List<TestAccount> testAccounts = new ArrayList<>(testAccountsByName.values());
|
||||||
|
|
||||||
|
if (includeRewardShare != null)
|
||||||
|
testAccounts.removeIf(account -> account.isRewardShare != includeRewardShare);
|
||||||
|
|
||||||
|
Random random = new Random();
|
||||||
|
int index = random.nextInt(testAccounts.size());
|
||||||
|
|
||||||
|
return testAccounts.get(index);
|
||||||
|
}
|
||||||
|
|
||||||
public static List<TestAccount> getTestAccounts(Repository repository) {
|
public static List<TestAccount> getTestAccounts(Repository repository) {
|
||||||
return testAccountsByName.values().stream().map(account -> new TestAccount(repository, account)).collect(Collectors.toList());
|
return testAccountsByName.values().stream().map(account -> new TestAccount(repository, account)).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
"minAccountLevelToRewardShare": 5,
|
"minAccountLevelToRewardShare": 5,
|
||||||
"maxRewardSharesPerMintingAccount": 20,
|
"maxRewardSharesPerMintingAccount": 20,
|
||||||
|
"founderEffectiveMintingLevel": 10,
|
||||||
"onlineAccountSignaturesMinLifetime": 3600000,
|
"onlineAccountSignaturesMinLifetime": 3600000,
|
||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
"rewardsByHeight": [
|
"rewardsByHeight": [
|
||||||
@ -23,6 +24,7 @@
|
|||||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
{ "levels": [ 9, 10 ], "share": 0.25 }
|
||||||
],
|
],
|
||||||
"qoraHoldersShare": 0.20,
|
"qoraHoldersShare": 0.20,
|
||||||
|
"qoraPerQortReward": 250,
|
||||||
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
||||||
"blockTimingsByHeight": [
|
"blockTimingsByHeight": [
|
||||||
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
|
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
|
||||||
|
69
src/test/resources/test-chain-v2-minting.json
Normal file
69
src/test/resources/test-chain-v2-minting.json
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
{
|
||||||
|
"isTestChain": true,
|
||||||
|
"blockTimestampMargin": 500,
|
||||||
|
"transactionExpiryPeriod": 86400000,
|
||||||
|
"maxBlockSize": 2097152,
|
||||||
|
"maxBytesPerUnitFee": 1024,
|
||||||
|
"unitFee": "0.1",
|
||||||
|
"requireGroupForApproval": false,
|
||||||
|
"minAccountLevelToRewardShare": 5,
|
||||||
|
"maxRewardSharesPerMintingAccount": 20,
|
||||||
|
"founderEffectiveMintingLevel": 10,
|
||||||
|
"onlineAccountSignaturesMinLifetime": 3600000,
|
||||||
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
|
"rewardsByHeight": [
|
||||||
|
{ "height": 1, "reward": 100 },
|
||||||
|
{ "height": 11, "reward": 10 },
|
||||||
|
{ "height": 21, "reward": 1 }
|
||||||
|
],
|
||||||
|
"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,
|
||||||
|
"qoraPerQortReward": 250,
|
||||||
|
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
||||||
|
"blockTimingsByHeight": [
|
||||||
|
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
|
||||||
|
],
|
||||||
|
"featureTriggers": {
|
||||||
|
"messageHeight": 0,
|
||||||
|
"atHeight": 0,
|
||||||
|
"assetsTimestamp": 0,
|
||||||
|
"votingTimestamp": 0,
|
||||||
|
"arbitraryTimestamp": 0,
|
||||||
|
"powfixTimestamp": 0,
|
||||||
|
"v2Timestamp": 0,
|
||||||
|
"newAssetPricingTimestamp": 0,
|
||||||
|
"groupApprovalTimestamp": 0
|
||||||
|
},
|
||||||
|
"genesisInfo": {
|
||||||
|
"version": 4,
|
||||||
|
"timestamp": 0,
|
||||||
|
"transactions": [
|
||||||
|
{ "type": "ISSUE_ASSET", "owner": "QcFmNxSArv5tWEzCtTKb2Lqc5QkKuQ7RNs", "assetName": "QORT", "description": "QORT native coin", "data": "", "quantity": 0, "isDivisible": true, "fee": 0, "reference": "3Verk6ZKBJc3WTTVfxFC9icSjKdM8b92eeJEpJP8qNizG4ZszNFq8wdDYdSjJXq2iogDFR1njyhsBdVpbvDfjzU7" },
|
||||||
|
{ "type": "ISSUE_ASSET", "owner": "QUwGVHPPxJNJ2dq95abQNe79EyBN2K26zM", "assetName": "Legacy-QORA", "description": "Representative legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true },
|
||||||
|
{ "type": "ISSUE_ASSET", "owner": "QUwGVHPPxJNJ2dq95abQNe79EyBN2K26zM", "assetName": "QORT-from-QORA", "description": "QORT gained from holding legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true },
|
||||||
|
|
||||||
|
{ "type": "GENESIS", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "amount": "1000000000" },
|
||||||
|
{ "type": "GENESIS", "recipient": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "amount": "1000000" },
|
||||||
|
{ "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "amount": "1000000" },
|
||||||
|
{ "type": "GENESIS", "recipient": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "amount": "1000000" },
|
||||||
|
|
||||||
|
{ "type": "ACCOUNT_FLAGS", "target": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "andMask": -1, "orMask": 1, "xorMask": 0 },
|
||||||
|
{ "type": "REWARD_SHARE", "minterPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "rewardSharePublicKey": "7PpfnvLSG7y4HPh8hE7KoqAjLCkv7Ui6xw4mKAkbZtox", "sharePercent": 100 },
|
||||||
|
|
||||||
|
{ "type": "ACCOUNT_LEVEL", "target": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "level": 1 },
|
||||||
|
{ "type": "REWARD_SHARE", "minterPublicKey": "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry", "recipient": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "rewardSharePublicKey": "CcABzvk26TFEHG7Yok84jxyd4oBtLkx8RJdGFVz2csvp", "sharePercent": 100 },
|
||||||
|
|
||||||
|
{ "type": "ACCOUNT_LEVEL", "target": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "level": 8 },
|
||||||
|
{ "type": "REWARD_SHARE", "minterPublicKey": "7KNBj2MnEb6zq1vvKY1q8G2Voctcc2Z1X4avFyEH2eJC", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "rewardSharePublicKey": "6bnEKqZbsCSWryUQnbBT9Umufdu3CapFvxfAni6afhFb", "sharePercent": 100 },
|
||||||
|
|
||||||
|
{ "type": "ACCOUNT_LEVEL", "target": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "level": 5 },
|
||||||
|
{ "type": "REWARD_SHARE", "minterPublicKey": "CGAedAQU91SR73iqoYtss6NAsra284SShXnDWvRXqR4G", "recipient": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "rewardSharePublicKey": "4QafENiQCCDCnbXgcZfiyCu9qWqZ6YEciXAyFb4TT8YQ", "sharePercent": 100 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@
|
|||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
"minAccountLevelToRewardShare": 5,
|
"minAccountLevelToRewardShare": 5,
|
||||||
"maxRewardSharesPerMintingAccount": 20,
|
"maxRewardSharesPerMintingAccount": 20,
|
||||||
|
"founderEffectiveMintingLevel": 10,
|
||||||
"onlineAccountSignaturesMinLifetime": 3600000,
|
"onlineAccountSignaturesMinLifetime": 3600000,
|
||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
"rewardsByHeight": [
|
"rewardsByHeight": [
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
"minAccountLevelToRewardShare": 5,
|
"minAccountLevelToRewardShare": 5,
|
||||||
"maxRewardSharesPerMintingAccount": 20,
|
"maxRewardSharesPerMintingAccount": 20,
|
||||||
|
"founderEffectiveMintingLevel": 10,
|
||||||
"onlineAccountSignaturesMinLifetime": 3600000,
|
"onlineAccountSignaturesMinLifetime": 3600000,
|
||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
"rewardsByHeight": [
|
"rewardsByHeight": [
|
||||||
|
6
src/test/resources/test-settings-v2-minting.json
Normal file
6
src/test/resources/test-settings-v2-minting.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"restrictedApi": false,
|
||||||
|
"blockchainConfig": "src/test/resources/test-chain-v2-minting.json",
|
||||||
|
"wipeUnconfirmedOnStart": false,
|
||||||
|
"minPeers": 0
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user