Browse Source

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.
lite-node
CalDescent 3 years ago
parent
commit
cfe92525ed
  1. 5
      src/main/java/org/qortal/controller/BlockMinter.java
  2. 68
      src/main/java/org/qortal/controller/Controller.java
  3. 5
      src/main/java/org/qortal/controller/Synchronizer.java
  4. 9
      src/main/java/org/qortal/controller/TransactionImporter.java
  5. 5
      src/main/java/org/qortal/controller/repository/AtStatesPruner.java
  6. 5
      src/main/java/org/qortal/controller/repository/AtStatesTrimmer.java
  7. 2
      src/main/java/org/qortal/controller/repository/BlockArchiver.java
  8. 5
      src/main/java/org/qortal/controller/repository/BlockPruner.java
  9. 5
      src/main/java/org/qortal/controller/repository/OnlineAccountsSignaturesTrimmer.java
  10. 12
      src/main/java/org/qortal/network/Network.java
  11. 10
      src/main/java/org/qortal/repository/RepositoryManager.java
  12. 26
      src/main/java/org/qortal/transaction/Transaction.java
  13. 2
      src/main/resources/i18n/SysTray_de.properties
  14. 2
      src/main/resources/i18n/SysTray_en.properties
  15. 2
      src/main/resources/i18n/SysTray_fi.properties
  16. 2
      src/main/resources/i18n/SysTray_fr.properties
  17. 2
      src/main/resources/i18n/SysTray_hu.properties
  18. 2
      src/main/resources/i18n/SysTray_it.properties
  19. 2
      src/main/resources/i18n/SysTray_nl.properties
  20. 2
      src/main/resources/i18n/SysTray_ru.properties
  21. 2
      src/main/resources/i18n/SysTray_zh_CN.properties
  22. 2
      src/main/resources/i18n/SysTray_zh_TW.properties
  23. 2
      src/test/java/org/qortal/test/apps/CheckTranslations.java

5
src/main/java/org/qortal/controller/BlockMinter.java

@ -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

68
src/main/java/org/qortal/controller/Controller.java

@ -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()) {
// Rebuild Names table and check database integrity (if enabled)
NamesDatabaseIntegrityCheck namesDatabaseIntegrityCheck = new NamesDatabaseIntegrityCheck();
namesDatabaseIntegrityCheck.rebuildAllNames();
if (Settings.getInstance().isNamesIntegrityCheckEnabled()) {
namesDatabaseIntegrityCheck.runIntegrityCheck();
}
LOGGER.info("Validating blockchain");
try {
BlockChain.validate();
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
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;

5
src/main/java/org/qortal/controller/Synchronizer.java

@ -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);

9
src/main/java/org/qortal/controller/TransactionImporter.java

@ -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());

5
src/main/java/org/qortal/controller/repository/AtStatesPruner.java

@ -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

5
src/main/java/org/qortal/controller/repository/AtStatesTrimmer.java

@ -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();

2
src/main/java/org/qortal/controller/repository/BlockArchiver.java

@ -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;
}

5
src/main/java/org/qortal/controller/repository/BlockPruner.java

@ -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

5
src/main/java/org/qortal/controller/repository/OnlineAccountsSignaturesTrimmer.java

@ -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);

12
src/main/java/org/qortal/network/Network.java

@ -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

10
src/main/java/org/qortal/repository/RepositoryManager.java

@ -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()) {

26
src/main/java/org/qortal/transaction/Transaction.java

@ -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;

2
src/main/resources/i18n/SysTray_de.properties

@ -27,6 +27,8 @@ DB_MAINTENANCE = Datenbank Instandhaltung
EXIT = Verlassen
LITE_NODE = Lite node
MINTING_DISABLED = NOT minting
MINTING_ENABLED = \u2714 Minting

2
src/main/resources/i18n/SysTray_en.properties

@ -27,6 +27,8 @@ DB_MAINTENANCE = Database Maintenance
EXIT = Exit
LITE_NODE = Lite node
MINTING_DISABLED = NOT minting
MINTING_ENABLED = \u2714 Minting

2
src/main/resources/i18n/SysTray_fi.properties

@ -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

2
src/main/resources/i18n/SysTray_fr.properties

@ -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

2
src/main/resources/i18n/SysTray_hu.properties

@ -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

2
src/main/resources/i18n/SysTray_it.properties

@ -27,6 +27,8 @@ DB_MAINTENANCE = Manutenzione del database
EXIT = Uscita
LITE_NODE = Lite node
MINTING_DISABLED = Conio disabilitato
MINTING_ENABLED = \u2714 Conio abilitato

2
src/main/resources/i18n/SysTray_nl.properties

@ -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

2
src/main/resources/i18n/SysTray_ru.properties

@ -27,6 +27,8 @@ DB_MAINTENANCE = Обслуживание базы данных
EXIT = Выход
LITE_NODE = Lite node
MINTING_DISABLED = Чеканка отключена
MINTING_ENABLED = \u2714 Чеканка активна

2
src/main/resources/i18n/SysTray_zh_CN.properties

@ -27,6 +27,8 @@ DB_MAINTENANCE = 数据库维护
EXIT = 退出核心
LITE_NODE = Lite node
MINTING_DISABLED = 没有铸币
MINTING_ENABLED = \u2714 铸币

2
src/main/resources/i18n/SysTray_zh_TW.properties

@ -27,6 +27,8 @@ DB_MAINTENANCE = 數據庫維護
EXIT = 退出核心
LITE_NODE = Lite node
MINTING_DISABLED = 沒有鑄幣
MINTING_ENABLED = \u2714 鑄幣

2
src/test/java/org/qortal/test/apps/CheckTranslations.java

@ -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…
Cancel
Save