mirror of
https://github.com/Qortal/qortal.git
synced 2025-07-30 13:41:23 +00:00
Compare commits
13 Commits
reduce-rew
...
qora-holde
Author | SHA1 | Date | |
---|---|---|---|
|
90e8cfc737 | ||
|
57bd3c3459 | ||
|
ad0d8fac91 | ||
|
a8b58d2007 | ||
|
a099ecf55b | ||
|
6b91b0477d | ||
|
d7e7c1f48c | ||
|
7c5932a512 | ||
|
610a3fcf83 | ||
|
b329dc41bc | ||
|
ef249066cd | ||
|
ca7d58c272 | ||
|
10a578428b |
2
pom.xml
2
pom.xml
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.qortal</groupId>
|
||||
<artifactId>qortal</artifactId>
|
||||
<version>3.4.0</version>
|
||||
<version>3.4.1</version>
|
||||
<packaging>jar</packaging>
|
||||
<properties>
|
||||
<skipTests>true</skipTests>
|
||||
|
@@ -8,6 +8,7 @@ import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.security.Security;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@@ -18,6 +19,8 @@ import org.qortal.api.ApiRequest;
|
||||
import org.qortal.controller.AutoUpdate;
|
||||
import org.qortal.settings.Settings;
|
||||
|
||||
import static org.qortal.controller.AutoUpdate.AGENTLIB_JVM_HOLDER_ARG;
|
||||
|
||||
public class ApplyUpdate {
|
||||
|
||||
static {
|
||||
@@ -197,6 +200,11 @@ public class ApplyUpdate {
|
||||
// JVM arguments
|
||||
javaCmd.addAll(ManagementFactory.getRuntimeMXBean().getInputArguments());
|
||||
|
||||
// Reapply any retained, but disabled, -agentlib JVM arg
|
||||
javaCmd = javaCmd.stream()
|
||||
.map(arg -> arg.replace(AGENTLIB_JVM_HOLDER_ARG, "-agentlib"))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Call mainClass in JAR
|
||||
javaCmd.addAll(Arrays.asList("-jar", JAR_FILENAME));
|
||||
|
||||
@@ -205,7 +213,7 @@ public class ApplyUpdate {
|
||||
}
|
||||
|
||||
try {
|
||||
LOGGER.info(() -> String.format("Restarting node with: %s", String.join(" ", javaCmd)));
|
||||
LOGGER.info(String.format("Restarting node with: %s", String.join(" ", javaCmd)));
|
||||
|
||||
ProcessBuilder processBuilder = new ProcessBuilder(javaCmd);
|
||||
|
||||
@@ -214,8 +222,15 @@ public class ApplyUpdate {
|
||||
processBuilder.environment().put(JAVA_TOOL_OPTIONS_NAME, JAVA_TOOL_OPTIONS_VALUE);
|
||||
}
|
||||
|
||||
processBuilder.start();
|
||||
} catch (IOException e) {
|
||||
// New process will inherit our stdout and stderr
|
||||
processBuilder.redirectOutput(ProcessBuilder.Redirect.INHERIT);
|
||||
processBuilder.redirectError(ProcessBuilder.Redirect.INHERIT);
|
||||
|
||||
Process process = processBuilder.start();
|
||||
|
||||
// Nothing to pipe to new process, so close output stream (process's stdin)
|
||||
process.getOutputStream().close();
|
||||
} catch (Exception e) {
|
||||
LOGGER.error(String.format("Failed to restart node (BAD): %s", e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@ import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
|
||||
import org.qortal.controller.Controller;
|
||||
import org.qortal.controller.OnlineAccountsManager;
|
||||
import org.qortal.controller.Synchronizer;
|
||||
import org.qortal.network.Network;
|
||||
|
||||
@@ -21,7 +22,7 @@ public class NodeStatus {
|
||||
public final int height;
|
||||
|
||||
public NodeStatus() {
|
||||
this.isMintingPossible = Controller.getInstance().isMintingPossible();
|
||||
this.isMintingPossible = OnlineAccountsManager.getInstance().hasActiveOnlineAccountSignatures();
|
||||
|
||||
this.syncPercent = Synchronizer.getInstance().getSyncPercent();
|
||||
this.isSynchronizing = Synchronizer.getInstance().isSynchronizing();
|
||||
|
@@ -125,12 +125,12 @@ public class AdminResource {
|
||||
}
|
||||
|
||||
private String getNodeType() {
|
||||
if (Settings.getInstance().isTopOnly()) {
|
||||
return "topOnly";
|
||||
}
|
||||
else if (Settings.getInstance().isLite()) {
|
||||
if (Settings.getInstance().isLite()) {
|
||||
return "lite";
|
||||
}
|
||||
else if (Settings.getInstance().isTopOnly()) {
|
||||
return "topOnly";
|
||||
}
|
||||
else {
|
||||
return "full";
|
||||
}
|
||||
|
@@ -114,7 +114,7 @@ public class BlocksResource {
|
||||
@Path("/signature/{signature}/data")
|
||||
@Operation(
|
||||
summary = "Fetch serialized, base58 encoded block data using base58 signature",
|
||||
description = "Returns serialized data for the block that matches the given signature",
|
||||
description = "Returns serialized data for the block that matches the given signature, and an optional block serialization version",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
description = "the block data",
|
||||
@@ -125,7 +125,7 @@ public class BlocksResource {
|
||||
@ApiErrors({
|
||||
ApiError.INVALID_SIGNATURE, ApiError.BLOCK_UNKNOWN, ApiError.INVALID_DATA, ApiError.REPOSITORY_ISSUE
|
||||
})
|
||||
public String getSerializedBlockData(@PathParam("signature") String signature58) {
|
||||
public String getSerializedBlockData(@PathParam("signature") String signature58, @QueryParam("version") Integer version) {
|
||||
// Decode signature
|
||||
byte[] signature;
|
||||
try {
|
||||
@@ -136,20 +136,41 @@ public class BlocksResource {
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
// Default to version 1
|
||||
if (version == null) {
|
||||
version = 1;
|
||||
}
|
||||
|
||||
// Check the database first
|
||||
BlockData blockData = repository.getBlockRepository().fromSignature(signature);
|
||||
if (blockData != null) {
|
||||
Block block = new Block(repository, blockData);
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
bytes.write(Ints.toByteArray(block.getBlockData().getHeight()));
|
||||
bytes.write(BlockTransformer.toBytes(block));
|
||||
|
||||
switch (version) {
|
||||
case 1:
|
||||
bytes.write(BlockTransformer.toBytes(block));
|
||||
break;
|
||||
|
||||
case 2:
|
||||
bytes.write(BlockTransformer.toBytesV2(block));
|
||||
break;
|
||||
|
||||
default:
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||
}
|
||||
|
||||
return Base58.encode(bytes.toByteArray());
|
||||
}
|
||||
|
||||
// Not found, so try the block archive
|
||||
byte[] bytes = BlockArchiveReader.getInstance().fetchSerializedBlockBytesForSignature(signature, false, repository);
|
||||
if (bytes != null) {
|
||||
return Base58.encode(bytes);
|
||||
if (version != 1) {
|
||||
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, "Archived blocks require version 1");
|
||||
}
|
||||
return Base58.encode(bytes);
|
||||
}
|
||||
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BLOCK_UNKNOWN);
|
||||
|
@@ -1914,7 +1914,7 @@ public class Block {
|
||||
// Fetch list of legacy QORA holders who haven't reached their cap of QORT reward.
|
||||
List<EligibleQoraHolderData> qoraHolders = this.repository.getAccountRepository().getEligibleLegacyQoraHolders(isProcessingNotOrphaning ? null : this.blockData.getHeight());
|
||||
final boolean haveQoraHolders = !qoraHolders.isEmpty();
|
||||
final long qoraHoldersShare = BlockChain.getInstance().getQoraHoldersShare();
|
||||
final long qoraHoldersShare = BlockChain.getInstance().getQoraHoldersShareAtHeight(this.blockData.getHeight());
|
||||
|
||||
// Perform account-level-based reward scaling if appropriate
|
||||
if (!haveFounders) {
|
||||
|
@@ -113,9 +113,13 @@ public class BlockChain {
|
||||
/** Generated lookup of share-bin by account level */
|
||||
private AccountLevelShareBin[] shareBinsByLevel;
|
||||
|
||||
/** Share of block reward/fees to legacy QORA coin holders */
|
||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||
private Long qoraHoldersShare;
|
||||
/** Share of block reward/fees to legacy QORA coin holders, by block height */
|
||||
public static class ShareByHeight {
|
||||
public int height;
|
||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||
public long share;
|
||||
}
|
||||
private List<ShareByHeight> qoraHoldersShareByHeight;
|
||||
|
||||
/** How many legacy QORA per 1 QORT of block reward. */
|
||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||
@@ -354,10 +358,6 @@ public class BlockChain {
|
||||
return this.cumulativeBlocksByLevel;
|
||||
}
|
||||
|
||||
public long getQoraHoldersShare() {
|
||||
return this.qoraHoldersShare;
|
||||
}
|
||||
|
||||
public long getQoraPerQortReward() {
|
||||
return this.qoraPerQortReward;
|
||||
}
|
||||
@@ -468,6 +468,15 @@ public class BlockChain {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long getQoraHoldersShareAtHeight(int ourHeight) {
|
||||
// Scan through for QORA share at our height
|
||||
for (int i = qoraHoldersShareByHeight.size() - 1; i >= 0; --i)
|
||||
if (qoraHoldersShareByHeight.get(i).height <= ourHeight)
|
||||
return qoraHoldersShareByHeight.get(i).share;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Validate blockchain config read from JSON */
|
||||
private void validateConfig() {
|
||||
if (this.genesisInfo == null)
|
||||
@@ -479,8 +488,8 @@ public class BlockChain {
|
||||
if (this.sharesByLevel == null)
|
||||
Settings.throwValidationError("No \"sharesByLevel\" entry found in blockchain config");
|
||||
|
||||
if (this.qoraHoldersShare == null)
|
||||
Settings.throwValidationError("No \"qoraHoldersShare\" entry found in blockchain config");
|
||||
if (this.qoraHoldersShareByHeight == null)
|
||||
Settings.throwValidationError("No \"qoraHoldersShareByHeight\" entry found in blockchain config");
|
||||
|
||||
if (this.qoraPerQortReward == null)
|
||||
Settings.throwValidationError("No \"qoraPerQortReward\" entry found in blockchain config");
|
||||
@@ -518,7 +527,7 @@ public class BlockChain {
|
||||
Settings.throwValidationError(String.format("Missing feature trigger \"%s\" in blockchain config", featureTrigger.name()));
|
||||
|
||||
// Check block reward share bounds
|
||||
long totalShare = this.qoraHoldersShare;
|
||||
long totalShare = this.getQoraHoldersShareAtHeight(1);
|
||||
// Add share percents for account-level-based rewards
|
||||
for (AccountLevelShareBin accountLevelShareBin : this.sharesByLevel)
|
||||
totalShare += accountLevelShareBin.share;
|
||||
@@ -556,6 +565,7 @@ public class BlockChain {
|
||||
this.blocksNeededByLevel = Collections.unmodifiableList(this.blocksNeededByLevel);
|
||||
this.cumulativeBlocksByLevel = Collections.unmodifiableList(this.cumulativeBlocksByLevel);
|
||||
this.blockTimingsByHeight = Collections.unmodifiableList(this.blockTimingsByHeight);
|
||||
this.qoraHoldersShareByHeight = Collections.unmodifiableList(this.qoraHoldersShareByHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -15,6 +15,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@@ -40,6 +41,7 @@ public class AutoUpdate extends Thread {
|
||||
|
||||
public static final String JAR_FILENAME = "qortal.jar";
|
||||
public static final String NEW_JAR_FILENAME = "new-" + JAR_FILENAME;
|
||||
public static final String AGENTLIB_JVM_HOLDER_ARG = "-DQORTAL_agentlib=";
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(AutoUpdate.class);
|
||||
private static final long CHECK_INTERVAL = 20 * 60 * 1000L; // ms
|
||||
@@ -243,6 +245,11 @@ public class AutoUpdate extends Thread {
|
||||
// JVM arguments
|
||||
javaCmd.addAll(ManagementFactory.getRuntimeMXBean().getInputArguments());
|
||||
|
||||
// Disable, but retain, any -agentlib JVM arg as sub-process might fail if it tries to reuse same port
|
||||
javaCmd = javaCmd.stream()
|
||||
.map(arg -> arg.replace("-agentlib", AGENTLIB_JVM_HOLDER_ARG))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Remove JNI options as they won't be supported by command-line 'java'
|
||||
// These are typically added by the AdvancedInstaller Java launcher EXE
|
||||
javaCmd.removeAll(Arrays.asList("abort", "exit", "vfprintf"));
|
||||
@@ -261,10 +268,19 @@ public class AutoUpdate extends Thread {
|
||||
Translator.INSTANCE.translate("SysTray", "APPLYING_UPDATE_AND_RESTARTING"),
|
||||
MessageType.INFO);
|
||||
|
||||
new ProcessBuilder(javaCmd).start();
|
||||
ProcessBuilder processBuilder = new ProcessBuilder(javaCmd);
|
||||
|
||||
// New process will inherit our stdout and stderr
|
||||
processBuilder.redirectOutput(ProcessBuilder.Redirect.INHERIT);
|
||||
processBuilder.redirectError(ProcessBuilder.Redirect.INHERIT);
|
||||
|
||||
Process process = processBuilder.start();
|
||||
|
||||
// Nothing to pipe to new process, so close output stream (process's stdin)
|
||||
process.getOutputStream().close();
|
||||
|
||||
return true; // applying update OK
|
||||
} catch (IOException e) {
|
||||
} catch (Exception e) {
|
||||
LOGGER.error(String.format("Failed to apply update: %s", e.getMessage()));
|
||||
|
||||
try {
|
||||
|
@@ -819,7 +819,7 @@ public class Controller extends Thread {
|
||||
actionText = String.format("%s", Translator.INSTANCE.translate("SysTray", "SYNCHRONIZING_BLOCKCHAIN"));
|
||||
SysTray.getInstance().setTrayIcon(3);
|
||||
}
|
||||
else if (OnlineAccountsManager.getInstance().hasOnlineAccounts()) {
|
||||
else if (OnlineAccountsManager.getInstance().hasActiveOnlineAccountSignatures()) {
|
||||
actionText = Translator.INSTANCE.translate("SysTray", "MINTING_ENABLED");
|
||||
SysTray.getInstance().setTrayIcon(2);
|
||||
}
|
||||
|
@@ -54,7 +54,7 @@ public class OnlineAccountsManager {
|
||||
private static final long ONLINE_ACCOUNTS_BROADCAST_INTERVAL = 15 * 1000L; // ms
|
||||
|
||||
private static final long ONLINE_ACCOUNTS_V2_PEER_VERSION = 0x0300020000L; // v3.2.0
|
||||
private static final long ONLINE_ACCOUNTS_V3_PEER_VERSION = 0x03000300cbL; // v3.3.203
|
||||
private static final long ONLINE_ACCOUNTS_V3_PEER_VERSION = 0x0300040000L; // v3.4.0
|
||||
|
||||
private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(4, new NamedThreadFactory("OnlineAccounts"));
|
||||
private volatile boolean isStopping = false;
|
||||
@@ -462,6 +462,18 @@ public class OnlineAccountsManager {
|
||||
return this.currentOnlineAccounts.containsKey(onlineAccountsTimestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether we have submitted - or attempted to submit - our online account
|
||||
* signature(s) to the network.
|
||||
* @return true if our signature(s) have been submitted recently.
|
||||
*/
|
||||
public boolean hasActiveOnlineAccountSignatures() {
|
||||
final Long minLatestBlockTimestamp = NTP.getTime() - (2 * 60 * 60 * 1000L);
|
||||
boolean isUpToDate = Controller.getInstance().isUpToDate(minLatestBlockTimestamp);
|
||||
|
||||
return isUpToDate && hasOurOnlineAccounts();
|
||||
}
|
||||
|
||||
public boolean hasOurOnlineAccounts() {
|
||||
return this.hasOurOnlineAccounts;
|
||||
}
|
||||
|
@@ -375,7 +375,7 @@ public abstract class Bitcoiny implements ForeignBlockchain {
|
||||
|
||||
public Long getWalletBalanceFromTransactions(String key58) throws ForeignBlockchainException {
|
||||
long balance = 0;
|
||||
Comparator<SimpleTransaction> oldestTimestampFirstComparator = Comparator.comparingInt(SimpleTransaction::getTimestamp);
|
||||
Comparator<SimpleTransaction> oldestTimestampFirstComparator = Comparator.comparingLong(SimpleTransaction::getTimestamp);
|
||||
List<SimpleTransaction> transactions = getWalletTransactions(key58).stream().sorted(oldestTimestampFirstComparator).collect(Collectors.toList());
|
||||
for (SimpleTransaction transaction : transactions) {
|
||||
balance += transaction.getTotalAmount();
|
||||
@@ -455,7 +455,7 @@ public abstract class Bitcoiny implements ForeignBlockchain {
|
||||
// Process new keys
|
||||
} while (true);
|
||||
|
||||
Comparator<SimpleTransaction> newestTimestampFirstComparator = Comparator.comparingInt(SimpleTransaction::getTimestamp).reversed();
|
||||
Comparator<SimpleTransaction> newestTimestampFirstComparator = Comparator.comparingLong(SimpleTransaction::getTimestamp).reversed();
|
||||
|
||||
// Update cache and return
|
||||
transactionsCacheTimestamp = NTP.getTime();
|
||||
@@ -537,7 +537,8 @@ public abstract class Bitcoiny implements ForeignBlockchain {
|
||||
// All inputs and outputs relate to this wallet, so the balance should be unaffected
|
||||
amount = 0;
|
||||
}
|
||||
return new SimpleTransaction(t.txHash, t.timestamp, amount, fee, inputs, outputs);
|
||||
long timestampMillis = t.timestamp * 1000L;
|
||||
return new SimpleTransaction(t.txHash, timestampMillis, amount, fee, inputs, outputs);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -7,7 +7,7 @@ import java.util.List;
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class SimpleTransaction {
|
||||
private String txHash;
|
||||
private Integer timestamp;
|
||||
private Long timestamp;
|
||||
private long totalAmount;
|
||||
private long feeAmount;
|
||||
private List<Input> inputs;
|
||||
@@ -74,7 +74,7 @@ public class SimpleTransaction {
|
||||
public SimpleTransaction() {
|
||||
}
|
||||
|
||||
public SimpleTransaction(String txHash, Integer timestamp, long totalAmount, long feeAmount, List<Input> inputs, List<Output> outputs) {
|
||||
public SimpleTransaction(String txHash, Long timestamp, long totalAmount, long feeAmount, List<Input> inputs, List<Output> outputs) {
|
||||
this.txHash = txHash;
|
||||
this.timestamp = timestamp;
|
||||
this.totalAmount = totalAmount;
|
||||
@@ -87,7 +87,7 @@ public class SimpleTransaction {
|
||||
return txHash;
|
||||
}
|
||||
|
||||
public Integer getTimestamp() {
|
||||
public Long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
|
@@ -18,7 +18,7 @@
|
||||
"maxRewardSharesPerFounderMintingAccount": 6,
|
||||
"maxRewardSharesByTimestamp": [
|
||||
{ "timestamp": 0, "maxShares": 6 },
|
||||
{ "timestamp": 9999999999999, "maxShares": 3 }
|
||||
{ "timestamp": 1657382400000, "maxShares": 3 }
|
||||
],
|
||||
"founderEffectiveMintingLevel": 10,
|
||||
"onlineAccountSignaturesMinLifetime": 43200000,
|
||||
@@ -45,7 +45,10 @@
|
||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
||||
],
|
||||
"qoraHoldersShare": 0.20,
|
||||
"qoraHoldersShareByHeight": [
|
||||
{ "height": 1, "share": 0.20 },
|
||||
{ "height": 9999999, "share": 0.01 }
|
||||
],
|
||||
"qoraPerQortReward": 250,
|
||||
"blocksNeededByLevel": [ 7200, 64800, 129600, 172800, 244000, 345600, 518400, 691200, 864000, 1036800 ],
|
||||
"blockTimingsByHeight": [
|
||||
@@ -61,7 +64,7 @@
|
||||
"atFindNextTransactionFix": 275000,
|
||||
"newBlockSigHeight": 320000,
|
||||
"shareBinFix": 399000,
|
||||
"rewardShareLimitTimestamp": 9999999999999,
|
||||
"rewardShareLimitTimestamp": 1657382400000,
|
||||
"calcChainWeightTimestamp": 1620579600000,
|
||||
"transactionV5Timestamp": 1642176000000,
|
||||
"transactionV6Timestamp": 9999999999999,
|
||||
|
@@ -75,7 +75,7 @@ public class GetWalletTransactions {
|
||||
|
||||
System.out.println(String.format("Found %d transaction%s", transactions.size(), (transactions.size() != 1 ? "s" : "")));
|
||||
|
||||
for (SimpleTransaction transaction : transactions.stream().sorted(Comparator.comparingInt(SimpleTransaction::getTimestamp)).collect(Collectors.toList()))
|
||||
for (SimpleTransaction transaction : transactions.stream().sorted(Comparator.comparingLong(SimpleTransaction::getTimestamp)).collect(Collectors.toList()))
|
||||
System.out.println(String.format("%s", transaction));
|
||||
}
|
||||
|
||||
|
@@ -4,6 +4,7 @@ import static org.junit.Assert.*;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -14,6 +15,7 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.asset.Asset;
|
||||
import org.qortal.block.Block;
|
||||
import org.qortal.block.BlockChain;
|
||||
import org.qortal.block.BlockChain.RewardByHeight;
|
||||
import org.qortal.controller.BlockMinter;
|
||||
@@ -109,7 +111,7 @@ public class RewardTests extends Common {
|
||||
public void testLegacyQoraReward() throws DataException {
|
||||
Common.useSettings("test-settings-v2-qora-holder-extremes.json");
|
||||
|
||||
long qoraHoldersShare = BlockChain.getInstance().getQoraHoldersShare();
|
||||
long qoraHoldersShare = BlockChain.getInstance().getQoraHoldersShareAtHeight(1);
|
||||
BigInteger qoraHoldersShareBI = BigInteger.valueOf(qoraHoldersShare);
|
||||
|
||||
long qoraPerQort = BlockChain.getInstance().getQoraPerQortReward();
|
||||
@@ -190,6 +192,47 @@ public class RewardTests extends Common {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLegacyQoraRewardReduction() throws DataException {
|
||||
Common.useSettings("test-settings-v2-qora-holder-extremes.json");
|
||||
|
||||
// Make sure that the QORA share reduces between blocks 4 and 5
|
||||
assertTrue(BlockChain.getInstance().getQoraHoldersShareAtHeight(5) < BlockChain.getInstance().getQoraHoldersShareAtHeight(4));
|
||||
|
||||
// Keep track of balance deltas at each height
|
||||
Map<Integer, Long> chloeQortBalanceDeltaAtEachHeight = new HashMap<>();
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
Map<String, Map<Long, Long>> initialBalances = AccountUtils.getBalances(repository, Asset.QORT, Asset.LEGACY_QORA, Asset.QORT_FROM_QORA);
|
||||
long chloeLastQortBalance = initialBalances.get("chloe").get(Asset.QORT);
|
||||
|
||||
for (int i=2; i<=10; i++) {
|
||||
|
||||
Block block = BlockUtils.mintBlock(repository);
|
||||
|
||||
// Add to map of balance deltas at each height
|
||||
long chloeNewQortBalance = AccountUtils.getBalance(repository, "chloe", Asset.QORT);
|
||||
chloeQortBalanceDeltaAtEachHeight.put(block.getBlockData().getHeight(), chloeNewQortBalance - chloeLastQortBalance);
|
||||
chloeLastQortBalance = chloeNewQortBalance;
|
||||
}
|
||||
|
||||
// Ensure blocks 2-4 paid out the same rewards to Chloe
|
||||
assertEquals(chloeQortBalanceDeltaAtEachHeight.get(2), chloeQortBalanceDeltaAtEachHeight.get(4));
|
||||
|
||||
// Ensure block 5 paid a lower reward
|
||||
assertTrue(chloeQortBalanceDeltaAtEachHeight.get(5) < chloeQortBalanceDeltaAtEachHeight.get(4));
|
||||
|
||||
// Check that the reward was 20x lower
|
||||
assertTrue(chloeQortBalanceDeltaAtEachHeight.get(5) == chloeQortBalanceDeltaAtEachHeight.get(4) / 20);
|
||||
|
||||
// Orphan to block 4 and ensure that Chloe's balance hasn't been incorrectly affected by the reward reduction
|
||||
BlockUtils.orphanToBlock(repository, 4);
|
||||
long expectedChloeQortBalance = initialBalances.get("chloe").get(Asset.QORT) + chloeQortBalanceDeltaAtEachHeight.get(2) +
|
||||
chloeQortBalanceDeltaAtEachHeight.get(3) + chloeQortBalanceDeltaAtEachHeight.get(4);
|
||||
assertEquals(expectedChloeQortBalance, AccountUtils.getBalance(repository, "chloe", Asset.QORT));
|
||||
}
|
||||
}
|
||||
|
||||
/** Use Alice-Chloe reward-share to bump Chloe from level 0 to level 1, then check orphaning works as expected. */
|
||||
@Test
|
||||
public void testLevel1() throws DataException {
|
||||
@@ -295,7 +338,7 @@ public class RewardTests extends Common {
|
||||
* So Dilbert should receive 100% - legacy QORA holder's share.
|
||||
*/
|
||||
|
||||
final long qoraHoldersShare = BlockChain.getInstance().getQoraHoldersShare();
|
||||
final long qoraHoldersShare = BlockChain.getInstance().getQoraHoldersShareAtHeight(1);
|
||||
final long remainingShare = 1_00000000 - qoraHoldersShare;
|
||||
|
||||
long dilbertExpectedBalance = initialBalances.get("dilbert").get(Asset.QORT);
|
||||
|
@@ -26,7 +26,10 @@
|
||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
||||
],
|
||||
"qoraHoldersShare": 0.20,
|
||||
"qoraHoldersShareByHeight": [
|
||||
{ "height": 1, "share": 0.20 },
|
||||
{ "height": 1000000, "share": 0.01 }
|
||||
],
|
||||
"qoraPerQortReward": 250,
|
||||
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
||||
"blockTimingsByHeight": [
|
||||
|
@@ -30,7 +30,10 @@
|
||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
||||
],
|
||||
"qoraHoldersShare": 0.20,
|
||||
"qoraHoldersShareByHeight": [
|
||||
{ "height": 1, "share": 0.20 },
|
||||
{ "height": 1000000, "share": 0.01 }
|
||||
],
|
||||
"qoraPerQortReward": 250,
|
||||
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
||||
"blockTimingsByHeight": [
|
||||
|
@@ -30,7 +30,10 @@
|
||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
||||
],
|
||||
"qoraHoldersShare": 0.20,
|
||||
"qoraHoldersShareByHeight": [
|
||||
{ "height": 1, "share": 0.20 },
|
||||
{ "height": 1000000, "share": 0.01 }
|
||||
],
|
||||
"qoraPerQortReward": 250,
|
||||
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
||||
"blockTimingsByHeight": [
|
||||
|
@@ -30,7 +30,10 @@
|
||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
||||
],
|
||||
"qoraHoldersShare": 0.20,
|
||||
"qoraHoldersShareByHeight": [
|
||||
{ "height": 1, "share": 0.20 },
|
||||
{ "height": 1000000, "share": 0.01 }
|
||||
],
|
||||
"qoraPerQortReward": 250,
|
||||
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
||||
"blockTimingsByHeight": [
|
||||
|
@@ -30,7 +30,10 @@
|
||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
||||
],
|
||||
"qoraHoldersShare": 0.20,
|
||||
"qoraHoldersShareByHeight": [
|
||||
{ "height": 1, "share": 0.20 },
|
||||
{ "height": 1000000, "share": 0.01 }
|
||||
],
|
||||
"qoraPerQortReward": 250,
|
||||
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
||||
"blockTimingsByHeight": [
|
||||
|
@@ -30,7 +30,10 @@
|
||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
||||
],
|
||||
"qoraHoldersShare": 0.20,
|
||||
"qoraHoldersShareByHeight": [
|
||||
{ "height": 1, "share": 0.20 },
|
||||
{ "height": 5, "share": 0.01 }
|
||||
],
|
||||
"qoraPerQortReward": 250,
|
||||
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
||||
"blockTimingsByHeight": [
|
||||
|
@@ -30,7 +30,10 @@
|
||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
||||
],
|
||||
"qoraHoldersShare": 0.20,
|
||||
"qoraHoldersShareByHeight": [
|
||||
{ "height": 1, "share": 0.20 },
|
||||
{ "height": 1000000, "share": 0.01 }
|
||||
],
|
||||
"qoraPerQortReward": 250,
|
||||
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
||||
"blockTimingsByHeight": [
|
||||
|
@@ -30,7 +30,10 @@
|
||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
||||
],
|
||||
"qoraHoldersShare": 0.20,
|
||||
"qoraHoldersShareByHeight": [
|
||||
{ "height": 1, "share": 0.20 },
|
||||
{ "height": 1000000, "share": 0.01 }
|
||||
],
|
||||
"qoraPerQortReward": 250,
|
||||
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
||||
"blockTimingsByHeight": [
|
||||
|
@@ -30,7 +30,10 @@
|
||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
||||
],
|
||||
"qoraHoldersShare": 0.20,
|
||||
"qoraHoldersShareByHeight": [
|
||||
{ "height": 1, "share": 0.20 },
|
||||
{ "height": 1000000, "share": 0.01 }
|
||||
],
|
||||
"qoraPerQortReward": 250,
|
||||
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
||||
"blockTimingsByHeight": [
|
||||
|
@@ -30,7 +30,10 @@
|
||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
||||
],
|
||||
"qoraHoldersShare": 0.20,
|
||||
"qoraHoldersShareByHeight": [
|
||||
{ "height": 1, "share": 0.20 },
|
||||
{ "height": 1000000, "share": 0.01 }
|
||||
],
|
||||
"qoraPerQortReward": 250,
|
||||
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
||||
"blockTimingsByHeight": [
|
||||
|
@@ -30,7 +30,10 @@
|
||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
||||
],
|
||||
"qoraHoldersShare": 0.20,
|
||||
"qoraHoldersShareByHeight": [
|
||||
{ "height": 1, "share": 0.20 },
|
||||
{ "height": 1000000, "share": 0.01 }
|
||||
],
|
||||
"qoraPerQortReward": 250,
|
||||
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
||||
"blockTimingsByHeight": [
|
||||
|
Reference in New Issue
Block a user