Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
0fded97
Add missing properties
Dec 29, 2025
b0f4826
Add catches to manage gracefully the shutdown process
Dec 29, 2025
10823a8
Shutdown TradeBot
Dec 29, 2025
4998ab1
Restore unsorted featureTriggers section
Dec 29, 2025
028c290
Add missing properties to make testnet start
Dec 30, 2025
b3ea65d
Revert to false
Dec 30, 2025
756e8eb
Add check for controller shutdown to avoid electrum connections
Jan 4, 2026
41449cc
Add logger info
Jan 4, 2026
e33b4fd
Remove duplicated condition
Jan 4, 2026
6b88993
Add timers shutdown
Jan 4, 2026
4af2761
Change logger type
Jan 4, 2026
4f0d448
Add shutdown method
Jan 4, 2026
396259a
Improve logger messages, invoke shutdown methos for HSQLDBCacheUtils …
Jan 4, 2026
64564aa
Improve shutdown
Jan 4, 2026
a33bdf8
Added connection spinup upon need only for Electrum-based connections.
crowetic Jan 13, 2026
d63d02a
removed unnecessary formatting changes
crowetic Jan 13, 2026
f31f378
removed unnecessary formatting changes
crowetic Jan 13, 2026
14bacf2
On‑demand connection management with idle disconnects and in‑flight R…
crowetic Jan 14, 2026
6b4cb8f
Added Null Pointer Exception prevention and retry logic for failed co…
crowetic Jan 14, 2026
53ce930
Fixed reading synchronized list without locking it by removing stream…
crowetic Jan 14, 2026
532ef22
added fix for 'cannot invoke intvalue' in follower thread by checking…
crowetic Jan 14, 2026
5641ea3
Merge pull request #31 from crowetic/electrumX-update-jan-2026
kennycud Jan 17, 2026
77d212f
Merge branch 'master' into fix/shutdown-improvements
nbenaglia Jan 17, 2026
6607d6d
Merge pull request #24 from nbenaglia/fix/shutdown-improvements
kennycud Jan 17, 2026
b9396ed
skip foreign fees with unavailable AT data when processing foreign fees
kennycud Jan 17, 2026
6cf7d2d
Catches the InterruptedException in method fetchAllMetadata
Jan 18, 2026
53958e5
Improve performances of rebuildAllNames
Jan 18, 2026
08378f9
Improve progress logging
Jan 18, 2026
2290201
Add clearer logging
Jan 18, 2026
0549ca3
Merge pull request #32 from nbenaglia/feature/shutdown-improvements-bis
kennycud Jan 18, 2026
23b7083
Merge pull request #33 from nbenaglia/feature/rebuild_name_refactoring
kennycud Jan 18, 2026
d7f6f77
removing group chat messages from non-members
kennycud Jan 18, 2026
5b15867
general chat transactions are rejected when sent and are discarded wh…
kennycud Jan 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,11 @@ public String processTransaction(String rawBytes58, @HeaderParam(ApiService.API_
if (transactionData == null)
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);

// general chat transactions are invalid
if( transactionData.getType() == TransactionType.CHAT && transactionData.getTxGroupId() == 0 && transactionData.getRecipient() == null) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
}

try (final Repository repository = RepositoryManager.getRepository()) {
Transaction transaction = Transaction.fromData(repository, transactionData);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ public void onWebSocketConnect(Session session) {
if (txGroupIds != null && txGroupIds.size() == 1) {
int txGroupId = Integer.parseInt(txGroupIds.get(0));

// reject general chat
if( txGroupId == 0 ) {
session.close(4001, "invalid criteria");
return;
}

try (final Repository repository = RepositoryManager.getRepository()) {
List<ChatMessage> chatMessages = repository.getChatRepository().getMessagesMatchingCriteria(
null,
Expand Down
42 changes: 29 additions & 13 deletions src/main/java/org/qortal/controller/Controller.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import org.qortal.network.PeerAddress;
import org.qortal.network.message.*;
import org.qortal.repository.*;
import org.qortal.repository.hsqldb.HSQLDBCacheUtils;
import org.qortal.repository.hsqldb.HSQLDBRepositoryFactory;
import org.qortal.settings.Settings;
import org.qortal.transaction.Transaction;
Expand Down Expand Up @@ -423,15 +424,15 @@ public static void main(String[] args) {
}

if( Settings.getInstance().isDbCacheEnabled() ) {
LOGGER.info("Db Cache Starting ...");
LOGGER.info("Starting Db Cache...");
HSQLDBDataCacheManager hsqldbDataCacheManager = new HSQLDBDataCacheManager();
hsqldbDataCacheManager.start();
}
else {
LOGGER.info("Db Cache Disabled");
}

LOGGER.info("Arbitrary Indexing Starting ...");
LOGGER.info("Starting Arbitrary Indexing...");
ArbitraryIndexUtils.startCaching(
Settings.getInstance().getArbitraryIndexingPriority(),
Settings.getInstance().getArbitraryIndexingFrequency()
Expand All @@ -441,15 +442,15 @@ public static void main(String[] args) {
Optional<HSQLDBBalanceRecorder> recorder = HSQLDBBalanceRecorder.getInstance();

if( recorder.isPresent() ) {
LOGGER.info("Balance Recorder Starting ...");
LOGGER.info("Starting Balance Recorder...");
recorder.get().start();
}
else {
LOGGER.info("Balance Recorder won't start.");
}
}
else {
LOGGER.info("Balance Recorder Disabled");
LOGGER.info("Balance Recorder disabled");
}
} catch (DataException e) {
// If exception has no cause or message then repository is in use by some other process.
Expand All @@ -469,17 +470,17 @@ public static void main(String[] args) {

// Rebuild Names table and check database integrity (if enabled)
NamesDatabaseIntegrityCheck namesDatabaseIntegrityCheck = new NamesDatabaseIntegrityCheck();
LOGGER.info("Rebuilding all names...");
namesDatabaseIntegrityCheck.rebuildAllNames();
if (Settings.getInstance().isNamesIntegrityCheckEnabled()) {
LOGGER.info("Running database integrity check...");
namesDatabaseIntegrityCheck.runIntegrityCheck();
}

LOGGER.info("Validating blockchain");
LOGGER.info("Validating blockchain...");
try {
BlockChain.validate();

Controller.getInstance().refillLatestBlocksCache();
LOGGER.info(String.format("Our chain height at start-up: %d", Controller.getInstance().getChainHeight()));
LOGGER.info("Chain height at start-up: {}", Controller.getInstance().getChainHeight());
} catch (DataException e) {
LOGGER.error("Couldn't validate blockchain", e);
Gui.getInstance().fatalError("Blockchain validation issue", e);
Expand Down Expand Up @@ -565,7 +566,6 @@ public void run() {
);
}


LOGGER.info("Starting online accounts manager");
OnlineAccountsManager.getInstance().start();

Expand Down Expand Up @@ -599,7 +599,7 @@ public void run() {
}

if (Settings.getInstance().isGatewayEnabled()) {
LOGGER.info(String.format("Starting gateway service on port %d", Settings.getInstance().getGatewayPort()));
LOGGER.info("Starting gateway service on port {}", Settings.getInstance().getGatewayPort());
try {
GatewayService gatewayService = GatewayService.getInstance();
gatewayService.start();
Expand All @@ -612,7 +612,7 @@ public void run() {
}

if (Settings.getInstance().isDomainMapEnabled()) {
LOGGER.info(String.format("Starting domain map service on port %d", Settings.getInstance().getDomainMapPort()));
LOGGER.info("Starting domain map service on port {}", Settings.getInstance().getDomainMapPort());
try {
DomainMapService domainMapService = DomainMapService.getInstance();
domainMapService.start();
Expand Down Expand Up @@ -782,12 +782,12 @@ public void run() {
if (ntpTime != null) {
if (ntpTime != now)
// Only log if non-zero offset
LOGGER.info(String.format("Adjusting system time by NTP offset: %dms", ntpTime - now));
LOGGER.info("Adjusting system time by NTP offset: {}ms", ntpTime - now);

ntpCheckTimestamp = now + NTP_POST_SYNC_CHECK_PERIOD;
requestSysTrayUpdate = true;
} else {
LOGGER.info(String.format("No NTP offset yet"));
LOGGER.info("No NTP offset yet");
ntpCheckTimestamp = now + NTP_PRE_SYNC_CHECK_PERIOD;
// We can't do much without a valid NTP time
continue;
Expand Down Expand Up @@ -1120,6 +1120,11 @@ public void shutdown() {

LOGGER.info("Shutting down synchronizer");
Synchronizer.getInstance().shutdown();
try {
Synchronizer.getInstance().join();
} catch (InterruptedException e) {
// We were interrupted while waiting for thread to join
}

LOGGER.info("Shutting down API");
ApiService.getInstance().stop();
Expand Down Expand Up @@ -1177,6 +1182,17 @@ public void shutdown() {
// We were interrupted while waiting for thread to join
}

LOGGER.info("Shutting down TradeBot");
TradeBot.getInstance().shutdown();

// Shutdown database cache timers before closing repository
LOGGER.info("Shutting down database cache timers");
HSQLDBCacheUtils.shutdown();

// Shutdown arbitrary metadata manager scheduler before closing repository
LOGGER.info("Shutting down arbitrary metadata manager");
ArbitraryMetadataManager.getInstance().shutdown();

// Make sure we're the only thread modifying the blockchain when shutting down the repository
ReentrantLock blockchainLock = Controller.getInstance().getBlockchainLock();
try {
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/org/qortal/controller/ForeignFeesManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,9 @@ private void processForeignFeesImportQueue() {

ATData atData = repository.getATRepository().fromATAddress(atAddress);

// if AT data is not available, then continue on to the next AT
if( atData == null ) continue;

LOGGER.debug("verify signer for atAddress = " + atAddress);

// determine if the creator authorized the foreign fee
Expand Down
38 changes: 37 additions & 1 deletion src/main/java/org/qortal/controller/TransactionImporter.java
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,27 @@ public void run() {
public void shutdown() {
isStopping = true;
this.interrupt();

// Shutdown all schedulers
LOGGER.info("Shutting down TransactionImporter schedulers");
try {
getTransactionMessageScheduler.shutdownNow();
getUnconfirmedTransactionsMessageScheduler.shutdownNow();
signatureMessageScheduler.shutdownNow();

if (!getTransactionMessageScheduler.awaitTermination(5, TimeUnit.SECONDS)) {
LOGGER.warn("getTransactionMessageScheduler did not terminate in time");
}
if (!getUnconfirmedTransactionsMessageScheduler.awaitTermination(5, TimeUnit.SECONDS)) {
LOGGER.warn("getUnconfirmedTransactionsMessageScheduler did not terminate in time");
}
if (!signatureMessageScheduler.awaitTermination(5, TimeUnit.SECONDS)) {
LOGGER.warn("signatureMessageScheduler did not terminate in time");
}
} catch (InterruptedException e) {
LOGGER.warn("Interrupted while waiting for TransactionImporter schedulers to terminate", e);
Thread.currentThread().interrupt();
}
}


Expand Down Expand Up @@ -245,6 +266,13 @@ private void importTransactionsInQueue() {
return;
}

// discard general chat transactions, chat transactions with no group and no recipient
sigValidTransactions.removeIf(
transactionData -> transactionData.getType() == Transaction.TransactionType.CHAT &&
transactionData.getTxGroupId() == 0 &&
transactionData.getRecipient() == null
);

if (Synchronizer.getInstance().isSyncRequested() || Synchronizer.getInstance().isSynchronizing()) {
// Prioritize syncing, and don't attempt to lock
return;
Expand Down Expand Up @@ -397,6 +425,9 @@ public void onNetworkGetTransactionMessage(Peer peer, Message message) {
}

private void processNetworkGetTransactionMessages() {
if (Controller.isStopping()) {
return;
}

try {
List<PeerMessage> messagesToProcess;
Expand Down Expand Up @@ -490,14 +521,16 @@ private static void sendTransactionMessage(String signature58, TransactionData d
// Scheduled executor service to process messages every second
private final ScheduledExecutorService getUnconfirmedTransactionsMessageScheduler = Executors.newScheduledThreadPool(1);


public void onNetworkGetUnconfirmedTransactionsMessage(Peer peer, Message message) {
synchronized (getUnconfirmedTransactionsMessageLock) {
getUnconfirmedTransactionsMessageList.add(new PeerMessage(peer, message));
}
}

private void processNetworkGetUnconfirmedTransactionsMessages() {
if (Controller.isStopping()) {
return;
}

List<PeerMessage> messagesToProcess;
synchronized (getUnconfirmedTransactionsMessageLock) {
Expand Down Expand Up @@ -542,6 +575,9 @@ public void onNetworkTransactionSignaturesMessage(Peer peer, Message message) {
}

public void processNetworkTransactionSignaturesMessage() {
if (Controller.isStopping()) {
return;
}

try {
List<PeerMessage> messagesToProcess;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ public void run() {

// Process queue
processResourceQueue();
} catch (InterruptedException e) {
// Check if we're shutting down
if (Controller.isStopping()) {
LOGGER.info("Arbitrary Data Cache Manager shutting down");
break;
}
LOGGER.warn("Arbitrary Data Cache Manager interrupted, retrying...", e);
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
Thread.sleep(600_000L); // wait 10 minutes to continue
Expand All @@ -71,6 +78,10 @@ public void run() {

// Clear queue before terminating thread
processResourceQueue();
} catch (InterruptedException e) {
if (!Controller.isStopping()) {
LOGGER.error("Arbitrary Data Cache Manager interrupted unexpectedly", e);
}
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,10 @@ private void fetchAllMetadata() throws InterruptedException {
);
} catch (DataException e) {
LOGGER.error("Repository issue when fetching arbitrary transaction data", e);
} catch (InterruptedException e) {
// Thread interrupted during shutdown - restore interrupt status and exit
Thread.currentThread().interrupt();
return;
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,11 @@ public void onNetworkGetArbitraryMetadataMessage(Peer peer, Message message) {

private void processNetworkGetArbitraryMetadataMessage() {

// Exit gracefully if shutting down
if (Controller.isStopping()) {
return;
}

try {
List<PeerMessage> messagesToProcess;
synchronized (lock) {
Expand Down Expand Up @@ -531,4 +536,29 @@ private void processNetworkGetArbitraryMetadataMessage() {
LOGGER.error(e.getMessage(), e);
}
}

/**
* Shutdown the scheduler
*
* Stops the scheduled executor service to allow clean shutdown
*/
public void shutdown() {
LOGGER.info("Shutting down ArbitraryMetadataManager scheduler...");

if (!scheduler.isShutdown()) {
scheduler.shutdown();
try {
if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
scheduler.shutdownNow();
LOGGER.debug("Scheduler forced shutdown");
}
} catch (InterruptedException e) {
scheduler.shutdownNow();
Thread.currentThread().interrupt();
}
LOGGER.debug("Scheduler shutdown complete");
}

LOGGER.info("ArbitraryMetadataManager scheduler shutdown complete");
}
}
3 changes: 2 additions & 1 deletion src/main/java/org/qortal/controller/arbitrary/Follower.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ private void fetch(OptionalInt limit) {
final int blockHeightThreshold = repository.getBlockRepository().getBlockchainHeight() - limit.getAsInt();

transactionsInReverseOrder
= latestArbitraryTransactionsByName.stream().filter(tx -> tx.getBlockHeight() > blockHeightThreshold)
= latestArbitraryTransactionsByName.stream()
.filter(tx -> tx.getBlockHeight() != null && tx.getBlockHeight() > blockHeightThreshold)
.collect(Collectors.toList());
} else {
transactionsInReverseOrder = latestArbitraryTransactionsByName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,18 +45,12 @@ private HSQLDBBalanceRecorder( int priorityRequested, int frequency, int capacit
public static Optional<HSQLDBBalanceRecorder> getInstance() {

if( SINGLETON == null ) {

SINGLETON
= new HSQLDBBalanceRecorder(
Settings.getInstance().getBalanceRecorderPriority(),
Settings.getInstance().getBalanceRecorderFrequency(),
Settings.getInstance().getBalanceRecorderCapacity()
);

}
else if( SINGLETON == null ) {

return Optional.empty();
}

return Optional.of(SINGLETON);
Expand All @@ -72,13 +66,11 @@ public void run() {

public List<BlockHeightRangeAddressAmounts> getLatestDynamics(int limit, long offset) {

List<BlockHeightRangeAddressAmounts> latest = this.balanceDynamics.stream()
return this.balanceDynamics.stream()
.sorted(BalanceRecorderUtils.BLOCK_HEIGHT_RANGE_ADDRESS_AMOUNTS_COMPARATOR.reversed())
.skip(offset)
.limit(limit)
.collect(Collectors.toList());

return latest;
}

public List<BlockHeightRange> getRanges(Integer offset, Integer limit, Boolean reverse) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ public void run() {
repository.discardChanges();
return;
}
} catch (InterruptedException e) {
if (Controller.isStopping()) {
LOGGER.info("Block Archiving shutting down");
} else {
LOGGER.error("Block Archiving interrupted during initialization. Restart ASAP. Report this error immediately to the developers.", e);
}
return;
} catch (Exception e) {
LOGGER.error("Block Archiving is not working! Not trying again. Restart ASAP. Report this error immediately to the developers.", e);
return;
Expand Down
Loading
Loading