forked from Qortal/qortal
Merge branch 'master' into chat-reference
This commit is contained in:
commit
1dd039fb2d
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;
|
||||||
}
|
}
|
||||||
@ -244,7 +248,7 @@ public class BlockMinter extends Thread {
|
|||||||
Block newBlock = Block.mint(repository, previousBlockData, mintingAccount);
|
Block newBlock = Block.mint(repository, previousBlockData, mintingAccount);
|
||||||
if (newBlock == null) {
|
if (newBlock == null) {
|
||||||
// For some reason we can't mint right now
|
// For some reason we can't mint right now
|
||||||
moderatedLog(() -> LOGGER.error("Couldn't build a to-be-minted block"));
|
moderatedLog(() -> LOGGER.info("Couldn't build a to-be-minted block"));
|
||||||
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)
|
||||||
|
@ -192,8 +192,8 @@ public class OnlineAccountsManager {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// Skip this account if it's already validated
|
// Skip this account if it's already validated
|
||||||
Set<OnlineAccountData> onlineAccounts = this.currentOnlineAccounts.computeIfAbsent(onlineAccountData.getTimestamp(), k -> ConcurrentHashMap.newKeySet());
|
Set<OnlineAccountData> onlineAccounts = this.currentOnlineAccounts.get(onlineAccountData.getTimestamp());
|
||||||
if (onlineAccounts.contains(onlineAccountData)) {
|
if (onlineAccounts != null && onlineAccounts.contains(onlineAccountData)) {
|
||||||
// We have already validated this online account
|
// We have already validated this online account
|
||||||
onlineAccountsImportQueue.remove(onlineAccountData);
|
onlineAccountsImportQueue.remove(onlineAccountData);
|
||||||
continue;
|
continue;
|
||||||
@ -214,8 +214,8 @@ public class OnlineAccountsManager {
|
|||||||
if (!onlineAccountsToAdd.isEmpty()) {
|
if (!onlineAccountsToAdd.isEmpty()) {
|
||||||
LOGGER.debug("Merging {} validated online accounts from import queue", onlineAccountsToAdd.size());
|
LOGGER.debug("Merging {} validated online accounts from import queue", onlineAccountsToAdd.size());
|
||||||
addAccounts(onlineAccountsToAdd);
|
addAccounts(onlineAccountsToAdd);
|
||||||
onlineAccountsImportQueue.removeAll(onlineAccountsToRemove);
|
|
||||||
}
|
}
|
||||||
|
onlineAccountsImportQueue.removeAll(onlineAccountsToRemove);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -600,7 +600,7 @@ public class OnlineAccountsManager {
|
|||||||
// MemoryPoW
|
// MemoryPoW
|
||||||
|
|
||||||
private boolean isMemoryPoWActive(Long timestamp) {
|
private boolean isMemoryPoWActive(Long timestamp) {
|
||||||
if (timestamp >= BlockChain.getInstance().getOnlineAccountsMemoryPoWTimestamp() || Settings.getInstance().isOnlineAccountsMemPoWEnabled()) {
|
if (timestamp >= BlockChain.getInstance().getOnlineAccountsMemoryPoWTimestamp()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -617,7 +617,7 @@ public class OnlineAccountsManager {
|
|||||||
|
|
||||||
private Integer computeMemoryPoW(byte[] bytes, byte[] publicKey, long onlineAccountsTimestamp) throws TimeoutException {
|
private Integer computeMemoryPoW(byte[] bytes, byte[] publicKey, long onlineAccountsTimestamp) throws TimeoutException {
|
||||||
if (!isMemoryPoWActive(NTP.getTime())) {
|
if (!isMemoryPoWActive(NTP.getTime())) {
|
||||||
LOGGER.info("Mempow start timestamp not yet reached, and onlineAccountsMemPoWEnabled not enabled in settings");
|
LOGGER.info("Mempow start timestamp not yet reached");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -702,7 +702,7 @@ public class OnlineAccountsManager {
|
|||||||
*/
|
*/
|
||||||
// Block::mint() - only wants online accounts with (online) timestamp that matches block's (online) timestamp so they can be added to new block
|
// Block::mint() - only wants online accounts with (online) timestamp that matches block's (online) timestamp so they can be added to new block
|
||||||
public List<OnlineAccountData> getOnlineAccounts(long onlineTimestamp) {
|
public List<OnlineAccountData> getOnlineAccounts(long onlineTimestamp) {
|
||||||
LOGGER.info(String.format("caller's timestamp: %d, our timestamps: %s", onlineTimestamp, String.join(", ", this.currentOnlineAccounts.keySet().stream().map(l -> Long.toString(l)).collect(Collectors.joining(", ")))));
|
LOGGER.debug(String.format("caller's timestamp: %d, our timestamps: %s", onlineTimestamp, String.join(", ", this.currentOnlineAccounts.keySet().stream().map(l -> Long.toString(l)).collect(Collectors.joining(", ")))));
|
||||||
|
|
||||||
return new ArrayList<>(Set.copyOf(this.currentOnlineAccounts.getOrDefault(onlineTimestamp, Collections.emptySet())));
|
return new ArrayList<>(Set.copyOf(this.currentOnlineAccounts.getOrDefault(onlineTimestamp, Collections.emptySet())));
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
@ -290,10 +295,6 @@ public class Settings {
|
|||||||
/** Additional offset added to values returned by NTP.getTime() */
|
/** Additional offset added to values returned by NTP.getTime() */
|
||||||
private Long testNtpOffset = null;
|
private Long testNtpOffset = null;
|
||||||
|
|
||||||
// Online accounts
|
|
||||||
|
|
||||||
/** Whether to opt-in to mempow computations for online accounts, ahead of general release */
|
|
||||||
private boolean onlineAccountsMemPoWEnabled = false;
|
|
||||||
|
|
||||||
|
|
||||||
/* Foreign chains */
|
/* Foreign chains */
|
||||||
@ -490,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)
|
||||||
@ -647,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;
|
||||||
@ -667,6 +672,9 @@ public class Settings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int getMinBlockchainPeers() {
|
public int getMinBlockchainPeers() {
|
||||||
|
if (singleNodeTestnet)
|
||||||
|
return 0;
|
||||||
|
|
||||||
return this.minBlockchainPeers;
|
return this.minBlockchainPeers;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -692,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; }
|
||||||
@ -800,10 +812,6 @@ public class Settings {
|
|||||||
return this.testNtpOffset;
|
return this.testNtpOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isOnlineAccountsMemPoWEnabled() {
|
|
||||||
return this.onlineAccountsMemPoWEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getRepositoryBackupInterval() {
|
public long getRepositoryBackupInterval() {
|
||||||
return this.repositoryBackupInterval;
|
return this.repositoryBackupInterval;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user