forked from Qortal/qortal
Disable various core functions when running as a lite node.
Lite nodes can't sync or mint blocks, and they also have a very limited ability to verify unconfirmed transactions due to a lack of contextual information (i.e. the blockchain). For now, most validation is skipped and they simply act as relays to help get transactions around the network. Full and topOnly nodes will disregard any invalid transactions upon receipt as usual, and since the lite nodes aren't signing any blocks, there is little risk to the reduced validation, other than the experience of the lite node itself. This can be tightened up considerably as the lite nodes become more powerful, but the current approach works as a PoC.
This commit is contained in:
parent
0e3a9ee2b2
commit
cfe92525ed
@ -61,6 +61,11 @@ public class BlockMinter extends Thread {
|
||||
public void run() {
|
||||
Thread.currentThread().setName("BlockMinter");
|
||||
|
||||
if (Settings.getInstance().isLite()) {
|
||||
// Lite nodes do not mint
|
||||
return;
|
||||
}
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
if (Settings.getInstance().getWipeUnconfirmedOnStart()) {
|
||||
// Wipe existing unconfirmed transactions
|
||||
|
@ -362,23 +362,27 @@ public class Controller extends Thread {
|
||||
return; // Not System.exit() so that GUI can display error
|
||||
}
|
||||
|
||||
// Rebuild Names table and check database integrity (if enabled)
|
||||
NamesDatabaseIntegrityCheck namesDatabaseIntegrityCheck = new NamesDatabaseIntegrityCheck();
|
||||
namesDatabaseIntegrityCheck.rebuildAllNames();
|
||||
if (Settings.getInstance().isNamesIntegrityCheckEnabled()) {
|
||||
namesDatabaseIntegrityCheck.runIntegrityCheck();
|
||||
}
|
||||
// If we have a non-lite node, we need to perform some startup actions
|
||||
if (!Settings.getInstance().isLite()) {
|
||||
|
||||
LOGGER.info("Validating blockchain");
|
||||
try {
|
||||
BlockChain.validate();
|
||||
// Rebuild Names table and check database integrity (if enabled)
|
||||
NamesDatabaseIntegrityCheck namesDatabaseIntegrityCheck = new NamesDatabaseIntegrityCheck();
|
||||
namesDatabaseIntegrityCheck.rebuildAllNames();
|
||||
if (Settings.getInstance().isNamesIntegrityCheckEnabled()) {
|
||||
namesDatabaseIntegrityCheck.runIntegrityCheck();
|
||||
}
|
||||
|
||||
Controller.getInstance().refillLatestBlocksCache();
|
||||
LOGGER.info(String.format("Our chain height at start-up: %d", Controller.getInstance().getChainHeight()));
|
||||
} catch (DataException e) {
|
||||
LOGGER.error("Couldn't validate blockchain", e);
|
||||
Gui.getInstance().fatalError("Blockchain validation issue", e);
|
||||
return; // Not System.exit() so that GUI can display error
|
||||
LOGGER.info("Validating blockchain");
|
||||
try {
|
||||
BlockChain.validate();
|
||||
|
||||
Controller.getInstance().refillLatestBlocksCache();
|
||||
LOGGER.info(String.format("Our chain height at start-up: %d", Controller.getInstance().getChainHeight()));
|
||||
} catch (DataException e) {
|
||||
LOGGER.error("Couldn't validate blockchain", e);
|
||||
Gui.getInstance().fatalError("Blockchain validation issue", e);
|
||||
return; // Not System.exit() so that GUI can display error
|
||||
}
|
||||
}
|
||||
|
||||
// Import current trade bot states and minting accounts if they exist
|
||||
@ -754,7 +758,11 @@ public class Controller extends Thread {
|
||||
final Long minLatestBlockTimestamp = NTP.getTime() - (30 * 60 * 1000L);
|
||||
|
||||
synchronized (Synchronizer.getInstance().syncLock) {
|
||||
if (this.isMintingPossible) {
|
||||
if (Settings.getInstance().isLite()) {
|
||||
actionText = Translator.INSTANCE.translate("SysTray", "LITE_NODE");
|
||||
SysTray.getInstance().setTrayIcon(4);
|
||||
}
|
||||
else if (this.isMintingPossible) {
|
||||
actionText = Translator.INSTANCE.translate("SysTray", "MINTING_ENABLED");
|
||||
SysTray.getInstance().setTrayIcon(2);
|
||||
}
|
||||
@ -776,7 +784,11 @@ public class Controller extends Thread {
|
||||
}
|
||||
}
|
||||
|
||||
String tooltip = String.format("%s - %d %s - %s %d", actionText, numberOfPeers, connectionsText, heightText, height) + "\n" + String.format("%s: %s", Translator.INSTANCE.translate("SysTray", "BUILD_VERSION"), this.buildVersion);
|
||||
String tooltip = String.format("%s - %d %s", actionText, numberOfPeers, connectionsText);
|
||||
if (!Settings.getInstance().isLite()) {
|
||||
tooltip.concat(String.format(" - %s %d", heightText, height));
|
||||
}
|
||||
tooltip.concat(String.format("\n%s: %s", Translator.INSTANCE.translate("SysTray", "BUILD_VERSION"), this.buildVersion));
|
||||
SysTray.getInstance().setToolTipText(tooltip);
|
||||
|
||||
this.callbackExecutor.execute(() -> {
|
||||
@ -933,6 +945,11 @@ public class Controller extends Thread {
|
||||
// Callbacks for/from network
|
||||
|
||||
public void doNetworkBroadcast() {
|
||||
if (Settings.getInstance().isLite()) {
|
||||
// Lite nodes have nothing to broadcast
|
||||
return;
|
||||
}
|
||||
|
||||
Network network = Network.getInstance();
|
||||
|
||||
// Send (if outbound) / Request peer lists
|
||||
@ -1450,11 +1467,13 @@ public class Controller extends Thread {
|
||||
private void onNetworkHeightV2Message(Peer peer, Message message) {
|
||||
HeightV2Message heightV2Message = (HeightV2Message) message;
|
||||
|
||||
// If peer is inbound and we've not updated their height
|
||||
// then this is probably their initial HEIGHT_V2 message
|
||||
// so they need a corresponding HEIGHT_V2 message from us
|
||||
if (!peer.isOutbound() && (peer.getChainTipData() == null || peer.getChainTipData().getLastHeight() == null))
|
||||
peer.sendMessage(Network.getInstance().buildHeightMessage(peer, getChainTip()));
|
||||
if (!Settings.getInstance().isLite()) {
|
||||
// If peer is inbound and we've not updated their height
|
||||
// then this is probably their initial HEIGHT_V2 message
|
||||
// so they need a corresponding HEIGHT_V2 message from us
|
||||
if (!peer.isOutbound() && (peer.getChainTipData() == null || peer.getChainTipData().getLastHeight() == null))
|
||||
peer.sendMessage(Network.getInstance().buildHeightMessage(peer, getChainTip()));
|
||||
}
|
||||
|
||||
// Update peer chain tip data
|
||||
PeerChainTipData newChainTipData = new PeerChainTipData(heightV2Message.getHeight(), heightV2Message.getSignature(), heightV2Message.getTimestamp(), heightV2Message.getMinterPublicKey());
|
||||
@ -1515,6 +1534,11 @@ public class Controller extends Thread {
|
||||
* @return boolean - whether our node's blockchain is up to date or not
|
||||
*/
|
||||
public boolean isUpToDate(Long minLatestBlockTimestamp) {
|
||||
if (Settings.getInstance().isLite()) {
|
||||
// Lite nodes are always "up to date"
|
||||
return true;
|
||||
}
|
||||
|
||||
// Do we even have a vaguely recent block?
|
||||
if (minLatestBlockTimestamp == null)
|
||||
return false;
|
||||
|
@ -134,6 +134,11 @@ public class Synchronizer extends Thread {
|
||||
public void run() {
|
||||
Thread.currentThread().setName("Synchronizer");
|
||||
|
||||
if (Settings.getInstance().isLite()) {
|
||||
// Lite nodes don't need to sync
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
while (running && !Controller.isStopping()) {
|
||||
Thread.sleep(1000);
|
||||
|
@ -11,6 +11,7 @@ import org.qortal.network.message.TransactionSignaturesMessage;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.settings.Settings;
|
||||
import org.qortal.transaction.Transaction;
|
||||
import org.qortal.utils.Base58;
|
||||
import org.qortal.utils.NTP;
|
||||
@ -105,6 +106,8 @@ public class TransactionImporter extends Thread {
|
||||
|
||||
List<Transaction> sigValidTransactions = new ArrayList<>();
|
||||
|
||||
boolean isLiteNode = Settings.getInstance().isLite();
|
||||
|
||||
// Signature validation round - does not require blockchain lock
|
||||
for (Map.Entry<TransactionData, Boolean> transactionEntry : incomingTransactionsCopy.entrySet()) {
|
||||
// Quick exit?
|
||||
@ -118,6 +121,12 @@ public class TransactionImporter extends Thread {
|
||||
// Only validate signature if we haven't already done so
|
||||
Boolean isSigValid = transactionEntry.getValue();
|
||||
if (!Boolean.TRUE.equals(isSigValid)) {
|
||||
if (isLiteNode) {
|
||||
// Lite nodes can't validate transactions, so can only assume that everything is valid
|
||||
sigValidTransactions.add(transaction);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!transaction.isSignatureValid()) {
|
||||
String signature58 = Base58.encode(transactionData.getSignature());
|
||||
|
||||
|
@ -19,6 +19,11 @@ public class AtStatesPruner implements Runnable {
|
||||
public void run() {
|
||||
Thread.currentThread().setName("AT States pruner");
|
||||
|
||||
if (Settings.getInstance().isLite()) {
|
||||
// Nothing to prune in lite mode
|
||||
return;
|
||||
}
|
||||
|
||||
boolean archiveMode = false;
|
||||
if (!Settings.getInstance().isTopOnly()) {
|
||||
// Top-only mode isn't enabled, but we might want to prune for the purposes of archiving
|
||||
|
@ -19,6 +19,11 @@ public class AtStatesTrimmer implements Runnable {
|
||||
public void run() {
|
||||
Thread.currentThread().setName("AT States trimmer");
|
||||
|
||||
if (Settings.getInstance().isLite()) {
|
||||
// Nothing to trim in lite mode
|
||||
return;
|
||||
}
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
int trimStartHeight = repository.getATRepository().getAtTrimHeight();
|
||||
|
||||
|
@ -21,7 +21,7 @@ public class BlockArchiver implements Runnable {
|
||||
public void run() {
|
||||
Thread.currentThread().setName("Block archiver");
|
||||
|
||||
if (!Settings.getInstance().isArchiveEnabled()) {
|
||||
if (!Settings.getInstance().isArchiveEnabled() || Settings.getInstance().isLite()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,11 @@ public class BlockPruner implements Runnable {
|
||||
public void run() {
|
||||
Thread.currentThread().setName("Block pruner");
|
||||
|
||||
if (Settings.getInstance().isLite()) {
|
||||
// Nothing to prune in lite mode
|
||||
return;
|
||||
}
|
||||
|
||||
boolean archiveMode = false;
|
||||
if (!Settings.getInstance().isTopOnly()) {
|
||||
// Top-only mode isn't enabled, but we might want to prune for the purposes of archiving
|
||||
|
@ -21,6 +21,11 @@ public class OnlineAccountsSignaturesTrimmer implements Runnable {
|
||||
public void run() {
|
||||
Thread.currentThread().setName("Online Accounts trimmer");
|
||||
|
||||
if (Settings.getInstance().isLite()) {
|
||||
// Nothing to trim in lite mode
|
||||
return;
|
||||
}
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
// Don't even start trimming until initial rush has ended
|
||||
Thread.sleep(INITIAL_SLEEP_PERIOD);
|
||||
|
@ -1062,11 +1062,13 @@ public class Network {
|
||||
// (If inbound sent anything here, it's possible it could be processed out-of-order with handshake message).
|
||||
|
||||
if (peer.isOutbound()) {
|
||||
// Send our height
|
||||
Message heightMessage = buildHeightMessage(peer, Controller.getInstance().getChainTip());
|
||||
if (!peer.sendMessage(heightMessage)) {
|
||||
peer.disconnect("failed to send height/info");
|
||||
return;
|
||||
if (!Settings.getInstance().isLite()) {
|
||||
// Send our height
|
||||
Message heightMessage = buildHeightMessage(peer, Controller.getInstance().getChainTip());
|
||||
if (!peer.sendMessage(heightMessage)) {
|
||||
peer.disconnect("failed to send height/info");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Send our peers list
|
||||
|
@ -62,6 +62,11 @@ public abstract class RepositoryManager {
|
||||
}
|
||||
|
||||
public static boolean archive(Repository repository) {
|
||||
if (Settings.getInstance().isLite()) {
|
||||
// Lite nodes have no blockchain
|
||||
return false;
|
||||
}
|
||||
|
||||
// Bulk archive the database the first time we use archive mode
|
||||
if (Settings.getInstance().isArchiveEnabled()) {
|
||||
if (RepositoryManager.canArchiveOrPrune()) {
|
||||
@ -82,6 +87,11 @@ public abstract class RepositoryManager {
|
||||
}
|
||||
|
||||
public static boolean prune(Repository repository) {
|
||||
if (Settings.getInstance().isLite()) {
|
||||
// Lite nodes have no blockchain
|
||||
return false;
|
||||
}
|
||||
|
||||
// Bulk prune the database the first time we use top-only or block archive mode
|
||||
if (Settings.getInstance().isTopOnly() ||
|
||||
Settings.getInstance().isArchiveEnabled()) {
|
||||
|
@ -530,11 +530,6 @@ public abstract class Transaction {
|
||||
if (now >= this.getDeadline())
|
||||
return ValidationResult.TIMESTAMP_TOO_OLD;
|
||||
|
||||
// Transactions with a expiry prior to latest block's timestamp are too old
|
||||
BlockData latestBlock = repository.getBlockRepository().getLastBlock();
|
||||
if (this.getDeadline() <= latestBlock.getTimestamp())
|
||||
return ValidationResult.TIMESTAMP_TOO_OLD;
|
||||
|
||||
// Transactions with a timestamp too far into future are too new
|
||||
long maxTimestamp = now + Settings.getInstance().getMaxTransactionTimestampFuture();
|
||||
if (this.transactionData.getTimestamp() > maxTimestamp)
|
||||
@ -545,14 +540,29 @@ public abstract class Transaction {
|
||||
if (feeValidationResult != ValidationResult.OK)
|
||||
return feeValidationResult;
|
||||
|
||||
PublicKeyAccount creator = this.getCreator();
|
||||
if (creator == null)
|
||||
return ValidationResult.MISSING_CREATOR;
|
||||
if (Settings.getInstance().isLite()) {
|
||||
// Everything from this point is difficult to validate for a lite node, since it has no blocks.
|
||||
// For now, we will assume it is valid, to allow it to move around the network easily.
|
||||
// If it turns out to be invalid, other full/top-only nodes will reject it on receipt.
|
||||
// Lite nodes would never mint a block, so there's not much risk of holding invalid transactions.
|
||||
// TODO: implement lite-only validation for each transaction type
|
||||
return ValidationResult.OK;
|
||||
}
|
||||
|
||||
// Reject if unconfirmed pile already has X transactions from same creator
|
||||
if (countUnconfirmedByCreator(creator) >= Settings.getInstance().getMaxUnconfirmedPerAccount())
|
||||
return ValidationResult.TOO_MANY_UNCONFIRMED;
|
||||
|
||||
// Transactions with a expiry prior to latest block's timestamp are too old
|
||||
// Not relevant for lite nodes, as they don't have any blocks
|
||||
BlockData latestBlock = repository.getBlockRepository().getLastBlock();
|
||||
if (this.getDeadline() <= latestBlock.getTimestamp())
|
||||
return ValidationResult.TIMESTAMP_TOO_OLD;
|
||||
|
||||
PublicKeyAccount creator = this.getCreator();
|
||||
if (creator == null)
|
||||
return ValidationResult.MISSING_CREATOR;
|
||||
|
||||
// Check transaction's txGroupId
|
||||
if (!this.isValidTxGroupId())
|
||||
return ValidationResult.INVALID_TX_GROUP_ID;
|
||||
|
@ -27,6 +27,8 @@ DB_MAINTENANCE = Datenbank Instandhaltung
|
||||
|
||||
EXIT = Verlassen
|
||||
|
||||
LITE_NODE = Lite node
|
||||
|
||||
MINTING_DISABLED = NOT minting
|
||||
|
||||
MINTING_ENABLED = \u2714 Minting
|
||||
|
@ -27,6 +27,8 @@ DB_MAINTENANCE = Database Maintenance
|
||||
|
||||
EXIT = Exit
|
||||
|
||||
LITE_NODE = Lite node
|
||||
|
||||
MINTING_DISABLED = NOT minting
|
||||
|
||||
MINTING_ENABLED = \u2714 Minting
|
||||
|
@ -27,6 +27,8 @@ DB_MAINTENANCE = Tietokannan ylläpito
|
||||
|
||||
EXIT = Pois
|
||||
|
||||
LITE_NODE = Lite node
|
||||
|
||||
MINTING_DISABLED = EI lyö rahaa
|
||||
|
||||
MINTING_ENABLED = \u2714 Lyö rahaa
|
||||
|
@ -27,6 +27,8 @@ DB_MAINTENANCE = Maintenance de la base de données
|
||||
|
||||
EXIT = Quitter
|
||||
|
||||
LITE_NODE = Lite node
|
||||
|
||||
MINTING_DISABLED = NE mint PAS
|
||||
|
||||
MINTING_ENABLED = \u2714 Minting
|
||||
|
@ -27,6 +27,8 @@ DB_MAINTENANCE = Adatbázis karbantartás
|
||||
|
||||
EXIT = Kilépés
|
||||
|
||||
LITE_NODE = Lite node
|
||||
|
||||
MINTING_DISABLED = QORT-érmeverés jelenleg nincs folyamatban
|
||||
|
||||
MINTING_ENABLED = \u2714 QORT-érmeverés folyamatban
|
||||
|
@ -27,6 +27,8 @@ DB_MAINTENANCE = Manutenzione del database
|
||||
|
||||
EXIT = Uscita
|
||||
|
||||
LITE_NODE = Lite node
|
||||
|
||||
MINTING_DISABLED = Conio disabilitato
|
||||
|
||||
MINTING_ENABLED = \u2714 Conio abilitato
|
||||
|
@ -27,6 +27,8 @@ DB_MAINTENANCE = Database Onderhoud
|
||||
|
||||
EXIT = Verlaten
|
||||
|
||||
LITE_NODE = Lite node
|
||||
|
||||
MINTING_DISABLED = Minten is uitgeschakeld
|
||||
|
||||
MINTING_ENABLED = \u2714 Minten is ingeschakeld
|
||||
|
@ -27,6 +27,8 @@ DB_MAINTENANCE = Обслуживание базы данных
|
||||
|
||||
EXIT = Выход
|
||||
|
||||
LITE_NODE = Lite node
|
||||
|
||||
MINTING_DISABLED = Чеканка отключена
|
||||
|
||||
MINTING_ENABLED = \u2714 Чеканка активна
|
||||
|
@ -27,6 +27,8 @@ DB_MAINTENANCE = 数据库维护
|
||||
|
||||
EXIT = 退出核心
|
||||
|
||||
LITE_NODE = Lite node
|
||||
|
||||
MINTING_DISABLED = 没有铸币
|
||||
|
||||
MINTING_ENABLED = \u2714 铸币
|
||||
|
@ -27,6 +27,8 @@ DB_MAINTENANCE = 數據庫維護
|
||||
|
||||
EXIT = 退出核心
|
||||
|
||||
LITE_NODE = Lite node
|
||||
|
||||
MINTING_DISABLED = 沒有鑄幣
|
||||
|
||||
MINTING_ENABLED = \u2714 鑄幣
|
||||
|
@ -15,7 +15,7 @@ public class CheckTranslations {
|
||||
private static final String[] SUPPORTED_LANGS = new String[] { "en", "de", "zh", "ru" };
|
||||
private static final Set<String> SYSTRAY_KEYS = Set.of("AUTO_UPDATE", "APPLYING_UPDATE_AND_RESTARTING", "BLOCK_HEIGHT",
|
||||
"BUILD_VERSION", "CHECK_TIME_ACCURACY", "CONNECTING", "CONNECTION", "CONNECTIONS", "CREATING_BACKUP_OF_DB_FILES",
|
||||
"DB_BACKUP", "DB_CHECKPOINT", "EXIT", "MINTING_DISABLED", "MINTING_ENABLED", "OPEN_UI", "PERFORMING_DB_CHECKPOINT",
|
||||
"DB_BACKUP", "DB_CHECKPOINT", "EXIT", "LITE_NODE", "MINTING_DISABLED", "MINTING_ENABLED", "OPEN_UI", "PERFORMING_DB_CHECKPOINT",
|
||||
"SYNCHRONIZE_CLOCK", "SYNCHRONIZING_BLOCKCHAIN", "SYNCHRONIZING_CLOCK");
|
||||
|
||||
private static String failurePrefix;
|
||||
|
Loading…
Reference in New Issue
Block a user