forked from Qortal/qortal
Merge pull request #207 from AlphaX-Reloaded/master
Merging - adding features for 'emergency update', modifications to testnet config and scripts, and reindex functionality for sync-from-genesis.
This commit is contained in:
commit
524ed2b301
22
pom.xml
22
pom.xml
@ -16,14 +16,14 @@
|
|||||||
<ciyam-at.version>1.4.2</ciyam-at.version>
|
<ciyam-at.version>1.4.2</ciyam-at.version>
|
||||||
<commons-net.version>3.8.0</commons-net.version>
|
<commons-net.version>3.8.0</commons-net.version>
|
||||||
<commons-text.version>1.12.0</commons-text.version>
|
<commons-text.version>1.12.0</commons-text.version>
|
||||||
<commons-io.version>2.16.1</commons-io.version>
|
<commons-io.version>2.17.0</commons-io.version>
|
||||||
<commons-compress.version>1.26.2</commons-compress.version>
|
<commons-compress.version>1.27.1</commons-compress.version>
|
||||||
<commons-lang3.version>3.14.0</commons-lang3.version>
|
<commons-lang3.version>3.17.0</commons-lang3.version>
|
||||||
<dagger.version>1.2.2</dagger.version>
|
<dagger.version>1.2.2</dagger.version>
|
||||||
<extendedset.version>0.12.3</extendedset.version>
|
<extendedset.version>0.12.3</extendedset.version>
|
||||||
<git-commit-id-plugin.version>4.9.10</git-commit-id-plugin.version>
|
<git-commit-id-plugin.version>4.9.10</git-commit-id-plugin.version>
|
||||||
<grpc.version>1.65.0</grpc.version>
|
<grpc.version>1.66.0</grpc.version>
|
||||||
<guava.version>33.2.1-jre</guava.version>
|
<guava.version>33.3.0-jre</guava.version>
|
||||||
<hamcrest-library.version>2.2</hamcrest-library.version>
|
<hamcrest-library.version>2.2</hamcrest-library.version>
|
||||||
<homoglyph.version>1.2.1</homoglyph.version>
|
<homoglyph.version>1.2.1</homoglyph.version>
|
||||||
<hsqldb.version>2.5.1</hsqldb.version>
|
<hsqldb.version>2.5.1</hsqldb.version>
|
||||||
@ -32,10 +32,10 @@
|
|||||||
<javax.servlet-api.version>4.0.1</javax.servlet-api.version>
|
<javax.servlet-api.version>4.0.1</javax.servlet-api.version>
|
||||||
<jaxb-runtime.version>2.3.9</jaxb-runtime.version>
|
<jaxb-runtime.version>2.3.9</jaxb-runtime.version>
|
||||||
<jersey.version>2.42</jersey.version>
|
<jersey.version>2.42</jersey.version>
|
||||||
<jetty.version>9.4.54.v20240208</jetty.version>
|
<jetty.version>9.4.56.v20240826</jetty.version>
|
||||||
<json-simple.version>1.1.1</json-simple.version>
|
<json-simple.version>1.1.1</json-simple.version>
|
||||||
<json.version>20240303</json.version>
|
<json.version>20240303</json.version>
|
||||||
<jsoup.version>1.17.2</jsoup.version>
|
<jsoup.version>1.18.1</jsoup.version>
|
||||||
<junit-jupiter-engine.version>5.11.0-M2</junit-jupiter-engine.version>
|
<junit-jupiter-engine.version>5.11.0-M2</junit-jupiter-engine.version>
|
||||||
<lifecycle-mapping.version>1.0.0</lifecycle-mapping.version>
|
<lifecycle-mapping.version>1.0.0</lifecycle-mapping.version>
|
||||||
<log4j.version>2.23.1</log4j.version>
|
<log4j.version>2.23.1</log4j.version>
|
||||||
@ -45,11 +45,11 @@
|
|||||||
<maven-dependency-plugin.version>3.6.1</maven-dependency-plugin.version>
|
<maven-dependency-plugin.version>3.6.1</maven-dependency-plugin.version>
|
||||||
<maven-jar-plugin.version>3.4.2</maven-jar-plugin.version>
|
<maven-jar-plugin.version>3.4.2</maven-jar-plugin.version>
|
||||||
<maven-package-info-plugin.version>1.1.0</maven-package-info-plugin.version>
|
<maven-package-info-plugin.version>1.1.0</maven-package-info-plugin.version>
|
||||||
<maven-plugin.version>2.16.2</maven-plugin.version>
|
<maven-plugin.version>2.17.1</maven-plugin.version>
|
||||||
<maven-reproducible-build-plugin.version>0.16</maven-reproducible-build-plugin.version>
|
<maven-reproducible-build-plugin.version>0.17</maven-reproducible-build-plugin.version>
|
||||||
<maven-resources-plugin.version>3.3.1</maven-resources-plugin.version>
|
<maven-resources-plugin.version>3.3.1</maven-resources-plugin.version>
|
||||||
<maven-shade-plugin.version>3.6.0</maven-shade-plugin.version>
|
<maven-shade-plugin.version>3.6.0</maven-shade-plugin.version>
|
||||||
<maven-surefire-plugin.version>3.3.0</maven-surefire-plugin.version>
|
<maven-surefire-plugin.version>3.5.0</maven-surefire-plugin.version>
|
||||||
<protobuf.version>3.25.3</protobuf.version>
|
<protobuf.version>3.25.3</protobuf.version>
|
||||||
<replacer.version>1.5.3</replacer.version>
|
<replacer.version>1.5.3</replacer.version>
|
||||||
<simplemagic.version>1.17</simplemagic.version>
|
<simplemagic.version>1.17</simplemagic.version>
|
||||||
@ -57,7 +57,7 @@
|
|||||||
<swagger-api.version>2.0.10</swagger-api.version>
|
<swagger-api.version>2.0.10</swagger-api.version>
|
||||||
<swagger-ui.version>5.17.14</swagger-ui.version>
|
<swagger-ui.version>5.17.14</swagger-ui.version>
|
||||||
<upnp.version>1.2</upnp.version>
|
<upnp.version>1.2</upnp.version>
|
||||||
<xz.version>1.9</xz.version>
|
<xz.version>1.10</xz.version>
|
||||||
</properties>
|
</properties>
|
||||||
<build>
|
<build>
|
||||||
<sourceDirectory>src/main/java</sourceDirectory>
|
<sourceDirectory>src/main/java</sourceDirectory>
|
||||||
|
@ -7,7 +7,10 @@ import org.qortal.controller.LiteNode;
|
|||||||
import org.qortal.data.account.AccountBalanceData;
|
import org.qortal.data.account.AccountBalanceData;
|
||||||
import org.qortal.data.account.AccountData;
|
import org.qortal.data.account.AccountData;
|
||||||
import org.qortal.data.account.RewardShareData;
|
import org.qortal.data.account.RewardShareData;
|
||||||
|
import org.qortal.data.naming.NameData;
|
||||||
import org.qortal.repository.DataException;
|
import org.qortal.repository.DataException;
|
||||||
|
import org.qortal.repository.GroupRepository;
|
||||||
|
import org.qortal.repository.NameRepository;
|
||||||
import org.qortal.repository.Repository;
|
import org.qortal.repository.Repository;
|
||||||
import org.qortal.settings.Settings;
|
import org.qortal.settings.Settings;
|
||||||
import org.qortal.utils.Base58;
|
import org.qortal.utils.Base58;
|
||||||
@ -15,6 +18,8 @@ import org.qortal.utils.Base58;
|
|||||||
import javax.xml.bind.annotation.XmlAccessType;
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static org.qortal.utils.Amounts.prettyAmount;
|
import static org.qortal.utils.Amounts.prettyAmount;
|
||||||
|
|
||||||
@XmlAccessorType(XmlAccessType.NONE) // Stops JAX-RS errors when unmarshalling blockchain config
|
@XmlAccessorType(XmlAccessType.NONE) // Stops JAX-RS errors when unmarshalling blockchain config
|
||||||
@ -193,10 +198,11 @@ public class Account {
|
|||||||
|
|
||||||
/** Returns whether account can be considered a "minting account".
|
/** Returns whether account can be considered a "minting account".
|
||||||
* <p>
|
* <p>
|
||||||
* To be considered a "minting account", the account needs to pass at least one of these tests:<br>
|
* To be considered a "minting account", the account needs to pass all of these tests:<br>
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>account's level is at least <tt>minAccountLevelToMint</tt> from blockchain config</li>
|
* <li>account's level is at least <tt>minAccountLevelToMint</tt> from blockchain config</li>
|
||||||
* <li>account has 'founder' flag set</li>
|
* <li>account's address have registered a name</li>
|
||||||
|
* <li>account's address is member of minter group</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* @return true if account can be considered "minting account"
|
* @return true if account can be considered "minting account"
|
||||||
@ -204,15 +210,53 @@ public class Account {
|
|||||||
*/
|
*/
|
||||||
public boolean canMint() throws DataException {
|
public boolean canMint() throws DataException {
|
||||||
AccountData accountData = this.repository.getAccountRepository().getAccount(this.address);
|
AccountData accountData = this.repository.getAccountRepository().getAccount(this.address);
|
||||||
|
NameRepository nameRepository = this.repository.getNameRepository();
|
||||||
|
GroupRepository groupRepository = this.repository.getGroupRepository();
|
||||||
|
|
||||||
|
int blockchainHeight = this.repository.getBlockRepository().getBlockchainHeight();
|
||||||
|
int nameCheckHeight = BlockChain.getInstance().getOnlyMintWithNameHeight();
|
||||||
|
int levelToMint = BlockChain.getInstance().getMinAccountLevelToMint();
|
||||||
|
int level = accountData.getLevel();
|
||||||
|
int groupIdToMint = BlockChain.getInstance().getMintingGroupId();
|
||||||
|
int groupCheckHeight = BlockChain.getInstance().getGroupMemberCheckHeight();
|
||||||
|
|
||||||
|
String myAddress = accountData.getAddress();
|
||||||
|
List<NameData> myName = nameRepository.getNamesByOwner(myAddress);
|
||||||
|
boolean isMember = groupRepository.memberExists(groupIdToMint, myAddress);
|
||||||
|
|
||||||
if (accountData == null)
|
if (accountData == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Integer level = accountData.getLevel();
|
// Can only mint if level is at least minAccountLevelToMint< from blockchain config
|
||||||
if (level != null && level >= BlockChain.getInstance().getMinAccountLevelToMint())
|
if (blockchainHeight < nameCheckHeight && level >= levelToMint)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
// Founders can always mint, unless they have a penalty
|
// Can only mint if have registered a name
|
||||||
if (Account.isFounder(accountData.getFlags()) && accountData.getBlocksMintedPenalty() == 0)
|
if (blockchainHeight >= nameCheckHeight && blockchainHeight < groupCheckHeight && level >= levelToMint && !myName.isEmpty())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Can only mint if have registered a name and is member of minter group id
|
||||||
|
if (blockchainHeight >= groupCheckHeight && level >= levelToMint && !myName.isEmpty() && isMember)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Founders needs to pass same tests like minters
|
||||||
|
if (blockchainHeight < nameCheckHeight &&
|
||||||
|
Account.isFounder(accountData.getFlags()) &&
|
||||||
|
accountData.getBlocksMintedPenalty() == 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (blockchainHeight >= nameCheckHeight &&
|
||||||
|
blockchainHeight < groupCheckHeight &&
|
||||||
|
Account.isFounder(accountData.getFlags()) &&
|
||||||
|
accountData.getBlocksMintedPenalty() == 0 &&
|
||||||
|
!myName.isEmpty())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (blockchainHeight >= groupCheckHeight &&
|
||||||
|
Account.isFounder(accountData.getFlags()) &&
|
||||||
|
accountData.getBlocksMintedPenalty() == 0 &&
|
||||||
|
!myName.isEmpty() &&
|
||||||
|
isMember)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -35,6 +35,7 @@ import org.qortal.data.account.RewardShareData;
|
|||||||
import org.qortal.network.Network;
|
import org.qortal.network.Network;
|
||||||
import org.qortal.network.Peer;
|
import org.qortal.network.Peer;
|
||||||
import org.qortal.network.PeerAddress;
|
import org.qortal.network.PeerAddress;
|
||||||
|
import org.qortal.repository.ReindexManager;
|
||||||
import org.qortal.repository.DataException;
|
import org.qortal.repository.DataException;
|
||||||
import org.qortal.repository.Repository;
|
import org.qortal.repository.Repository;
|
||||||
import org.qortal.repository.RepositoryManager;
|
import org.qortal.repository.RepositoryManager;
|
||||||
@ -894,6 +895,50 @@ public class AdminResource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/repository/reindex")
|
||||||
|
@Operation(
|
||||||
|
summary = "Reindex repository",
|
||||||
|
description = "Rebuilds all transactions and balances from archived blocks. Warning: takes around 1 week, and the core will not function normally during this time. If 'false' is returned, the database may be left in an inconsistent state, requiring another reindex or a bootstrap to correct it.",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "\"true\"",
|
||||||
|
content = @Content(mediaType = MediaType.TEXT_PLAIN, schema = @Schema(type = "string"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ApiErrors({ApiError.REPOSITORY_ISSUE, ApiError.BLOCKCHAIN_NEEDS_SYNC})
|
||||||
|
@SecurityRequirement(name = "apiKey")
|
||||||
|
public String reindex(@HeaderParam(Security.API_KEY_HEADER) String apiKey) {
|
||||||
|
Security.checkApiCallAllowed(request);
|
||||||
|
|
||||||
|
if (Synchronizer.getInstance().isSynchronizing())
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BLOCKCHAIN_NEEDS_SYNC);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ReentrantLock blockchainLock = Controller.getInstance().getBlockchainLock();
|
||||||
|
|
||||||
|
blockchainLock.lockInterruptibly();
|
||||||
|
|
||||||
|
try {
|
||||||
|
ReindexManager reindexManager = new ReindexManager();
|
||||||
|
reindexManager.reindex();
|
||||||
|
return "true";
|
||||||
|
|
||||||
|
} catch (DataException e) {
|
||||||
|
LOGGER.info("DataException when reindexing: {}", e.getMessage());
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
blockchainLock.unlock();
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// We couldn't lock blockchain to perform reindex
|
||||||
|
return "false";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "false";
|
||||||
|
}
|
||||||
|
|
||||||
@DELETE
|
@DELETE
|
||||||
@Path("/repository")
|
@Path("/repository")
|
||||||
@Operation(
|
@Operation(
|
||||||
@ -966,8 +1011,6 @@ public class AdminResource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("/apikey/generate")
|
@Path("/apikey/generate")
|
||||||
@Operation(
|
@Operation(
|
||||||
|
@ -167,7 +167,7 @@ public enum Service {
|
|||||||
COMMENT(1800, true, 500*1024L, true, false, null),
|
COMMENT(1800, true, 500*1024L, true, false, null),
|
||||||
CHAIN_COMMENT(1810, true, 239L, true, false, null),
|
CHAIN_COMMENT(1810, true, 239L, true, false, null),
|
||||||
MAIL(1900, true, 1024*1024L, true, false, null),
|
MAIL(1900, true, 1024*1024L, true, false, null),
|
||||||
MAIL_PRIVATE(1901, true, 1024*1024L, true, true, null),
|
MAIL_PRIVATE(1901, true, 5*1024*1024L, true, true, null),
|
||||||
MESSAGE(1910, true, 1024*1024L, true, false, null),
|
MESSAGE(1910, true, 1024*1024L, true, false, null),
|
||||||
MESSAGE_PRIVATE(1911, true, 1024*1024L, true, true, null);
|
MESSAGE_PRIVATE(1911, true, 1024*1024L, true, true, null);
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ import org.qortal.repository.ATRepository;
|
|||||||
import org.qortal.repository.DataException;
|
import org.qortal.repository.DataException;
|
||||||
import org.qortal.repository.Repository;
|
import org.qortal.repository.Repository;
|
||||||
import org.qortal.repository.TransactionRepository;
|
import org.qortal.repository.TransactionRepository;
|
||||||
|
import org.qortal.settings.Settings;
|
||||||
import org.qortal.transaction.AtTransaction;
|
import org.qortal.transaction.AtTransaction;
|
||||||
import org.qortal.transaction.Transaction;
|
import org.qortal.transaction.Transaction;
|
||||||
import org.qortal.transaction.Transaction.ApprovalStatus;
|
import org.qortal.transaction.Transaction.ApprovalStatus;
|
||||||
@ -104,6 +105,7 @@ public class Block {
|
|||||||
protected Repository repository;
|
protected Repository repository;
|
||||||
protected BlockData blockData;
|
protected BlockData blockData;
|
||||||
protected PublicKeyAccount minter;
|
protected PublicKeyAccount minter;
|
||||||
|
boolean isTestnet = Settings.getInstance().isTestNet();
|
||||||
|
|
||||||
// Other properties
|
// Other properties
|
||||||
private static final Logger LOGGER = LogManager.getLogger(Block.class);
|
private static final Logger LOGGER = LogManager.getLogger(Block.class);
|
||||||
@ -1281,13 +1283,20 @@ public class Block {
|
|||||||
// Create repository savepoint here so we can rollback to it after testing transactions
|
// Create repository savepoint here so we can rollback to it after testing transactions
|
||||||
repository.setSavepoint();
|
repository.setSavepoint();
|
||||||
|
|
||||||
|
if (!isTestnet) {
|
||||||
if (this.blockData.getHeight() == 212937) {
|
if (this.blockData.getHeight() == 212937) {
|
||||||
// Apply fix for block 212937 but fix will be rolled back before we exit method
|
// Apply fix for block 212937 but fix will be rolled back before we exit method
|
||||||
Block212937.processFix(this);
|
Block212937.processFix(this);
|
||||||
}
|
} else if (this.blockData.getHeight() == 1333492) {
|
||||||
else if (InvalidNameRegistrationBlocks.isAffectedBlock(this.blockData.getHeight())) {
|
// Apply fix for block 1333492 but fix will be rolled back before we exit method
|
||||||
|
Block1333492.processFix(this);
|
||||||
|
} else if (InvalidNameRegistrationBlocks.isAffectedBlock(this.blockData.getHeight())) {
|
||||||
// Apply fix for affected name registration blocks, but fix will be rolled back before we exit method
|
// Apply fix for affected name registration blocks, but fix will be rolled back before we exit method
|
||||||
InvalidNameRegistrationBlocks.processFix(this);
|
InvalidNameRegistrationBlocks.processFix(this);
|
||||||
|
} else if (InvalidBalanceBlocks.isAffectedBlock(this.blockData.getHeight())) {
|
||||||
|
// Apply fix for affected balance blocks, but fix will be rolled back before we exit method
|
||||||
|
InvalidBalanceBlocks.processFix(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Transaction transaction : this.getTransactions()) {
|
for (Transaction transaction : this.getTransactions()) {
|
||||||
@ -1550,23 +1559,25 @@ public class Block {
|
|||||||
processBlockRewards();
|
processBlockRewards();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isTestnet) {
|
||||||
if (this.blockData.getHeight() == 212937) {
|
if (this.blockData.getHeight() == 212937) {
|
||||||
// Apply fix for block 212937
|
// Apply fix for block 212937
|
||||||
Block212937.processFix(this);
|
Block212937.processFix(this);
|
||||||
}
|
} else if (this.blockData.getHeight() == 1333492) {
|
||||||
|
// Apply fix for block 1333492
|
||||||
if (this.blockData.getHeight() == BlockChain.getInstance().getSelfSponsorshipAlgoV1Height()) {
|
Block1333492.processFix(this);
|
||||||
|
} else if (InvalidBalanceBlocks.isAffectedBlock(this.blockData.getHeight())) {
|
||||||
|
// Apply fix for affected balance blocks
|
||||||
|
InvalidBalanceBlocks.processFix(this);
|
||||||
|
} else if (this.blockData.getHeight() == BlockChain.getInstance().getSelfSponsorshipAlgoV1Height()) {
|
||||||
SelfSponsorshipAlgoV1Block.processAccountPenalties(this);
|
SelfSponsorshipAlgoV1Block.processAccountPenalties(this);
|
||||||
}
|
} else if (this.blockData.getHeight() == BlockChain.getInstance().getSelfSponsorshipAlgoV2Height()) {
|
||||||
|
|
||||||
if (this.blockData.getHeight() == BlockChain.getInstance().getSelfSponsorshipAlgoV2Height()) {
|
|
||||||
SelfSponsorshipAlgoV2Block.processAccountPenalties(this);
|
SelfSponsorshipAlgoV2Block.processAccountPenalties(this);
|
||||||
}
|
} else if (this.blockData.getHeight() == BlockChain.getInstance().getSelfSponsorshipAlgoV3Height()) {
|
||||||
|
|
||||||
if (this.blockData.getHeight() == BlockChain.getInstance().getSelfSponsorshipAlgoV3Height()) {
|
|
||||||
SelfSponsorshipAlgoV3Block.processAccountPenalties(this);
|
SelfSponsorshipAlgoV3Block.processAccountPenalties(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// We're about to (test-)process a batch of transactions,
|
// We're about to (test-)process a batch of transactions,
|
||||||
// so create an account reference cache so get/set correct last-references.
|
// so create an account reference cache so get/set correct last-references.
|
||||||
@ -1850,22 +1861,24 @@ public class Block {
|
|||||||
// Invalidate expandedAccounts as they may have changed due to orphaning TRANSFER_PRIVS transactions, etc.
|
// Invalidate expandedAccounts as they may have changed due to orphaning TRANSFER_PRIVS transactions, etc.
|
||||||
this.cachedExpandedAccounts = null;
|
this.cachedExpandedAccounts = null;
|
||||||
|
|
||||||
|
if (!isTestnet) {
|
||||||
if (this.blockData.getHeight() == 212937) {
|
if (this.blockData.getHeight() == 212937) {
|
||||||
// Revert fix for block 212937
|
// Revert fix for block 212937
|
||||||
Block212937.orphanFix(this);
|
Block212937.orphanFix(this);
|
||||||
}
|
} else if (this.blockData.getHeight() == 1333492) {
|
||||||
|
// Revert fix for block 1333492
|
||||||
if (this.blockData.getHeight() == BlockChain.getInstance().getSelfSponsorshipAlgoV1Height()) {
|
Block1333492.orphanFix(this);
|
||||||
|
} else if (InvalidBalanceBlocks.isAffectedBlock(this.blockData.getHeight())) {
|
||||||
|
// Revert fix for affected balance blocks
|
||||||
|
InvalidBalanceBlocks.orphanFix(this);
|
||||||
|
} else if (this.blockData.getHeight() == BlockChain.getInstance().getSelfSponsorshipAlgoV1Height()) {
|
||||||
SelfSponsorshipAlgoV1Block.orphanAccountPenalties(this);
|
SelfSponsorshipAlgoV1Block.orphanAccountPenalties(this);
|
||||||
}
|
} else if (this.blockData.getHeight() == BlockChain.getInstance().getSelfSponsorshipAlgoV2Height()) {
|
||||||
|
|
||||||
if (this.blockData.getHeight() == BlockChain.getInstance().getSelfSponsorshipAlgoV2Height()) {
|
|
||||||
SelfSponsorshipAlgoV2Block.orphanAccountPenalties(this);
|
SelfSponsorshipAlgoV2Block.orphanAccountPenalties(this);
|
||||||
}
|
} else if (this.blockData.getHeight() == BlockChain.getInstance().getSelfSponsorshipAlgoV3Height()) {
|
||||||
|
|
||||||
if (this.blockData.getHeight() == BlockChain.getInstance().getSelfSponsorshipAlgoV3Height()) {
|
|
||||||
SelfSponsorshipAlgoV3Block.orphanAccountPenalties(this);
|
SelfSponsorshipAlgoV3Block.orphanAccountPenalties(this);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Account levels and block rewards are only processed/orphaned on block reward distribution blocks
|
// Account levels and block rewards are only processed/orphaned on block reward distribution blocks
|
||||||
if (this.isRewardDistributionBlock()) {
|
if (this.isRewardDistributionBlock()) {
|
||||||
|
101
src/main/java/org/qortal/block/Block1333492.java
Normal file
101
src/main/java/org/qortal/block/Block1333492.java
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
package org.qortal.block;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.eclipse.persistence.jaxb.JAXBContextFactory;
|
||||||
|
import org.eclipse.persistence.jaxb.UnmarshallerProperties;
|
||||||
|
import org.qortal.data.account.AccountBalanceData;
|
||||||
|
import org.qortal.repository.DataException;
|
||||||
|
|
||||||
|
import javax.xml.bind.JAXBContext;
|
||||||
|
import javax.xml.bind.JAXBException;
|
||||||
|
import javax.xml.bind.UnmarshalException;
|
||||||
|
import javax.xml.bind.Unmarshaller;
|
||||||
|
import javax.xml.transform.stream.StreamSource;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Block 1333492
|
||||||
|
* <p>
|
||||||
|
* As described in InvalidBalanceBlocks.java, legacy bugs caused a small drift in account balances.
|
||||||
|
* This block adjusts any remaining differences between a clean reindex/resync and a recent bootstrap.
|
||||||
|
* <p>
|
||||||
|
* The block height 1333492 isn't significant - it's simply the height of a recent bootstrap at the
|
||||||
|
* time of development, so that the account balances could be accessed and compared against the same
|
||||||
|
* block in a reindexed db.
|
||||||
|
* <p>
|
||||||
|
* As with InvalidBalanceBlocks, the discrepancies are insignificant, except for a single
|
||||||
|
* account which has a 3.03 QORT discrepancy. This was due to the account being the first recipient
|
||||||
|
* of a name sale and encountering an early bug in this area.
|
||||||
|
* <p>
|
||||||
|
* The total offset for this block is 3.02816514 QORT.
|
||||||
|
*/
|
||||||
|
public final class Block1333492 {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LogManager.getLogger(Block1333492.class);
|
||||||
|
private static final String ACCOUNT_DELTAS_SOURCE = "block-1333492-deltas.json";
|
||||||
|
|
||||||
|
private static final List<AccountBalanceData> accountDeltas = readAccountDeltas();
|
||||||
|
|
||||||
|
private Block1333492() {
|
||||||
|
/* Do not instantiate */
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static List<AccountBalanceData> readAccountDeltas() {
|
||||||
|
Unmarshaller unmarshaller;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create JAXB context aware of classes we need to unmarshal
|
||||||
|
JAXBContext jc = JAXBContextFactory.createContext(new Class[] {
|
||||||
|
AccountBalanceData.class
|
||||||
|
}, null);
|
||||||
|
|
||||||
|
// Create unmarshaller
|
||||||
|
unmarshaller = jc.createUnmarshaller();
|
||||||
|
|
||||||
|
// Set the unmarshaller media type to JSON
|
||||||
|
unmarshaller.setProperty(UnmarshallerProperties.MEDIA_TYPE, "application/json");
|
||||||
|
|
||||||
|
// Tell unmarshaller that there's no JSON root element in the JSON input
|
||||||
|
unmarshaller.setProperty(UnmarshallerProperties.JSON_INCLUDE_ROOT, false);
|
||||||
|
} catch (JAXBException e) {
|
||||||
|
String message = "Failed to setup unmarshaller to read block 1333492 deltas";
|
||||||
|
LOGGER.error(message, e);
|
||||||
|
throw new RuntimeException(message, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassLoader classLoader = BlockChain.class.getClassLoader();
|
||||||
|
InputStream in = classLoader.getResourceAsStream(ACCOUNT_DELTAS_SOURCE);
|
||||||
|
StreamSource jsonSource = new StreamSource(in);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Attempt to unmarshal JSON stream to BlockChain config
|
||||||
|
return (List<AccountBalanceData>) unmarshaller.unmarshal(jsonSource, AccountBalanceData.class).getValue();
|
||||||
|
} catch (UnmarshalException e) {
|
||||||
|
String message = "Failed to parse block 1333492 deltas";
|
||||||
|
LOGGER.error(message, e);
|
||||||
|
throw new RuntimeException(message, e);
|
||||||
|
} catch (JAXBException e) {
|
||||||
|
String message = "Unexpected JAXB issue while processing block 1333492 deltas";
|
||||||
|
LOGGER.error(message, e);
|
||||||
|
throw new RuntimeException(message, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void processFix(Block block) throws DataException {
|
||||||
|
block.repository.getAccountRepository().modifyAssetBalances(accountDeltas);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void orphanFix(Block block) throws DataException {
|
||||||
|
// Create inverse deltas
|
||||||
|
List<AccountBalanceData> inverseDeltas = accountDeltas.stream()
|
||||||
|
.map(delta -> new AccountBalanceData(delta.getAddress(), delta.getAssetId(), 0 - delta.getBalance()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
block.repository.getAccountRepository().modifyAssetBalances(inverseDeltas);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -80,7 +80,12 @@ public class BlockChain {
|
|||||||
arbitraryOptionalFeeTimestamp,
|
arbitraryOptionalFeeTimestamp,
|
||||||
unconfirmableRewardSharesHeight,
|
unconfirmableRewardSharesHeight,
|
||||||
disableTransferPrivsTimestamp,
|
disableTransferPrivsTimestamp,
|
||||||
enableTransferPrivsTimestamp
|
enableTransferPrivsTimestamp,
|
||||||
|
cancelSellNameValidationTimestamp,
|
||||||
|
disableRewardshareHeight,
|
||||||
|
enableRewardshareHeight,
|
||||||
|
onlyMintWithNameHeight,
|
||||||
|
groupMemberCheckHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
// Custom transaction fees
|
// Custom transaction fees
|
||||||
@ -200,6 +205,7 @@ public class BlockChain {
|
|||||||
private int minAccountLevelToRewardShare;
|
private int minAccountLevelToRewardShare;
|
||||||
private int maxRewardSharesPerFounderMintingAccount;
|
private int maxRewardSharesPerFounderMintingAccount;
|
||||||
private int founderEffectiveMintingLevel;
|
private int founderEffectiveMintingLevel;
|
||||||
|
private int mintingGroupId;
|
||||||
|
|
||||||
/** 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;
|
||||||
@ -397,7 +403,6 @@ public class BlockChain {
|
|||||||
return this.onlineAccountsModulusV2Timestamp;
|
return this.onlineAccountsModulusV2Timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Block reward batching */
|
/* Block reward batching */
|
||||||
public long getBlockRewardBatchStartHeight() {
|
public long getBlockRewardBatchStartHeight() {
|
||||||
return this.blockRewardBatchStartHeight;
|
return this.blockRewardBatchStartHeight;
|
||||||
@ -524,6 +529,10 @@ public class BlockChain {
|
|||||||
return this.onlineAccountSignaturesMaxLifetime;
|
return this.onlineAccountSignaturesMaxLifetime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getMintingGroupId() {
|
||||||
|
return this.mintingGroupId;
|
||||||
|
}
|
||||||
|
|
||||||
public CiyamAtSettings getCiyamAtSettings() {
|
public CiyamAtSettings getCiyamAtSettings() {
|
||||||
return this.ciyamAtSettings;
|
return this.ciyamAtSettings;
|
||||||
}
|
}
|
||||||
@ -610,6 +619,26 @@ public class BlockChain {
|
|||||||
return this.featureTriggers.get(FeatureTrigger.enableTransferPrivsTimestamp.name()).longValue();
|
return this.featureTriggers.get(FeatureTrigger.enableTransferPrivsTimestamp.name()).longValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getCancelSellNameValidationTimestamp() {
|
||||||
|
return this.featureTriggers.get(FeatureTrigger.cancelSellNameValidationTimestamp.name()).longValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getDisableRewardshareHeight() {
|
||||||
|
return this.featureTriggers.get(FeatureTrigger.disableRewardshareHeight.name()).intValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getEnableRewardshareHeight() {
|
||||||
|
return this.featureTriggers.get(FeatureTrigger.enableRewardshareHeight.name()).intValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getOnlyMintWithNameHeight() {
|
||||||
|
return this.featureTriggers.get(FeatureTrigger.onlyMintWithNameHeight.name()).intValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getGroupMemberCheckHeight() {
|
||||||
|
return this.featureTriggers.get(FeatureTrigger.groupMemberCheckHeight.name()).intValue();
|
||||||
|
}
|
||||||
|
|
||||||
// More complex getters for aspects that change by height or timestamp
|
// More complex getters for aspects that change by height or timestamp
|
||||||
|
|
||||||
public long getRewardAtHeight(int ourHeight) {
|
public long getRewardAtHeight(int ourHeight) {
|
||||||
@ -805,10 +834,12 @@ public class BlockChain {
|
|||||||
boolean isLite = Settings.getInstance().isLite();
|
boolean isLite = Settings.getInstance().isLite();
|
||||||
boolean canBootstrap = Settings.getInstance().getBootstrap();
|
boolean canBootstrap = Settings.getInstance().getBootstrap();
|
||||||
boolean needsArchiveRebuild = false;
|
boolean needsArchiveRebuild = false;
|
||||||
|
int checkHeight = 0;
|
||||||
BlockData chainTip;
|
BlockData chainTip;
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
chainTip = repository.getBlockRepository().getLastBlock();
|
chainTip = repository.getBlockRepository().getLastBlock();
|
||||||
|
checkHeight = repository.getBlockRepository().getBlockchainHeight();
|
||||||
|
|
||||||
// Ensure archive is (at least partially) intact, and force a bootstrap if it isn't
|
// Ensure archive is (at least partially) intact, and force a bootstrap if it isn't
|
||||||
if (!isTopOnly && archiveEnabled && canBootstrap) {
|
if (!isTopOnly && archiveEnabled && canBootstrap) {
|
||||||
@ -824,6 +855,17 @@ public class BlockChain {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!canBootstrap) {
|
||||||
|
if (checkHeight > 2) {
|
||||||
|
LOGGER.info("Retrieved block 2 from archive. Syncing from genesis block resumed!");
|
||||||
|
} else {
|
||||||
|
needsArchiveRebuild = (repository.getBlockArchiveRepository().fromHeight(2) == null);
|
||||||
|
if (needsArchiveRebuild) {
|
||||||
|
LOGGER.info("Couldn't retrieve block 2 from archive. Bootstrapping is disabled. Syncing from genesis block!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Validate checkpoints
|
// Validate checkpoints
|
||||||
// Limited to topOnly nodes for now, in order to reduce risk, and to solve a real-world problem with divergent topOnly nodes
|
// Limited to topOnly nodes for now, in order to reduce risk, and to solve a real-world problem with divergent topOnly nodes
|
||||||
// TODO: remove the isTopOnly conditional below once this feature has had more testing time
|
// TODO: remove the isTopOnly conditional below once this feature has had more testing time
|
||||||
@ -856,13 +898,14 @@ public class BlockChain {
|
|||||||
|
|
||||||
// Check first block is Genesis Block
|
// Check first block is Genesis Block
|
||||||
if (!isGenesisBlockValid() || needsArchiveRebuild) {
|
if (!isGenesisBlockValid() || needsArchiveRebuild) {
|
||||||
|
if (checkHeight < 3) {
|
||||||
try {
|
try {
|
||||||
rebuildBlockchain();
|
rebuildBlockchain();
|
||||||
|
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
throw new DataException(String.format("Interrupted when trying to rebuild blockchain: %s", e.getMessage()));
|
throw new DataException(String.format("Interrupted when trying to rebuild blockchain: %s", e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// We need to create a new connection, as the previous repository and its connections may be been
|
// We need to create a new connection, as the previous repository and its connections may be been
|
||||||
// closed by rebuildBlockchain() if a bootstrap was applied
|
// closed by rebuildBlockchain() if a bootstrap was applied
|
||||||
@ -1001,5 +1044,4 @@ public class BlockChain {
|
|||||||
blockchainLock.unlock();
|
blockchainLock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
134
src/main/java/org/qortal/block/InvalidBalanceBlocks.java
Normal file
134
src/main/java/org/qortal/block/InvalidBalanceBlocks.java
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
package org.qortal.block;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.eclipse.persistence.jaxb.JAXBContextFactory;
|
||||||
|
import org.eclipse.persistence.jaxb.UnmarshallerProperties;
|
||||||
|
import org.qortal.data.account.AccountBalanceData;
|
||||||
|
import org.qortal.repository.DataException;
|
||||||
|
|
||||||
|
import javax.xml.bind.JAXBContext;
|
||||||
|
import javax.xml.bind.JAXBException;
|
||||||
|
import javax.xml.bind.UnmarshalException;
|
||||||
|
import javax.xml.bind.Unmarshaller;
|
||||||
|
import javax.xml.transform.stream.StreamSource;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Due to various bugs - which have been fixed - a small amount of balance drift occurred
|
||||||
|
* in the chainstate of running nodes and bootstraps, when compared with a clean sync from genesis.
|
||||||
|
* This resulted in a significant number of invalid transactions in the chain history due to
|
||||||
|
* subtle balance discrepancies. The sum of all discrepancies that resulted in an invalid
|
||||||
|
* transaction is 0.00198322 QORT, so despite the large quantity of transactions, they
|
||||||
|
* represent an insignificant amount when summed.
|
||||||
|
* <p>
|
||||||
|
* This class is responsible for retroactively fixing all the past transactions which
|
||||||
|
* are invalid due to the balance discrepancies.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
public final class InvalidBalanceBlocks {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LogManager.getLogger(InvalidBalanceBlocks.class);
|
||||||
|
|
||||||
|
private static final String ACCOUNT_DELTAS_SOURCE = "invalid-transaction-balance-deltas.json";
|
||||||
|
|
||||||
|
private static final List<AccountBalanceData> accountDeltas = readAccountDeltas();
|
||||||
|
private static final List<Integer> affectedHeights = getAffectedHeights();
|
||||||
|
|
||||||
|
private InvalidBalanceBlocks() {
|
||||||
|
/* Do not instantiate */
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static List<AccountBalanceData> readAccountDeltas() {
|
||||||
|
Unmarshaller unmarshaller;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create JAXB context aware of classes we need to unmarshal
|
||||||
|
JAXBContext jc = JAXBContextFactory.createContext(new Class[] {
|
||||||
|
AccountBalanceData.class
|
||||||
|
}, null);
|
||||||
|
|
||||||
|
// Create unmarshaller
|
||||||
|
unmarshaller = jc.createUnmarshaller();
|
||||||
|
|
||||||
|
// Set the unmarshaller media type to JSON
|
||||||
|
unmarshaller.setProperty(UnmarshallerProperties.MEDIA_TYPE, "application/json");
|
||||||
|
|
||||||
|
// Tell unmarshaller that there's no JSON root element in the JSON input
|
||||||
|
unmarshaller.setProperty(UnmarshallerProperties.JSON_INCLUDE_ROOT, false);
|
||||||
|
} catch (JAXBException e) {
|
||||||
|
String message = "Failed to setup unmarshaller to read block 212937 deltas";
|
||||||
|
LOGGER.error(message, e);
|
||||||
|
throw new RuntimeException(message, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassLoader classLoader = BlockChain.class.getClassLoader();
|
||||||
|
InputStream in = classLoader.getResourceAsStream(ACCOUNT_DELTAS_SOURCE);
|
||||||
|
StreamSource jsonSource = new StreamSource(in);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Attempt to unmarshal JSON stream to BlockChain config
|
||||||
|
return (List<AccountBalanceData>) unmarshaller.unmarshal(jsonSource, AccountBalanceData.class).getValue();
|
||||||
|
} catch (UnmarshalException e) {
|
||||||
|
String message = "Failed to parse balance deltas";
|
||||||
|
LOGGER.error(message, e);
|
||||||
|
throw new RuntimeException(message, e);
|
||||||
|
} catch (JAXBException e) {
|
||||||
|
String message = "Unexpected JAXB issue while processing balance deltas";
|
||||||
|
LOGGER.error(message, e);
|
||||||
|
throw new RuntimeException(message, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Integer> getAffectedHeights() {
|
||||||
|
List<Integer> heights = new ArrayList<>();
|
||||||
|
for (AccountBalanceData accountBalanceData : accountDeltas) {
|
||||||
|
if (!heights.contains(accountBalanceData.getHeight())) {
|
||||||
|
heights.add(accountBalanceData.getHeight());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return heights;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<AccountBalanceData> getAccountDeltasAtHeight(int height) {
|
||||||
|
return accountDeltas.stream().filter(a -> a.getHeight() == height).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isAffectedBlock(int height) {
|
||||||
|
return affectedHeights.contains(Integer.valueOf(height));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void processFix(Block block) throws DataException {
|
||||||
|
Integer blockHeight = block.getBlockData().getHeight();
|
||||||
|
List<AccountBalanceData> deltas = getAccountDeltasAtHeight(blockHeight);
|
||||||
|
if (deltas == null) {
|
||||||
|
throw new DataException(String.format("Unable to lookup invalid balance data for block height %d", blockHeight));
|
||||||
|
}
|
||||||
|
|
||||||
|
block.repository.getAccountRepository().modifyAssetBalances(deltas);
|
||||||
|
|
||||||
|
LOGGER.info("Applied balance patch for block {}", blockHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void orphanFix(Block block) throws DataException {
|
||||||
|
Integer blockHeight = block.getBlockData().getHeight();
|
||||||
|
List<AccountBalanceData> deltas = getAccountDeltasAtHeight(blockHeight);
|
||||||
|
if (deltas == null) {
|
||||||
|
throw new DataException(String.format("Unable to lookup invalid balance data for block height %d", blockHeight));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create inverse delta(s)
|
||||||
|
for (AccountBalanceData accountBalanceData : deltas) {
|
||||||
|
AccountBalanceData inverseBalanceData = new AccountBalanceData(accountBalanceData.getAddress(), accountBalanceData.getAssetId(), -accountBalanceData.getBalance());
|
||||||
|
block.repository.getAccountRepository().modifyAssetBalances(List.of(inverseBalanceData));
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.info("Reverted balance patch for block {}", blockHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -32,6 +32,7 @@ import org.qortal.gui.Gui;
|
|||||||
import org.qortal.gui.SysTray;
|
import org.qortal.gui.SysTray;
|
||||||
import org.qortal.network.Network;
|
import org.qortal.network.Network;
|
||||||
import org.qortal.network.Peer;
|
import org.qortal.network.Peer;
|
||||||
|
import org.qortal.network.PeerAddress;
|
||||||
import org.qortal.network.message.*;
|
import org.qortal.network.message.*;
|
||||||
import org.qortal.repository.*;
|
import org.qortal.repository.*;
|
||||||
import org.qortal.repository.hsqldb.HSQLDBRepositoryFactory;
|
import org.qortal.repository.hsqldb.HSQLDBRepositoryFactory;
|
||||||
@ -48,8 +49,11 @@ import java.io.File;
|
|||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
import java.security.SecureRandom;
|
||||||
import java.security.Security;
|
import java.security.Security;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
@ -592,6 +596,73 @@ public class Controller extends Thread {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 10*60*1000, 10*60*1000);
|
}, 10*60*1000, 10*60*1000);
|
||||||
|
|
||||||
|
// Check if we need sync from genesis and start syncing
|
||||||
|
Timer syncFromGenesis = new Timer();
|
||||||
|
syncFromGenesis.schedule(new TimerTask() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
LOGGER.debug("Start sync from genesis check.");
|
||||||
|
boolean canBootstrap = Settings.getInstance().getBootstrap();
|
||||||
|
boolean needsArchiveRebuild = false;
|
||||||
|
int checkHeight = 0;
|
||||||
|
Repository repository = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
repository = RepositoryManager.getRepository();
|
||||||
|
needsArchiveRebuild = (repository.getBlockArchiveRepository().fromHeight(2) == null);
|
||||||
|
checkHeight = repository.getBlockRepository().getBlockchainHeight();
|
||||||
|
} catch (DataException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canBootstrap || !needsArchiveRebuild || checkHeight > 3) {
|
||||||
|
LOGGER.debug("Bootstrapping is enabled or we have more than 2 blocks, cancel sync from genesis check.");
|
||||||
|
syncFromGenesis.cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needsArchiveRebuild && !canBootstrap) {
|
||||||
|
LOGGER.info("Start syncing from genesis!");
|
||||||
|
List<Peer> seeds = new ArrayList<>(Network.getInstance().getImmutableHandshakedPeers());
|
||||||
|
|
||||||
|
// Check if have a qualified peer to sync
|
||||||
|
if (seeds.isEmpty()) {
|
||||||
|
LOGGER.info("No connected peers, will try again later.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = new SecureRandom().nextInt(seeds.size());
|
||||||
|
String syncNode = String.valueOf(seeds.get(index));
|
||||||
|
PeerAddress peerAddress = PeerAddress.fromString(syncNode);
|
||||||
|
InetSocketAddress resolvedAddress = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
resolvedAddress = peerAddress.toSocketAddress();
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
InetSocketAddress finalResolvedAddress = resolvedAddress;
|
||||||
|
Peer targetPeer = seeds.stream().filter(peer -> peer.getResolvedAddress().equals(finalResolvedAddress)).findFirst().orElse(null);
|
||||||
|
Synchronizer.SynchronizationResult syncResult;
|
||||||
|
|
||||||
|
try {
|
||||||
|
do {
|
||||||
|
try {
|
||||||
|
syncResult = Synchronizer.getInstance().actuallySynchronize(targetPeer, true);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (syncResult == Synchronizer.SynchronizationResult.OK);
|
||||||
|
} finally {
|
||||||
|
// We are syncing now, so can cancel the check
|
||||||
|
syncFromGenesis.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 3*60*1000, 3*60*1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Called by AdvancedInstaller's launch EXE in single-instance mode, when an instance is already running. */
|
/** Called by AdvancedInstaller's launch EXE in single-instance mode, when an instance is already running. */
|
||||||
|
213
src/main/java/org/qortal/repository/ReindexManager.java
Normal file
213
src/main/java/org/qortal/repository/ReindexManager.java
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
package org.qortal.repository;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.qortal.block.Block;
|
||||||
|
import org.qortal.block.GenesisBlock;
|
||||||
|
import org.qortal.controller.Controller;
|
||||||
|
import org.qortal.data.block.BlockArchiveData;
|
||||||
|
import org.qortal.data.block.BlockData;
|
||||||
|
import org.qortal.data.transaction.TransactionData;
|
||||||
|
import org.qortal.settings.Settings;
|
||||||
|
import org.qortal.transaction.Transaction;
|
||||||
|
import org.qortal.transform.block.BlockTransformation;
|
||||||
|
import org.qortal.utils.Base58;
|
||||||
|
import org.qortal.utils.NTP;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
public class ReindexManager {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LogManager.getLogger(ReindexManager.class);
|
||||||
|
|
||||||
|
private Repository repository;
|
||||||
|
|
||||||
|
private final int pruneAndTrimBlockInterval = 2000;
|
||||||
|
private final int maintenanceBlockInterval = 50000;
|
||||||
|
|
||||||
|
private boolean resume = false;
|
||||||
|
|
||||||
|
public ReindexManager() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reindex() throws DataException {
|
||||||
|
try {
|
||||||
|
this.runPreChecks();
|
||||||
|
this.rebuildRepository();
|
||||||
|
|
||||||
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
this.repository = repository;
|
||||||
|
this.requestCheckpoint();
|
||||||
|
this.processGenesisBlock();
|
||||||
|
this.processBlocks();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new DataException("Interrupted before complete");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runPreChecks() throws DataException, InterruptedException {
|
||||||
|
LOGGER.info("Running pre-checks...");
|
||||||
|
if (Settings.getInstance().isTopOnly()) {
|
||||||
|
throw new DataException("Reindexing not supported in top-only mode. Please bootstrap or resync from genesis.");
|
||||||
|
}
|
||||||
|
if (Settings.getInstance().isLite()) {
|
||||||
|
throw new DataException("Reindexing not supported in lite mode.");
|
||||||
|
}
|
||||||
|
|
||||||
|
while (NTP.getTime() == null) {
|
||||||
|
LOGGER.info("Waiting for NTP...");
|
||||||
|
Thread.sleep(5000L);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rebuildRepository() throws DataException {
|
||||||
|
if (resume) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.info("Rebuilding repository...");
|
||||||
|
RepositoryManager.rebuild();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requestCheckpoint() {
|
||||||
|
RepositoryManager.setRequestedCheckpoint(Boolean.TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processGenesisBlock() throws DataException, InterruptedException {
|
||||||
|
if (resume) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOGGER.info("Processing genesis block...");
|
||||||
|
|
||||||
|
GenesisBlock genesisBlock = GenesisBlock.getInstance(repository);
|
||||||
|
|
||||||
|
// Add Genesis Block to blockchain
|
||||||
|
genesisBlock.process();
|
||||||
|
|
||||||
|
this.repository.saveChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processBlocks() throws DataException {
|
||||||
|
LOGGER.info("Processing blocks...");
|
||||||
|
|
||||||
|
int height = this.repository.getBlockRepository().getBlockchainHeight();
|
||||||
|
while (true) {
|
||||||
|
height++;
|
||||||
|
|
||||||
|
boolean processed = this.processBlock(height);
|
||||||
|
if (!processed) {
|
||||||
|
LOGGER.info("Block {} couldn't be processed. If this is the last archived block, then the process is complete.", height);
|
||||||
|
break; // TODO: check if complete
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prune and trim regularly, leaving a buffer
|
||||||
|
if (height >= pruneAndTrimBlockInterval*2 && height % pruneAndTrimBlockInterval == 0) {
|
||||||
|
int startHeight = Math.max(height - pruneAndTrimBlockInterval*2, 2);
|
||||||
|
int endHeight = height - pruneAndTrimBlockInterval;
|
||||||
|
LOGGER.info("Pruning and trimming blocks {} to {}...", startHeight, endHeight);
|
||||||
|
this.repository.getATRepository().rebuildLatestAtStates(height - 250);
|
||||||
|
this.repository.saveChanges();
|
||||||
|
this.prune(startHeight, endHeight);
|
||||||
|
this.trim(startHeight, endHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run repository maintenance regularly, to keep blockchain.data size down
|
||||||
|
if (height % maintenanceBlockInterval == 0) {
|
||||||
|
this.runRepositoryMaintenance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean processBlock(int height) throws DataException {
|
||||||
|
Block block = this.fetchBlock(height);
|
||||||
|
if (block == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transactions are stored without approval status so determine that now
|
||||||
|
for (Transaction transaction : block.getTransactions())
|
||||||
|
transaction.setInitialApprovalStatus();
|
||||||
|
|
||||||
|
// It's best not to run preProcess() until there is a reason to
|
||||||
|
// block.preProcess();
|
||||||
|
|
||||||
|
Block.ValidationResult validationResult = block.isValid();
|
||||||
|
if (validationResult != Block.ValidationResult.OK) {
|
||||||
|
throw new DataException(String.format("Invalid block at height %d: %s", height, validationResult));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save transactions attached to this block
|
||||||
|
for (Transaction transaction : block.getTransactions()) {
|
||||||
|
TransactionData transactionData = transaction.getTransactionData();
|
||||||
|
this.repository.getTransactionRepository().save(transactionData);
|
||||||
|
}
|
||||||
|
|
||||||
|
block.process();
|
||||||
|
|
||||||
|
LOGGER.info(String.format("Reindexed block height %d, sig %.8s", block.getBlockData().getHeight(), Base58.encode(block.getBlockData().getSignature())));
|
||||||
|
|
||||||
|
// Add to block archive table, since this originated from the archive but the chainstate has to be rebuilt
|
||||||
|
this.addToBlockArchive(block.getBlockData());
|
||||||
|
|
||||||
|
this.repository.saveChanges();
|
||||||
|
|
||||||
|
Controller.getInstance().onNewBlock(block.getBlockData());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Block fetchBlock(int height) {
|
||||||
|
BlockTransformation b = BlockArchiveReader.getInstance().fetchBlockAtHeight(height);
|
||||||
|
if (b != null) {
|
||||||
|
if (b.getAtStatesHash() != null) {
|
||||||
|
return new Block(this.repository, b.getBlockData(), b.getTransactions(), b.getAtStatesHash());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return new Block(this.repository, b.getBlockData(), b.getTransactions(), b.getAtStates());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addToBlockArchive(BlockData blockData) throws DataException {
|
||||||
|
// Write the signature and height into the BlockArchive table
|
||||||
|
BlockArchiveData blockArchiveData = new BlockArchiveData(blockData);
|
||||||
|
this.repository.getBlockArchiveRepository().save(blockArchiveData);
|
||||||
|
this.repository.getBlockArchiveRepository().setBlockArchiveHeight(blockData.getHeight()+1);
|
||||||
|
this.repository.saveChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void prune(int startHeight, int endHeight) throws DataException {
|
||||||
|
this.repository.getBlockRepository().pruneBlocks(startHeight, endHeight);
|
||||||
|
this.repository.getATRepository().pruneAtStates(startHeight, endHeight);
|
||||||
|
this.repository.getATRepository().setAtPruneHeight(endHeight+1);
|
||||||
|
this.repository.saveChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void trim(int startHeight, int endHeight) throws DataException {
|
||||||
|
this.repository.getBlockRepository().trimOldOnlineAccountsSignatures(startHeight, endHeight);
|
||||||
|
|
||||||
|
int count = 1; // Any number greater than 0
|
||||||
|
while (count > 0) {
|
||||||
|
count = this.repository.getATRepository().trimAtStates(startHeight, endHeight, Settings.getInstance().getAtStatesTrimLimit());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.repository.getBlockRepository().setBlockPruneHeight(endHeight+1);
|
||||||
|
this.repository.getATRepository().setAtTrimHeight(endHeight+1);
|
||||||
|
this.repository.saveChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runRepositoryMaintenance() throws DataException {
|
||||||
|
try {
|
||||||
|
this.repository.performPeriodicMaintenance(1000L);
|
||||||
|
} catch (TimeoutException e) {
|
||||||
|
LOGGER.info("Timed out waiting for repository before running maintenance");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -3,6 +3,7 @@ package org.qortal.transaction;
|
|||||||
import com.google.common.base.Utf8;
|
import com.google.common.base.Utf8;
|
||||||
import org.qortal.account.Account;
|
import org.qortal.account.Account;
|
||||||
import org.qortal.asset.Asset;
|
import org.qortal.asset.Asset;
|
||||||
|
import org.qortal.block.BlockChain;
|
||||||
import org.qortal.controller.repository.NamesDatabaseIntegrityCheck;
|
import org.qortal.controller.repository.NamesDatabaseIntegrityCheck;
|
||||||
import org.qortal.data.naming.NameData;
|
import org.qortal.data.naming.NameData;
|
||||||
import org.qortal.data.transaction.CancelSellNameTransactionData;
|
import org.qortal.data.transaction.CancelSellNameTransactionData;
|
||||||
@ -63,8 +64,11 @@ public class CancelSellNameTransaction extends Transaction {
|
|||||||
return ValidationResult.NAME_DOES_NOT_EXIST;
|
return ValidationResult.NAME_DOES_NOT_EXIST;
|
||||||
|
|
||||||
// Check name is currently for sale
|
// Check name is currently for sale
|
||||||
if (!nameData.isForSale())
|
if (!nameData.isForSale()) {
|
||||||
|
// Only validate after feature-trigger timestamp, due to a small number of double cancelations in the chain history
|
||||||
|
if (this.cancelSellNameTransactionData.getTimestamp() > BlockChain.getInstance().getCancelSellNameValidationTimestamp())
|
||||||
return ValidationResult.NAME_NOT_FOR_SALE;
|
return ValidationResult.NAME_NOT_FOR_SALE;
|
||||||
|
}
|
||||||
|
|
||||||
// Check transaction creator matches name's current owner
|
// Check transaction creator matches name's current owner
|
||||||
Account owner = getOwner();
|
Account owner = getOwner();
|
||||||
|
@ -98,6 +98,14 @@ public class RewardShareTransaction extends Transaction {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ValidationResult isValid() throws DataException {
|
public ValidationResult isValid() throws DataException {
|
||||||
|
final int disableRs = BlockChain.getInstance().getDisableRewardshareHeight();
|
||||||
|
final int enableRs = BlockChain.getInstance().getEnableRewardshareHeight();
|
||||||
|
int blockchainHeight = this.repository.getBlockRepository().getBlockchainHeight();
|
||||||
|
|
||||||
|
// Check if reward share is disabled.
|
||||||
|
if (blockchainHeight >= disableRs && blockchainHeight < enableRs)
|
||||||
|
return ValidationResult.GENERAL_TEMPORARY_DISABLED;
|
||||||
|
|
||||||
// Check reward share given to recipient. Negative is potentially OK to end a current reward-share. Zero also fine.
|
// Check reward share given to recipient. Negative is potentially OK to end a current reward-share. Zero also fine.
|
||||||
if (this.rewardShareTransactionData.getSharePercent() > MAX_SHARE)
|
if (this.rewardShareTransactionData.getSharePercent() > MAX_SHARE)
|
||||||
return ValidationResult.INVALID_REWARD_SHARE_PERCENT;
|
return ValidationResult.INVALID_REWARD_SHARE_PERCENT;
|
||||||
|
@ -249,6 +249,7 @@ public abstract class Transaction {
|
|||||||
ACCOUNT_NOT_TRANSFERABLE(99),
|
ACCOUNT_NOT_TRANSFERABLE(99),
|
||||||
TRANSFER_PRIVS_DISABLED(100),
|
TRANSFER_PRIVS_DISABLED(100),
|
||||||
TEMPORARY_DISABLED(101),
|
TEMPORARY_DISABLED(101),
|
||||||
|
GENERAL_TEMPORARY_DISABLED(102),
|
||||||
INVALID_BUT_OK(999),
|
INVALID_BUT_OK(999),
|
||||||
NOT_YET_RELEASED(1000),
|
NOT_YET_RELEASED(1000),
|
||||||
NOT_SUPPORTED(1001);
|
NOT_SUPPORTED(1001);
|
||||||
|
6106
src/main/resources/block-1333492-deltas.json
Normal file
6106
src/main/resources/block-1333492-deltas.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -37,6 +37,7 @@
|
|||||||
"blockRewardBatchStartHeight": 1508000,
|
"blockRewardBatchStartHeight": 1508000,
|
||||||
"blockRewardBatchSize": 1000,
|
"blockRewardBatchSize": 1000,
|
||||||
"blockRewardBatchAccountsBlockCount": 25,
|
"blockRewardBatchAccountsBlockCount": 25,
|
||||||
|
"mintingGroupId": 99999,
|
||||||
"rewardsByHeight": [
|
"rewardsByHeight": [
|
||||||
{ "height": 1, "reward": 5.00 },
|
{ "height": 1, "reward": 5.00 },
|
||||||
{ "height": 259201, "reward": 4.75 },
|
{ "height": 259201, "reward": 4.75 },
|
||||||
@ -103,7 +104,12 @@
|
|||||||
"arbitraryOptionalFeeTimestamp": 1680278400000,
|
"arbitraryOptionalFeeTimestamp": 1680278400000,
|
||||||
"unconfirmableRewardSharesHeight": 1575500,
|
"unconfirmableRewardSharesHeight": 1575500,
|
||||||
"disableTransferPrivsTimestamp": 1706745000000,
|
"disableTransferPrivsTimestamp": 1706745000000,
|
||||||
"enableTransferPrivsTimestamp": 1709251200000
|
"enableTransferPrivsTimestamp": 1709251200000,
|
||||||
|
"cancelSellNameValidationTimestamp": 1676986362069,
|
||||||
|
"disableRewardshareHeight": 9999800,
|
||||||
|
"enableRewardshareHeight": 9999850,
|
||||||
|
"onlyMintWithNameHeight": 9999900,
|
||||||
|
"groupMemberCheckHeight": 9999950
|
||||||
},
|
},
|
||||||
"checkpoints": [
|
"checkpoints": [
|
||||||
{ "height": 1136300, "signature": "3BbwawEF2uN8Ni5ofpJXkukoU8ctAPxYoFB7whq9pKfBnjfZcpfEJT4R95NvBDoTP8WDyWvsUvbfHbcr9qSZuYpSKZjUQTvdFf6eqznHGEwhZApWfvXu6zjGCxYCp65F4jsVYYJjkzbjmkCg5WAwN5voudngA23kMK6PpTNygapCzXt" }
|
{ "height": 1136300, "signature": "3BbwawEF2uN8Ni5ofpJXkukoU8ctAPxYoFB7whq9pKfBnjfZcpfEJT4R95NvBDoTP8WDyWvsUvbfHbcr9qSZuYpSKZjUQTvdFf6eqznHGEwhZApWfvXu6zjGCxYCp65F4jsVYYJjkzbjmkCg5WAwN5voudngA23kMK6PpTNygapCzXt" }
|
||||||
|
@ -197,3 +197,5 @@ TX_GROUP_ID_MISMATCH = die Gruppen-ID der Transaktion stimmt nicht überein
|
|||||||
TRANSFER_PRIVS_DISABLED = Übertragungsberechtigungen deaktiviert
|
TRANSFER_PRIVS_DISABLED = Übertragungsberechtigungen deaktiviert
|
||||||
|
|
||||||
TEMPORARY_DISABLED = Namensregistrierung vorübergehend deaktiviert
|
TEMPORARY_DISABLED = Namensregistrierung vorübergehend deaktiviert
|
||||||
|
|
||||||
|
GENERAL_TEMPORARY_DISABLED = Vorübergehend deaktiviert
|
||||||
|
@ -197,3 +197,5 @@ TX_GROUP_ID_MISMATCH = transaction's group ID does not match
|
|||||||
TRANSFER_PRIVS_DISABLED = transfer privileges disabled
|
TRANSFER_PRIVS_DISABLED = transfer privileges disabled
|
||||||
|
|
||||||
TEMPORARY_DISABLED = Name registration temporary disabled
|
TEMPORARY_DISABLED = Name registration temporary disabled
|
||||||
|
|
||||||
|
GENERAL_TEMPORARY_DISABLED = Temporary disabled
|
||||||
|
@ -197,3 +197,5 @@ TX_GROUP_ID_MISMATCH = el ID de grupo de la transacción no coincide
|
|||||||
TRANSFER_PRIVS_DISABLED = privilegios de transferencia deshabilitados
|
TRANSFER_PRIVS_DISABLED = privilegios de transferencia deshabilitados
|
||||||
|
|
||||||
TEMPORARY_DISABLED = Registro de nombre temporalmente deshabilitado
|
TEMPORARY_DISABLED = Registro de nombre temporalmente deshabilitado
|
||||||
|
|
||||||
|
GENERAL_TEMPORARY_DISABLED = Temporalmente deshabilitado
|
||||||
|
@ -197,3 +197,5 @@ TX_GROUP_ID_MISMATCH = transaktion ryhmä-ID:n vastaavuusvirhe
|
|||||||
TRANSFER_PRIVS_DISABLED = siirtooikeudet poistettu käytöstä
|
TRANSFER_PRIVS_DISABLED = siirtooikeudet poistettu käytöstä
|
||||||
|
|
||||||
TEMPORARY_DISABLED = Nimen rekisteröinti tilapäisesti poistettu käytöstä
|
TEMPORARY_DISABLED = Nimen rekisteröinti tilapäisesti poistettu käytöstä
|
||||||
|
|
||||||
|
GENERAL_TEMPORARY_DISABLED = Tilapäisesti poistettu käytöstä
|
||||||
|
@ -197,3 +197,5 @@ TX_GROUP_ID_MISMATCH = l'identifiant du groupe de transaction ne correspond pas
|
|||||||
TRANSFER_PRIVS_DISABLED = privilèges de transfert désactivés
|
TRANSFER_PRIVS_DISABLED = privilèges de transfert désactivés
|
||||||
|
|
||||||
TEMPORARY_DISABLED = Enregistrement du nom temporairement désactivé
|
TEMPORARY_DISABLED = Enregistrement du nom temporairement désactivé
|
||||||
|
|
||||||
|
GENERAL_TEMPORARY_DISABLED = Temporairement désactivé
|
||||||
|
@ -197,3 +197,5 @@ TX_GROUP_ID_MISMATCH = מזהה הקבוצה של העסקה אינו תואם
|
|||||||
TRANSFER_PRIVS_DISABLED = הרשאות העברה מושבתות
|
TRANSFER_PRIVS_DISABLED = הרשאות העברה מושבתות
|
||||||
|
|
||||||
TEMPORARY_DISABLED = רישום שמות מושבת זמנית
|
TEMPORARY_DISABLED = רישום שמות מושבת זמנית
|
||||||
|
|
||||||
|
GENERAL_TEMPORARY_DISABLED = נכה זמנית
|
||||||
|
@ -197,3 +197,5 @@ TX_GROUP_ID_MISMATCH = a tranzakció csoportazonosítója nem egyezik
|
|||||||
TRANSFER_PRIVS_DISABLED = átviteli jogosultságok letiltva
|
TRANSFER_PRIVS_DISABLED = átviteli jogosultságok letiltva
|
||||||
|
|
||||||
TEMPORARY_DISABLED = A névregisztráció ideiglenesen le van tiltva
|
TEMPORARY_DISABLED = A névregisztráció ideiglenesen le van tiltva
|
||||||
|
|
||||||
|
GENERAL_TEMPORARY_DISABLED = Ideiglenesen letiltva
|
||||||
|
@ -197,3 +197,5 @@ TX_GROUP_ID_MISMATCH = identificazione di gruppo della transazione non corrispon
|
|||||||
TRANSFER_PRIVS_DISABLED = privilegi di trasferimento disabilitati
|
TRANSFER_PRIVS_DISABLED = privilegi di trasferimento disabilitati
|
||||||
|
|
||||||
TEMPORARY_DISABLED = Registrazione del nome temporaneamente disabilitata
|
TEMPORARY_DISABLED = Registrazione del nome temporaneamente disabilitata
|
||||||
|
|
||||||
|
GENERAL_TEMPORARY_DISABLED = Temporaneamente disabilitata
|
||||||
|
@ -197,3 +197,5 @@ TX_GROUP_ID_MISMATCH = トランザクションのグループIDが一致しま
|
|||||||
TRANSFER_PRIVS_DISABLED = 転送権限が無効になっています
|
TRANSFER_PRIVS_DISABLED = 転送権限が無効になっています
|
||||||
|
|
||||||
TEMPORARY_DISABLED = 名前の登録が一時的に無効になっています
|
TEMPORARY_DISABLED = 名前の登録が一時的に無効になっています
|
||||||
|
|
||||||
|
GENERAL_TEMPORARY_DISABLED = 一時的に無効になっています
|
||||||
|
@ -197,3 +197,5 @@ TX_GROUP_ID_MISMATCH = 트랜잭션의 그룹 ID가 일치하지 않습니다
|
|||||||
TRANSFER_PRIVS_DISABLED = 권한 이전이 비활성화되었습니다.
|
TRANSFER_PRIVS_DISABLED = 권한 이전이 비활성화되었습니다.
|
||||||
|
|
||||||
TEMPORARY_DISABLED = 이름 등록이 일시적으로 비활성화되었습니다.
|
TEMPORARY_DISABLED = 이름 등록이 일시적으로 비활성화되었습니다.
|
||||||
|
|
||||||
|
GENERAL_TEMPORARY_DISABLED = 일시적인 장애
|
||||||
|
@ -197,3 +197,5 @@ TX_GROUP_ID_MISMATCH = groep-ID komt niet overeen
|
|||||||
TRANSFER_PRIVS_DISABLED = overdrachtsrechten uitgeschakeld
|
TRANSFER_PRIVS_DISABLED = overdrachtsrechten uitgeschakeld
|
||||||
|
|
||||||
TEMPORARY_DISABLED = Naamregistratie tijdelijk uitgeschakeld
|
TEMPORARY_DISABLED = Naamregistratie tijdelijk uitgeschakeld
|
||||||
|
|
||||||
|
GENERAL_TEMPORARY_DISABLED = Tijdelijk uitgeschakeld
|
||||||
|
@ -197,3 +197,5 @@ TX_GROUP_ID_MISMATCH = niezgodność ID grupy transakcji
|
|||||||
TRANSFER_PRIVS_DISABLED = uprawnienia do przenoszenia wyłączone
|
TRANSFER_PRIVS_DISABLED = uprawnienia do przenoszenia wyłączone
|
||||||
|
|
||||||
TEMPORARY_DISABLED = Rejestracja nazwy tymczasowo wyłączona
|
TEMPORARY_DISABLED = Rejestracja nazwy tymczasowo wyłączona
|
||||||
|
|
||||||
|
GENERAL_TEMPORARY_DISABLED = Tymczasowo wyłączona
|
||||||
|
@ -197,3 +197,5 @@ TX_GROUP_ID_MISMATCH = ID-ul de grup al tranzactiei nu se potriveste
|
|||||||
TRANSFER_PRIVS_DISABLED = privilegii de transfer dezactivate
|
TRANSFER_PRIVS_DISABLED = privilegii de transfer dezactivate
|
||||||
|
|
||||||
TEMPORARY_DISABLED = Înregistrarea numelui a fost temporar dezactivată
|
TEMPORARY_DISABLED = Înregistrarea numelui a fost temporar dezactivată
|
||||||
|
|
||||||
|
GENERAL_TEMPORARY_DISABLED = Temporar dezactivată
|
||||||
|
@ -197,3 +197,5 @@ TX_GROUP_ID_MISMATCH = не соответствие идентификатор
|
|||||||
TRANSFER_PRIVS_DISABLED = права на передачу отключены
|
TRANSFER_PRIVS_DISABLED = права на передачу отключены
|
||||||
|
|
||||||
TEMPORARY_DISABLED = Регистрация имени временно отключена
|
TEMPORARY_DISABLED = Регистрация имени временно отключена
|
||||||
|
|
||||||
|
GENERAL_TEMPORARY_DISABLED = Временно отключен
|
||||||
|
@ -197,3 +197,5 @@ TX_GROUP_ID_MISMATCH = transaktionens grupp-ID matchar inte
|
|||||||
TRANSFER_PRIVS_DISABLED = överföringsprivilegier inaktiverade
|
TRANSFER_PRIVS_DISABLED = överföringsprivilegier inaktiverade
|
||||||
|
|
||||||
TEMPORARY_DISABLED = Namnregistrering tillfälligt inaktiverad
|
TEMPORARY_DISABLED = Namnregistrering tillfälligt inaktiverad
|
||||||
|
|
||||||
|
GENERAL_TEMPORARY_DISABLED = Tillfälligt inaktiverad
|
||||||
|
@ -197,3 +197,5 @@ TX_GROUP_ID_MISMATCH = 群组ID交易不吻合
|
|||||||
TRANSFER_PRIVS_DISABLED = 传输权限已禁用
|
TRANSFER_PRIVS_DISABLED = 传输权限已禁用
|
||||||
|
|
||||||
TEMPORARY_DISABLED = 名称注册暂时禁用
|
TEMPORARY_DISABLED = 名称注册暂时禁用
|
||||||
|
|
||||||
|
GENERAL_TEMPORARY_DISABLED = 暂时残障
|
||||||
|
@ -197,3 +197,5 @@ TX_GROUP_ID_MISMATCH = 群組ID交易不吻合
|
|||||||
TRANSFER_PRIVS_DISABLED = 傳輸權限已停用
|
TRANSFER_PRIVS_DISABLED = 傳輸權限已停用
|
||||||
|
|
||||||
TEMPORARY_DISABLED = 名稱註冊暫時停用
|
TEMPORARY_DISABLED = 名稱註冊暫時停用
|
||||||
|
|
||||||
|
GENERAL_TEMPORARY_DISABLED = 暫時殘障
|
||||||
|
1796
src/main/resources/invalid-transaction-balance-deltas.json
Normal file
1796
src/main/resources/invalid-transaction-balance-deltas.json
Normal file
File diff suppressed because it is too large
Load Diff
73
testnet/log4j2.properties
Normal file
73
testnet/log4j2.properties
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
rootLogger.level = info
|
||||||
|
# On Windows, uncomment next line to set dirname:
|
||||||
|
# property.dirname = ${sys:user.home}\\AppData\\Local\\qortal\\
|
||||||
|
# property.filename = ${sys:log4j2.filenameTemplate:-log.txt}
|
||||||
|
|
||||||
|
rootLogger.appenderRef.console.ref = stdout
|
||||||
|
rootLogger.appenderRef.rolling.ref = FILE
|
||||||
|
|
||||||
|
# Suppress extraneous bitcoinj library output
|
||||||
|
logger.bitcoinj.name = org.bitcoinj
|
||||||
|
logger.bitcoinj.level = error
|
||||||
|
|
||||||
|
# Override HSQLDB logging level to "warn" as too much is logged at "info"
|
||||||
|
logger.hsqldb.name = hsqldb.db
|
||||||
|
logger.hsqldb.level = warn
|
||||||
|
|
||||||
|
# Support optional, per-session HSQLDB debugging
|
||||||
|
logger.hsqldbRepository.name = org.qortal.repository.hsqldb
|
||||||
|
logger.hsqldbRepository.level = debug
|
||||||
|
|
||||||
|
# Suppress extraneous Jersey warning
|
||||||
|
logger.jerseyInject.name = org.glassfish.jersey.internal.inject.Providers
|
||||||
|
logger.jerseyInject.level = off
|
||||||
|
|
||||||
|
# Suppress extraneous Jersey EOF 'errors' (actually remote peers disconnecting early)
|
||||||
|
logger.jerseyEOF.name = org.glassfish.jersey.server.internal
|
||||||
|
logger.jerseyEOF.level = off
|
||||||
|
|
||||||
|
# Suppress extraneous Jetty entries
|
||||||
|
# 2019-02-14 11:46:27 INFO ContextHandler:851 - Started o.e.j.s.ServletContextHandler@6949e948{/,null,AVAILABLE}
|
||||||
|
# 2019-02-14 11:46:27 INFO AbstractConnector:289 - Started ServerConnector@50ad322b{HTTP/1.1,[http/1.1]}{0.0.0.0:9085}
|
||||||
|
# 2019-02-14 11:46:27 INFO Server:374 - jetty-9.4.11.v20180605; built: 2018-06-05T18:24:03.829Z; git: d5fc0523cfa96bfebfbda19606cad384d772f04c; jvm 1.8.0_181-b13
|
||||||
|
# 2019-02-14 11:46:27 INFO Server:411 - Started @2539ms
|
||||||
|
logger.jetty.name = org.eclipse.jetty
|
||||||
|
logger.jetty.level = warn
|
||||||
|
# Even more extraneous Jetty output
|
||||||
|
# 2019-01-26 02:18:10 WARN ResourceService:718 - java.util.concurrent.TimeoutException: Idle timeout expired: 30000/30000 ms
|
||||||
|
logger.jettyRS.name = org.eclipse.jetty.server.ResourceService
|
||||||
|
logger.jettyRS.level = error
|
||||||
|
|
||||||
|
# Suppress extraneous slf4j entries
|
||||||
|
# 2019-02-14 11:46:27 INFO log:193 - Logging initialized @1636ms to org.eclipse.jetty.util.log.Slf4jLog
|
||||||
|
logger.slf4j.name = org.slf4j
|
||||||
|
logger.slf4j.level = warn
|
||||||
|
|
||||||
|
# Suppress extraneous Reflections entry
|
||||||
|
# 2019-02-27 10:45:25 WARN Reflections:179 - given scan urls are empty. set urls in the configuration
|
||||||
|
logger.orgReflections.name = org.reflections.Reflections
|
||||||
|
logger.orgReflections.level = off
|
||||||
|
logger.sunReflections.name = sun.reflect.Reflection
|
||||||
|
logger.sunReflections.level = off
|
||||||
|
|
||||||
|
appender.console.type = Console
|
||||||
|
appender.console.name = stdout
|
||||||
|
appender.console.layout.type = PatternLayout
|
||||||
|
appender.console.layout.pattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
|
||||||
|
appender.console.filter.threshold.type = ThresholdFilter
|
||||||
|
appender.console.filter.threshold.level = error
|
||||||
|
|
||||||
|
appender.rolling.type = RollingFile
|
||||||
|
appender.rolling.name = FILE
|
||||||
|
appender.rolling.fileName = qortal.log
|
||||||
|
appender.rolling.filePattern = qortal.%d{dd-MMM}.log.gz
|
||||||
|
appender.rolling.layout.type = PatternLayout
|
||||||
|
appender.rolling.layout.pattern = %d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
|
||||||
|
appender.rolling.policy.type = SizeBasedTriggeringPolicy
|
||||||
|
appender.rolling.policy.size = 10MB
|
||||||
|
appender.rolling.strategy.type = DefaultRolloverStrategy
|
||||||
|
appender.rolling.strategy.max = 7
|
||||||
|
# Set the immediate flush to true (default)
|
||||||
|
# appender.rolling.immediateFlush = true
|
||||||
|
# Set the append to true (default), should not overwrite
|
||||||
|
# appender.rolling.append=true
|
@ -1,5 +1,11 @@
|
|||||||
{
|
{
|
||||||
|
"listenPort": 62392,
|
||||||
|
"apiPort": 62391,
|
||||||
|
"bindAddress": "0.0.0.0",
|
||||||
"isTestNet": true,
|
"isTestNet": true,
|
||||||
|
"singleNodeTestnet": false,
|
||||||
|
"minPeerVersion": "4.5.2",
|
||||||
|
"allowConnectionsWithOlderPeerVersions": false,
|
||||||
"bitcoinNet": "TEST3",
|
"bitcoinNet": "TEST3",
|
||||||
"litecoinNet": "TEST3",
|
"litecoinNet": "TEST3",
|
||||||
"dogecoinNet": "TEST3",
|
"dogecoinNet": "TEST3",
|
||||||
@ -13,6 +19,17 @@
|
|||||||
"bootstrap": false,
|
"bootstrap": false,
|
||||||
"maxPeerConnectionTime": 999999999,
|
"maxPeerConnectionTime": 999999999,
|
||||||
"localAuthBypassEnabled": true,
|
"localAuthBypassEnabled": true,
|
||||||
"singleNodeTestnet": false,
|
"recoveryModeTimeout": 0,
|
||||||
"recoveryModeTimeout": 0
|
"uiLocalServers": [
|
||||||
|
"localhost",
|
||||||
|
"127.0.0.1",
|
||||||
|
"0.0.0.0/0",
|
||||||
|
"::/0"
|
||||||
|
],
|
||||||
|
"apiWhitelist": [
|
||||||
|
"localhost",
|
||||||
|
"127.0.0.1",
|
||||||
|
"0.0.0.0/0",
|
||||||
|
"::/0"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
45
testnet/start.sh
Normal file
45
testnet/start.sh
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Validate Java is installed and the minimum version is available
|
||||||
|
MIN_JAVA_VER='11'
|
||||||
|
|
||||||
|
if command -v java > /dev/null 2>&1; then
|
||||||
|
# Example: openjdk version "11.0.6" 2020-01-14
|
||||||
|
version=$(java -version 2>&1 | awk -F '"' '/version/ {print $2}' | cut -d'.' -f1,2)
|
||||||
|
if echo "${version}" "${MIN_JAVA_VER}" | awk '{ if ($2 > 0 && $1 >= $2) exit 0; else exit 1}'; then
|
||||||
|
echo 'Passed Java version check'
|
||||||
|
else
|
||||||
|
echo "Please upgrade your Java to version ${MIN_JAVA_VER} or greater"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Java is not available, please install Java ${MIN_JAVA_VER} or greater"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# No qortal.jar but we have a Maven built one?
|
||||||
|
# Be helpful and copy across to correct location
|
||||||
|
if [ ! -e qortal.jar -a -f target/qortal*.jar ]; then
|
||||||
|
echo "Copying Maven-built Qortal JAR to correct pathname"
|
||||||
|
cp target/qortal*.jar qortal.jar
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Limits Java JVM stack size and maximum heap usage.
|
||||||
|
# Comment out for bigger systems, e.g. non-routers
|
||||||
|
# or when API documentation is enabled
|
||||||
|
JVM_MEMORY_ARGS="-Xss256m -XX:+UseSerialGC"
|
||||||
|
|
||||||
|
# Although java.net.preferIPv4Stack is supposed to be false
|
||||||
|
# by default in Java 11, on some platforms (e.g. FreeBSD 12),
|
||||||
|
# it is overridden to be true by default. Hence we explicitly
|
||||||
|
# set it to false to obtain desired behaviour.
|
||||||
|
nohup nice -n 20 java \
|
||||||
|
-Djava.net.preferIPv4Stack=false \
|
||||||
|
${JVM_MEMORY_ARGS} \
|
||||||
|
-jar qortal.jar \
|
||||||
|
settings-test.json \
|
||||||
|
1>run.log 2>&1 &
|
||||||
|
|
||||||
|
# Save backgrounded process's PID
|
||||||
|
echo $! > run.pid
|
||||||
|
echo qortal running as pid $!
|
80
testnet/stop.sh
Normal file
80
testnet/stop.sh
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Check for color support
|
||||||
|
if [ -t 1 ]; then
|
||||||
|
ncolors=$( tput colors )
|
||||||
|
if [ -n "${ncolors}" -a "${ncolors}" -ge 8 ]; then
|
||||||
|
if normal="$( tput sgr0 )"; then
|
||||||
|
# use terminfo names
|
||||||
|
red="$( tput setaf 1 )"
|
||||||
|
green="$( tput setaf 2)"
|
||||||
|
else
|
||||||
|
# use termcap names for FreeBSD compat
|
||||||
|
normal="$( tput me )"
|
||||||
|
red="$( tput AF 1 )"
|
||||||
|
green="$( tput AF 2)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Track the pid if we can find it
|
||||||
|
read pid 2>/dev/null <run.pid
|
||||||
|
is_pid_valid=$?
|
||||||
|
|
||||||
|
# Swap out the API port if the --testnet (or -t) argument is specified
|
||||||
|
api_port=12391
|
||||||
|
if [[ "$@" = *"--testnet"* ]] || [[ "$@" = *"-t"* ]]; then
|
||||||
|
api_port=62391
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Attempt to locate the process ID if we don't have one
|
||||||
|
if [ -z "${pid}" ]; then
|
||||||
|
pid=$(ps aux | grep '[q]ortal.jar' | head -n 1 | awk '{print $2}')
|
||||||
|
is_pid_valid=$?
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Locate the API key if it exists
|
||||||
|
apikey=$(cat apikey.txt)
|
||||||
|
success=0
|
||||||
|
|
||||||
|
# Try and stop via the API
|
||||||
|
if [ -n "$apikey" ]; then
|
||||||
|
echo "Stopping Qortal via API..."
|
||||||
|
if curl --url "http://localhost:${api_port}/admin/stop?apiKey=$apikey" 1>/dev/null 2>&1; then
|
||||||
|
success=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Try to kill process with SIGTERM
|
||||||
|
if [ "$success" -ne 1 ] && [ -n "$pid" ]; then
|
||||||
|
echo "Stopping Qortal process $pid..."
|
||||||
|
if kill -15 "${pid}"; then
|
||||||
|
success=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Warn and exit if still no success
|
||||||
|
if [ "$success" -ne 1 ]; then
|
||||||
|
if [ -n "$pid" ]; then
|
||||||
|
echo "${red}Stop command failed - not running with process id ${pid}?${normal}"
|
||||||
|
else
|
||||||
|
echo "${red}Stop command failed - not running?${normal}"
|
||||||
|
fi
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$success" -eq 1 ]; then
|
||||||
|
echo "Qortal node should be shutting down"
|
||||||
|
if [ "${is_pid_valid}" -eq 0 ]; then
|
||||||
|
echo -n "Monitoring for Qortal node to end"
|
||||||
|
while s=`ps -p $pid -o stat=` && [[ "$s" && "$s" != 'Z' ]]; do
|
||||||
|
echo -n .
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
echo
|
||||||
|
echo "${green}Qortal ended gracefully${normal}"
|
||||||
|
rm -f run.pid
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
@ -5,7 +5,7 @@
|
|||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFees": [
|
"unitFees": [
|
||||||
{ "timestamp": 0, "fee": "0.001" }
|
{ "timestamp": 0, "fee": "0.01" }
|
||||||
],
|
],
|
||||||
"nameRegistrationUnitFees": [
|
"nameRegistrationUnitFees": [
|
||||||
{ "timestamp": 0, "fee": "1.25" }
|
{ "timestamp": 0, "fee": "1.25" }
|
||||||
@ -26,10 +26,14 @@
|
|||||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||||
"onlineAccountsModulusV2Timestamp": 0,
|
"onlineAccountsModulusV2Timestamp": 0,
|
||||||
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
|
||||||
|
"selfSponsorshipAlgoV2SnapshotTimestamp": 9999999999999,
|
||||||
|
"selfSponsorshipAlgoV3SnapshotTimestamp": 9999999999999,
|
||||||
|
"referenceTimestampBlock": 1670684455220,
|
||||||
"mempowTransactionUpdatesTimestamp": 1692554400000,
|
"mempowTransactionUpdatesTimestamp": 1692554400000,
|
||||||
"blockRewardBatchStartHeight": 10000,
|
"blockRewardBatchStartHeight": 2000000,
|
||||||
"blockRewardBatchSize": 1000,
|
"blockRewardBatchSize": 1000,
|
||||||
"blockRewardBatchAccountsBlockCount": 25,
|
"blockRewardBatchAccountsBlockCount": 25,
|
||||||
|
"mintingGroupId": 2,
|
||||||
"rewardsByHeight": [
|
"rewardsByHeight": [
|
||||||
{ "height": 1, "reward": 5.00 },
|
{ "height": 1, "reward": 5.00 },
|
||||||
{ "height": 259201, "reward": 4.75 },
|
{ "height": 259201, "reward": 4.75 },
|
||||||
@ -90,13 +94,23 @@
|
|||||||
"increaseOnlineAccountsDifficultyTimestamp": 9999999999999,
|
"increaseOnlineAccountsDifficultyTimestamp": 9999999999999,
|
||||||
"onlineAccountMinterLevelValidationHeight": 0,
|
"onlineAccountMinterLevelValidationHeight": 0,
|
||||||
"selfSponsorshipAlgoV1Height": 9999999,
|
"selfSponsorshipAlgoV1Height": 9999999,
|
||||||
|
"selfSponsorshipAlgoV2Height": 9999900,
|
||||||
|
"selfSponsorshipAlgoV3Height": 9999900,
|
||||||
"feeValidationFixTimestamp": 0,
|
"feeValidationFixTimestamp": 0,
|
||||||
"chatReferenceTimestamp": 0,
|
"chatReferenceTimestamp": 0,
|
||||||
"arbitraryOptionalFeeTimestamp": 0
|
"arbitraryOptionalFeeTimestamp": 0,
|
||||||
|
"unconfirmableRewardSharesHeight": 9999999,
|
||||||
|
"disableTransferPrivsTimestamp": 9999999999990,
|
||||||
|
"enableTransferPrivsTimestamp": 9999999999999,
|
||||||
|
"cancelSellNameValidationTimestamp": 9999999999999,
|
||||||
|
"disableRewardshareHeight": 8450,
|
||||||
|
"enableRewardshareHeight": 11400,
|
||||||
|
"onlyMintWithNameHeight": 8500,
|
||||||
|
"groupMemberCheckHeight": 11200
|
||||||
},
|
},
|
||||||
"genesisInfo": {
|
"genesisInfo": {
|
||||||
"version": 4,
|
"version": 4,
|
||||||
"timestamp": "1701874800000",
|
"timestamp": "1726152900000",
|
||||||
"transactions": [
|
"transactions": [
|
||||||
{ "type": "ISSUE_ASSET", "assetName": "QORT", "description": "QORTAL coin", "quantity": 0, "isDivisible": true, "data": "{}" },
|
{ "type": "ISSUE_ASSET", "assetName": "QORT", "description": "QORTAL coin", "quantity": 0, "isDivisible": true, "data": "{}" },
|
||||||
{ "type": "ISSUE_ASSET", "assetName": "Legacy-QORA", "description": "Representative legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true },
|
{ "type": "ISSUE_ASSET", "assetName": "Legacy-QORA", "description": "Representative legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true },
|
||||||
|
Loading…
x
Reference in New Issue
Block a user