mirror of
https://github.com/Qortal/qortal.git
synced 2025-03-26 23:44:34 +00:00
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
|
## Single-node testnet
|
||||||
|
|
||||||
A single-node testnet is possible with code modifications, for basic testing, or to more easily start a new testnet.
|
A single-node testnet is possible with an additional settings, or to more easily start a new testnet.
|
||||||
To do so, follow these steps:
|
Just add this setting:
|
||||||
- Comment out the `if (mintedLastBlock) { }` conditional in BlockMinter.java
|
```
|
||||||
- Comment out the `minBlockchainPeers` validation in Settings.validate()
|
"singleNodeTestnet": true
|
||||||
- Set `minBlockchainPeers` to 0 in settings.json
|
```
|
||||||
- Set `Synchronizer.RECOVERY_MODE_TIMEOUT` to `0`
|
This will automatically allow multiple consecutive blocks to be minted, as well as setting minBlockchainPeers 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
|
||||||
- Remember to put these values back after introducing other nodes
|
|
||||||
|
|
||||||
## Fixed network
|
## 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 ......`
|
- `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
|
- `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<>();
|
List<Block> newBlocks = new ArrayList<>();
|
||||||
|
|
||||||
|
final boolean isSingleNodeTestnet = Settings.getInstance().isSingleNodeTestnet();
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
// Going to need this a lot...
|
// Going to need this a lot...
|
||||||
BlockRepository blockRepository = repository.getBlockRepository();
|
BlockRepository blockRepository = repository.getBlockRepository();
|
||||||
@ -111,8 +113,9 @@ public class BlockMinter extends Thread {
|
|||||||
// Free up any repository locks
|
// Free up any repository locks
|
||||||
repository.discardChanges();
|
repository.discardChanges();
|
||||||
|
|
||||||
// Sleep for a while
|
// Sleep for a while.
|
||||||
Thread.sleep(1000);
|
// It's faster on single node testnets, to allow lots of blocks to be minted quickly.
|
||||||
|
Thread.sleep(isSingleNodeTestnet ? 50 : 1000);
|
||||||
|
|
||||||
isMintingPossible = false;
|
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());
|
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
|
// 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();
|
byte[] previousBlockMinter = previousBlockData.getMinterPublicKey();
|
||||||
boolean mintedLastBlock = mintingAccountsData.stream().anyMatch(mintingAccount -> Arrays.equals(mintingAccount.getPublicKey(), previousBlockMinter));
|
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"));
|
LOGGER.trace(String.format("One of our keys signed the last block, so we won't sign the next one"));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -1872,6 +1872,10 @@ public class Controller extends Thread {
|
|||||||
if (latestBlockData == null || latestBlockData.getTimestamp() < minLatestBlockTimestamp)
|
if (latestBlockData == null || latestBlockData.getTimestamp() < minLatestBlockTimestamp)
|
||||||
return false;
|
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
|
// Needs a mutable copy of the unmodifiableList
|
||||||
List<Peer> peers = new ArrayList<>(Network.getInstance().getImmutableHandshakedPeers());
|
List<Peer> peers = new ArrayList<>(Network.getInstance().getImmutableHandshakedPeers());
|
||||||
if (peers == null)
|
if (peers == null)
|
||||||
|
@ -56,8 +56,6 @@ public class Synchronizer extends Thread {
|
|||||||
/** Maximum number of consecutive failed sync attempts before marking peer as misbehaved */
|
/** 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 int MAX_CONSECUTIVE_FAILED_SYNC_ATTEMPTS = 3;
|
||||||
|
|
||||||
private static final long RECOVERY_MODE_TIMEOUT = 10 * 60 * 1000L; // ms
|
|
||||||
|
|
||||||
|
|
||||||
private boolean running;
|
private boolean running;
|
||||||
|
|
||||||
@ -399,9 +397,10 @@ public class Synchronizer extends Thread {
|
|||||||
timePeersLastAvailable = NTP.getTime();
|
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 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) {
|
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;
|
recoveryMode = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -184,6 +184,8 @@ public class Settings {
|
|||||||
|
|
||||||
// Peer-to-peer related
|
// Peer-to-peer related
|
||||||
private boolean isTestNet = false;
|
private boolean isTestNet = false;
|
||||||
|
/** Single node testnet mode */
|
||||||
|
private boolean singleNodeTestnet = false;
|
||||||
/** Port number for inbound peer-to-peer connections. */
|
/** Port number for inbound peer-to-peer connections. */
|
||||||
private Integer listenPort;
|
private Integer listenPort;
|
||||||
/** Whether to attempt to open the listen port via UPnP */
|
/** 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 */
|
/** Maximum number of retry attempts if a peer fails to respond with the requested data */
|
||||||
private int maxRetries = 2;
|
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 */
|
/** Minimum peer version number required in order to sync with them */
|
||||||
private String minPeerVersion = "3.6.3";
|
private String minPeerVersion = "3.6.3";
|
||||||
/** Whether to allow connections with peers below minPeerVersion
|
/** Whether to allow connections with peers below minPeerVersion
|
||||||
@ -486,7 +491,7 @@ public class Settings {
|
|||||||
|
|
||||||
private void validate() {
|
private void validate() {
|
||||||
// Validation goes here
|
// Validation goes here
|
||||||
if (this.minBlockchainPeers < 1)
|
if (this.minBlockchainPeers < 1 && !singleNodeTestnet)
|
||||||
throwValidationError("minBlockchainPeers must be at least 1");
|
throwValidationError("minBlockchainPeers must be at least 1");
|
||||||
|
|
||||||
if (this.apiKey != null && this.apiKey.trim().length() < 8)
|
if (this.apiKey != null && this.apiKey.trim().length() < 8)
|
||||||
@ -643,6 +648,10 @@ public class Settings {
|
|||||||
return this.isTestNet;
|
return this.isTestNet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isSingleNodeTestnet() {
|
||||||
|
return this.singleNodeTestnet;
|
||||||
|
}
|
||||||
|
|
||||||
public int getListenPort() {
|
public int getListenPort() {
|
||||||
if (this.listenPort != null)
|
if (this.listenPort != null)
|
||||||
return this.listenPort;
|
return this.listenPort;
|
||||||
@ -663,6 +672,9 @@ public class Settings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int getMinBlockchainPeers() {
|
public int getMinBlockchainPeers() {
|
||||||
|
if (singleNodeTestnet)
|
||||||
|
return 0;
|
||||||
|
|
||||||
return this.minBlockchainPeers;
|
return this.minBlockchainPeers;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -688,6 +700,10 @@ public class Settings {
|
|||||||
|
|
||||||
public int getMaxRetries() { return this.maxRetries; }
|
public int getMaxRetries() { return this.maxRetries; }
|
||||||
|
|
||||||
|
public long getRecoveryModeTimeout() {
|
||||||
|
return recoveryModeTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
public String getMinPeerVersion() { return this.minPeerVersion; }
|
public String getMinPeerVersion() { return this.minPeerVersion; }
|
||||||
|
|
||||||
public boolean getAllowConnectionsWithOlderPeerVersions() { return this.allowConnectionsWithOlderPeerVersions; }
|
public boolean getAllowConnectionsWithOlderPeerVersions() { return this.allowConnectionsWithOlderPeerVersions; }
|
||||||
|
Loading…
x
Reference in New Issue
Block a user