forked from Qortal/qortal
Modifications to support a single node testnet:
- Added "singleNodeTestnet" setting, allowing for fast and consecutive block minting, and no requirement for a minimum number of peers. - Added "recoveryModeTimeout" setting (previously hardcoded in Synchronizer). - Updated testnets documentation to include new settings and a quick start guide. - Added "generic" minting account that can be used in testnets (not functional on mainnet), to simplify the process for new devs.
This commit is contained in:
parent
b37aa749c6
commit
1d5497e484
44
TestNets.md
44
TestNets.md
@ -52,14 +52,13 @@
|
||||
|
||||
## Single-node testnet
|
||||
|
||||
A single-node testnet is possible with code modifications, for basic testing, or to more easily start a new testnet.
|
||||
To do so, follow these steps:
|
||||
- Comment out the `if (mintedLastBlock) { }` conditional in BlockMinter.java
|
||||
- Comment out the `minBlockchainPeers` validation in Settings.validate()
|
||||
- Set `minBlockchainPeers` to 0 in settings.json
|
||||
- Set `Synchronizer.RECOVERY_MODE_TIMEOUT` to `0`
|
||||
- All other steps should remain the same. Only a single reward share key is needed.
|
||||
- Remember to put these values back after introducing other nodes
|
||||
A single-node testnet is possible with an additional settings, or to more easily start a new testnet.
|
||||
Just add this setting:
|
||||
```
|
||||
"singleNodeTestnet": true
|
||||
```
|
||||
This will automatically allow multiple consecutive blocks to be minted, as well as setting minBlockchainPeers to 0.
|
||||
Remember to put these values back after introducing other nodes
|
||||
|
||||
## Fixed network
|
||||
|
||||
@ -93,3 +92,32 @@ Your options are:
|
||||
- `qort` tool, but prepend with one-time shell variable: `BASE_URL=some-node-hostname-or-ip:port qort ......`
|
||||
- `peer-heights`, but use `-t` option, or `BASE_URL` shell variable as above
|
||||
|
||||
## Example settings-test.json
|
||||
```
|
||||
{
|
||||
"isTestNet": true,
|
||||
"bitcoinNet": "TEST3",
|
||||
"repositoryPath": "db-testnet",
|
||||
"blockchainConfig": "testchain.json",
|
||||
"minBlockchainPeers": 1,
|
||||
"apiDocumentationEnabled": true,
|
||||
"apiRestricted": false,
|
||||
"bootstrap": false,
|
||||
"maxPeerConnectionTime": 999999999,
|
||||
"localAuthBypassEnabled": true,
|
||||
"singleNodeTestnet": true,
|
||||
"recoveryModeTimeout": 0
|
||||
}
|
||||
```
|
||||
|
||||
## Quick start
|
||||
Here are some steps to quickly get a single node testnet up and running with a generic minting account:
|
||||
1. Start with template `settings-test.json`, and create a `testchain.json` based on mainnet's blockchain.json (or obtain one from Qortal developers). These should be in the same directory as the jar.
|
||||
2. Make sure feature triggers and other timestamp/height activations are correctly set. Generally these would be `0` so that they are enabled from the start.
|
||||
3. Set a recent genesis `timestamp` in testchain.json, and add this reward share entry:
|
||||
`{ "type": "REWARD_SHARE", "minterPublicKey": "DwcUnhxjamqppgfXCLgbYRx8H9XFPUc2qYRy3CEvQWEw", "recipient": "QbTDMss7NtRxxQaSqBZtSLSNdSYgvGaqFf", "rewardSharePublicKey": "CRvQXxFfUMfr4q3o1PcUZPA4aPCiubBsXkk47GzRo754", "sharePercent": 0 },`
|
||||
4. Start the node, passing in settings-test.json, e.g: `java -jar qortal.jar settings-test.json`
|
||||
5. Once started, add the corresponding minting key to the node:
|
||||
`curl -X POST "http://localhost:62391/admin/mintingaccounts" -d "F48mYJycFgRdqtc58kiovwbcJgVukjzRE4qRRtRsK9ix"`
|
||||
6. Alternatively you can use your own minting account instead of the generic one above.
|
||||
7. After a short while, blocks should be minted from the genesis timestamp until the current time.
|
@ -93,6 +93,8 @@ public class BlockMinter extends Thread {
|
||||
|
||||
List<Block> newBlocks = new ArrayList<>();
|
||||
|
||||
final boolean isSingleNodeTestnet = Settings.getInstance().isSingleNodeTestnet();
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
// Going to need this a lot...
|
||||
BlockRepository blockRepository = repository.getBlockRepository();
|
||||
@ -111,8 +113,9 @@ public class BlockMinter extends Thread {
|
||||
// Free up any repository locks
|
||||
repository.discardChanges();
|
||||
|
||||
// Sleep for a while
|
||||
Thread.sleep(1000);
|
||||
// Sleep for a while.
|
||||
// It's faster on single node testnets, to allow lots of blocks to be minted quickly.
|
||||
Thread.sleep(isSingleNodeTestnet ? 50 : 1000);
|
||||
|
||||
isMintingPossible = false;
|
||||
|
||||
@ -223,9 +226,10 @@ public class BlockMinter extends Thread {
|
||||
List<PrivateKeyAccount> newBlocksMintingAccounts = mintingAccountsData.stream().map(accountData -> new PrivateKeyAccount(repository, accountData.getPrivateKey())).collect(Collectors.toList());
|
||||
|
||||
// We might need to sit the next block out, if one of our minting accounts signed the previous one
|
||||
// Skip this check for single node testnets, since they definitely need to mint every block
|
||||
byte[] previousBlockMinter = previousBlockData.getMinterPublicKey();
|
||||
boolean mintedLastBlock = mintingAccountsData.stream().anyMatch(mintingAccount -> Arrays.equals(mintingAccount.getPublicKey(), previousBlockMinter));
|
||||
if (mintedLastBlock) {
|
||||
if (mintedLastBlock && !isSingleNodeTestnet) {
|
||||
LOGGER.trace(String.format("One of our keys signed the last block, so we won't sign the next one"));
|
||||
continue;
|
||||
}
|
||||
|
@ -1872,6 +1872,10 @@ public class Controller extends Thread {
|
||||
if (latestBlockData == null || latestBlockData.getTimestamp() < minLatestBlockTimestamp)
|
||||
return false;
|
||||
|
||||
if (Settings.getInstance().isSingleNodeTestnet())
|
||||
// Single node testnets won't have peers, so we can assume up to date from this point
|
||||
return true;
|
||||
|
||||
// Needs a mutable copy of the unmodifiableList
|
||||
List<Peer> peers = new ArrayList<>(Network.getInstance().getImmutableHandshakedPeers());
|
||||
if (peers == null)
|
||||
|
@ -56,8 +56,6 @@ public class Synchronizer extends Thread {
|
||||
/** Maximum number of consecutive failed sync attempts before marking peer as misbehaved */
|
||||
private static final int MAX_CONSECUTIVE_FAILED_SYNC_ATTEMPTS = 3;
|
||||
|
||||
private static final long RECOVERY_MODE_TIMEOUT = 10 * 60 * 1000L; // ms
|
||||
|
||||
|
||||
private boolean running;
|
||||
|
||||
@ -399,9 +397,10 @@ public class Synchronizer extends Thread {
|
||||
timePeersLastAvailable = NTP.getTime();
|
||||
|
||||
// If enough time has passed, enter recovery mode, which lifts some restrictions on who we can sync with and when we can mint
|
||||
if (NTP.getTime() - timePeersLastAvailable > RECOVERY_MODE_TIMEOUT) {
|
||||
long recoveryModeTimeout = Settings.getInstance().getRecoveryModeTimeout();
|
||||
if (NTP.getTime() - timePeersLastAvailable > recoveryModeTimeout) {
|
||||
if (recoveryMode == false) {
|
||||
LOGGER.info(String.format("Peers have been unavailable for %d minutes. Entering recovery mode...", RECOVERY_MODE_TIMEOUT/60/1000));
|
||||
LOGGER.info(String.format("Peers have been unavailable for %d minutes. Entering recovery mode...", recoveryModeTimeout/60/1000));
|
||||
recoveryMode = true;
|
||||
}
|
||||
}
|
||||
|
@ -184,6 +184,8 @@ public class Settings {
|
||||
|
||||
// Peer-to-peer related
|
||||
private boolean isTestNet = false;
|
||||
/** Single node testnet mode */
|
||||
private boolean singleNodeTestnet = false;
|
||||
/** Port number for inbound peer-to-peer connections. */
|
||||
private Integer listenPort;
|
||||
/** Whether to attempt to open the listen port via UPnP */
|
||||
@ -203,6 +205,9 @@ public class Settings {
|
||||
/** Maximum number of retry attempts if a peer fails to respond with the requested data */
|
||||
private int maxRetries = 2;
|
||||
|
||||
/** The number of seconds of no activity before recovery mode begins */
|
||||
public long recoveryModeTimeout = 10 * 60 * 1000L;
|
||||
|
||||
/** Minimum peer version number required in order to sync with them */
|
||||
private String minPeerVersion = "3.6.3";
|
||||
/** Whether to allow connections with peers below minPeerVersion
|
||||
@ -486,7 +491,7 @@ public class Settings {
|
||||
|
||||
private void validate() {
|
||||
// Validation goes here
|
||||
if (this.minBlockchainPeers < 1)
|
||||
if (this.minBlockchainPeers < 1 && !singleNodeTestnet)
|
||||
throwValidationError("minBlockchainPeers must be at least 1");
|
||||
|
||||
if (this.apiKey != null && this.apiKey.trim().length() < 8)
|
||||
@ -643,6 +648,10 @@ public class Settings {
|
||||
return this.isTestNet;
|
||||
}
|
||||
|
||||
public boolean isSingleNodeTestnet() {
|
||||
return this.singleNodeTestnet;
|
||||
}
|
||||
|
||||
public int getListenPort() {
|
||||
if (this.listenPort != null)
|
||||
return this.listenPort;
|
||||
@ -663,6 +672,9 @@ public class Settings {
|
||||
}
|
||||
|
||||
public int getMinBlockchainPeers() {
|
||||
if (singleNodeTestnet)
|
||||
return 0;
|
||||
|
||||
return this.minBlockchainPeers;
|
||||
}
|
||||
|
||||
@ -688,6 +700,10 @@ public class Settings {
|
||||
|
||||
public int getMaxRetries() { return this.maxRetries; }
|
||||
|
||||
public long getRecoveryModeTimeout() {
|
||||
return recoveryModeTimeout;
|
||||
}
|
||||
|
||||
public String getMinPeerVersion() { return this.minPeerVersion; }
|
||||
|
||||
public boolean getAllowConnectionsWithOlderPeerVersions() { return this.allowConnectionsWithOlderPeerVersions; }
|
||||
|
Loading…
Reference in New Issue
Block a user