Skip to content

feat(db): support solidity conditional shutdown#70

Open
317787106 wants to merge 1 commit intodevelopfrom
feature/solidity_shutdown
Open

feat(db): support solidity conditional shutdown#70
317787106 wants to merge 1 commit intodevelopfrom
feature/solidity_shutdown

Conversation

@317787106
Copy link
Copy Markdown
Owner

@317787106 317787106 commented Apr 16, 2026

Summary

Closes tronprotocol#6610.

Refactors SolidityNode into a Spring-managed component with proper lifecycle support, enabling it to respond to conditional shutdown signals (block height, block time, block count) already implemented in TronNetDelegate.processBlock. The previous static start() approach bypassed Spring entirely, so the shutdown conditions could never be reached during solidity sync.

Problem

SolidityNode previously initialized its own TronApplicationContext, created a SolidityNode instance manually, and called appT.blockUntilShutdown() in a separate JVM entry path. This architecture had two consequences:

  1. Shutdown conditions were unreachable. The conditional-shutdown logic lives in TronNetDelegate.processBlock (checking latestSolidityNumShutDown), but SolidityNode called Manager.pushVerifiedBlock -> Manager.pushBlock — a path that never passes through TronNetDelegate. The shutdown signal was therefore silently ignored.
  2. No graceful resource cleanup. Thread pools and gRPC connections were abandoned on JVM exit rather than being released through Spring's @PreDestroy lifecycle.

Changes

SolidityNode (framework)

  • Converted from a static utility class to a Spring @Component with @Conditional(SolidityCondition.class) — only registered when --solidity flag is set.
  • Implements ApplicationListener<ContextClosedEvent>: sets flag = false at the very start of context close, before any @PreDestroy methods run on other beans, preventing block pushes into a partially-shut-down system.
  • @PostConstruct init(): initialises named ExecutorService thread pools via ExecutorServiceManager. gRPC client and remoteBlockNum are initialised in run() to keep init lightweight.
  • @PreDestroy shutdown(): sets flag = false (safety net), shuts down both executors via ExecutorServiceManager.shutdownAndAwaitTermination, and closes the gRPC client cleanly.
  • processSolidityBlock() uses blockQueue.poll(timeout) instead of take(), and handles InterruptedException explicitly so thread interruption during executor shutdown is clean.
  • getBlockByNum(): loop changed to while (flag); throws RuntimeException("SolidityNode is closing.") when flag is false — avoids NullPointerException from blockQueue.put(null).
  • getLastSolidityBlockNum(): loop changed to while (flag); returns 0 when flag is false — lets the caller's while (flag) loop exit quietly without a misleading error log.
  • loopProcessBlock() now calls tronNetDelegate.pushVerifiedBlock() instead of dbManager.pushVerifiedBlock(), routing blocks through the shutdown-condition check in processBlock.

TronNetDelegate (framework)

  • Added pushVerifiedBlock(BlockCapsule): sets generatedByMyself = true and delegates to processBlock(block, true), which already contains the conditional-shutdown logic. Includes timing log consistent with the removed Manager method.
  • Shutdown condition in processBlock uses <= instead of == because pushBlock may commit multiple blocks in a single batch write, causing the DB header number to jump past the target block number and never equal it exactly.

Manager (framework)

  • Removed pushVerifiedBlock(). Block push for solidity sync now goes through TronNetDelegate.

FullNode (framework)

  • In solidity mode, trustNodeAddr validation and p2pDisable = true are now enforced before the Spring context is created.
  • After appT.startup(), retrieves the SolidityNode bean from the context and calls run() — reusing the same application lifecycle rather than spawning a second context.

Tests

  • testSolidityGrpcCall — verifies gRPC connectivity through the running RpcApiService.
  • testSolidityNodeHttpApiService — verifies HTTP service start/stop idempotency.
  • testExecutorsInitializedOnStartup@PostConstruct creates both executors before run() is called.
  • testOnApplicationEventSetsFlagFalseContextClosedEvent sets flag = false before any @PreDestroy runs.
  • testGetBlockByNumThrowsWhenClosedRuntimeException (not null) is thrown when flag = false.
  • testGetLastSolidityBlockNumReturnsZeroWhenClosed — returns 0 when flag = false for a quiet loop exit.
  • testSolidityConditionMatchesWhenSolidityFlagSetSolidityCondition.matches() returns true with --solidity.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 16, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Removes Manager.pushVerifiedBlock, adds TronNetDelegate.pushVerifiedBlock, tightens shutdown gating, migrates SolidityNode into a Spring-managed component with lifecycle hooks and runtime run() logic that uses tronNetDelegate.pushVerifiedBlock(...), and adapts FullNode startup and tests accordingly.

Changes

Cohort / File(s) Summary
Block Verification API Migration
framework/src/main/java/org/tron/core/db/Manager.java, framework/src/main/java/org/tron/core/net/TronNetDelegate.java
Deleted Manager.pushVerifiedBlock(BlockCapsule); added TronNetDelegate.pushVerifiedBlock(BlockCapsule) which sets generatedByMyself=true, invokes processBlock(..., isSync=true), and logs timing. Updated processBlock shutdown comparison from == to <=.
SolidityNode Spring Integration
framework/src/main/java/org/tron/program/SolidityNode.java
Refactored into a Spring @Component with @Conditional; added @PostConstruct init() and @PreDestroy shutdown(); removed manual constructor/start/shutdown; added run() and ApplicationListener<ContextClosedEvent> logic; switched from blocking take() to timed poll(), improved interrupt handling, changed block push path to tronNetDelegate.pushVerifiedBlock(...), and make getBlockByNum throw on shutdown.
FullNode Startup Control Flow
framework/src/main/java/org/tron/program/FullNode.java
Removed early exit for Solidity mode; validate trustNodeAddr for Solidity (throws TronError with SOLID_NODE_INIT if missing), set parameter.setP2pDisable(true) for Solidity, and start the Spring context then invoke the SolidityNode bean run().
Tests Updated / Replaced
framework/src/test/java/org/tron/core/db/ManagerTest.java, framework/src/test/java/org/tron/program/SolidityNodeTest.java
Removed usage of Manager.pushVerifiedBlock from ManagerTest.transactionTest; replaced previous SolidityNode startup/shutdown tests with bean-based tests verifying lifecycle, executor creation, onApplicationEvent behavior, getBlockByNum throws at closing, getLastSolidityBlockNum() returns 0L, and SolidityCondition.matches(...) behavior.

Sequence Diagram(s)

sequenceDiagram
    participant SolidityNode
    participant DBGrpcClient
    participant TronNetDelegate
    participant Manager
    participant BlockchainDB

    SolidityNode->>DBGrpcClient: fetch BlockCapsule
    DBGrpcClient-->>SolidityNode: return BlockCapsule
    SolidityNode->>TronNetDelegate: pushVerifiedBlock(block)
    note right of TronNetDelegate: set generatedByMyself = true\ncall processBlock(..., isSync = true)
    TronNetDelegate->>Manager: processBlock / pushBlock(...)
    Manager->>BlockchainDB: persist block / update state
    BlockchainDB-->>Manager: ack
    Manager-->>TronNetDelegate: result
    TronNetDelegate-->>SolidityNode: result / log
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 I hopped from threads to beans today,
Spring woke the node where statics lay.
Verified blocks now take the delegate ride,
Managers step back while logs beam wide.
I nibble bytes and hum a graceful stride.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 32.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat(db): support solidity conditional shutdown' accurately reflects the main objective of refactoring SolidityNode to enable conditional shutdown, which is the primary change across all modified files.
Linked Issues check ✅ Passed All coding requirements from issue #6610 are met: SolidityNode routes blocks through TronNetDelegate to trigger conditional shutdown checks, p2pDisable is forced true, core block processing is preserved, and lifecycle/shutdown behavior is implemented.
Out of Scope Changes check ✅ Passed All changes are scoped to SolidityNode conditional shutdown: Manager.pushVerifiedBlock removal and TronNetDelegate.pushVerifiedBlock addition facilitate routing blocks through shutdown-aware component; FullNode changes enforce trustNodeAddr and trigger SolidityNode.run().
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/solidity_shutdown

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 6 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="framework/src/main/java/org/tron/program/SolidityNode.java">

<violation number="1" location="framework/src/main/java/org/tron/program/SolidityNode.java:69">
P1: Shutdown can hang because stop flag is not honored inside the inner retry loops (`getBlockByNum` / `getLastSolidityBlockNum`).</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread framework/src/main/java/org/tron/program/SolidityNode.java Outdated
@317787106 317787106 force-pushed the feature/solidity_shutdown branch from 3fc0053 to 0e5a48f Compare April 23, 2026 10:04
@317787106 317787106 changed the base branch from release_v4.8.1 to develop April 23, 2026 10:05
@317787106 317787106 changed the title feat(db): support solidity shutdown feat(db): support solidity conditional shutdown Apr 23, 2026
resolveCompatibilityIssueIfUsingFullNodeDatabase();
ID.set(chainBaseManager.getDynamicPropertiesStore().getLatestSolidifiedBlockNum());
databaseGrpcClient = new DatabaseGrpcClient(CommonParameter.getInstance().getTrustNodeAddr());
remoteBlockNum.set(getLastSolidityBlockNum());
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MUST] Move blocking remote RPC out of @PostConstruct

init() is executed synchronously during ApplicationContext.refresh(). It calls new DatabaseGrpcClient(trustNodeAddr) followed by remoteBlockNum.set(getLastSolidityBlockNum()), and getLastSolidityBlockNum() now runs a while (flag) retry loop that can only exit when flag becomes false (i.e. via @PreDestroy). If the trust node is unreachable at startup, the loop never returns, context.refresh() never completes, and context.registerShutdownHook() in FullNode.main is never registered — the process becomes unresponsive to SIGTERM. This is a regression vs. the old SolidityNode.start() which invoked this call after appT.startup(), when the shutdown hook was already wired.

Suggestion: Move databaseGrpcClient creation and remoteBlockNum initialization out of @PostConstruct into run() (or first iteration of getBlock). Keep @PostConstruct limited to local field initialization.

public void pushVerifiedBlock(BlockCapsule block) throws P2pException {
block.generatedByMyself = true;
long start = System.currentTimeMillis();
processBlock(block, true);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MUST] hitDown path leaves callers writing a fake success

When latestSolidityNumShutDown is hit, processBlock flips hitDown=true, unparks the hit-thread, and returns normally. The caller SolidityNode.loopProcessBlock treats that as success, then executes saveLatestSolidifiedBlockNum(blockNum) and logs "Success to process block", while the block was never actually pushed into BlockStore. Depending on the race between the hit-thread's System.exit(0) and the current thread's save, LATEST_SOLIDIFIED_BLOCK_NUM may be advanced to a block number that does not exist in the store — producing an inconsistent DB state and misleading logs.

Suggestion: Throw a dedicated sentinel (e.g. P2pException with a new TypeEnum, or a HitDownException) from processBlock when entering the hitDown branch, and let loopProcessBlock break out without calling saveLatestSolidifiedBlockNum. Alternatively, let the caller check tronNetDelegate.isHitDown() before writing state.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good suggestion, fix it.

new Thread(this::getBlock).start();
new Thread(this::processBlock).start();
getBlockExecutor.submit(this::getBlock);
processBlockExecutor.submit(this::processBlock);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[SHOULD] Use ExecutorServiceManager.submit for TronError propagation

getBlockExecutor.submit(this::getBlock) and processBlockExecutor.submit(this::processBlock) use the raw ExecutorService.submit. Any uncaught Throwable (OOM, AssertionError, unexpected RuntimeException) is captured inside the FutureTask and swallowed — the solidity sync threads die silently, with no log and no JVM exit. The project-wide convention is ExecutorServiceManager.submit(es, task) (see DposTask.java:78, TransactionsMsgHandler.java:90/120, Manager.java:562/567/573), which wraps the task in a try/catch that routes TronError through ExitManager::logAndExit.

Suggestion: Replace both submits with ExecutorServiceManager.submit(getBlockExecutor, this::getBlock) and the equivalent for processBlockExecutor.

sleep(exceptionSleepTime);
}
}
throw new RuntimeException("SolitityNode is closing.");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[SHOULD] Fix typo and avoid raw RuntimeException for shutdown path

Two issues on the shutdown exit of getBlockByNum (line 157) and getLastSolidityBlockNum (line 174):

  1. The string "SolitityNode is closing." has a typo — Solidity is misspelled as Solitity. This text surfaces in logger.error(e.getMessage()) downstream and will hamper future log grepping.
  2. Using a bare RuntimeException to signal graceful shutdown makes it indistinguishable from a real error in callers that catch Exception — the normal shutdown ends up being logged at ERROR level.

Suggestion: Correct the spelling, and either define a private sentinel exception (e.g. ShuttingDownException extends RuntimeException) or change the method to return null / return -1 and have callers check + break explicitly.

sleep(exceptionSleepTime);
}
}
throw new RuntimeException("SolitityNode is closing.");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[SHOULD] Re-interrupt after catching InterruptedException in sleep()

(Anchored here because sleep() at line 177-183 is outside the diff hunk.)

sleep(long) catches Exception and only logs e.getMessage(), dropping the interrupt flag when InterruptedException is the cause. During @PreDestroy, ExecutorServiceManager.shutdownAndAwaitTermination relies on shutdownNow() interrupting worker threads to end them quickly; by swallowing the interrupt bit, the worker may continue iterating while (flag) for an extra cycle and slow shutdown.

Suggestion: Catch InterruptedException specifically and call Thread.currentThread().interrupt(); before/after the log, and keep a separate catch (Exception e) for other cases if needed.

Args.getInstance().setTrustNodeAddr(trustNodeAddr);
}

@Test
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[SHOULD] Restore coverage lost with testSolidityArgs and add tests for new surfaces

This PR deletes testSolidityArgs (which asserted TronError(SOLID_NODE_INIT) when trustNodeAddr is missing), deletes a pushVerifiedBlock assertion in ManagerTest.transactionTest, and introduces new surfaces without any test:

  • TronNetDelegate.pushVerifiedBlock(BlockCapsule) (new method, zero tests)
  • SolidityNode lifecycle (@PostConstruct / @PreDestroy / run()) (zero integration tests)
  • The hitDown shutdown path end-to-end

The PR main behavioral promise — "shutdown signal is now reachable from solidity mode" — has no automated coverage; any future change to processBlock hitDown branch or the solidity lifecycle will regress without CI catching it.

Suggestion: Add at minimum (1) a happy-path and a hitDown test for TronNetDelegate.pushVerifiedBlock verifying that LATEST_SOLIDIFIED_BLOCK_NUM is NOT advanced on hitDown; (2) an integration test for SolidityNode lifecycle; (3) a test at FullNode level for the missing-trust-node case that replaces the deleted testSolidityArgs.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (3)
framework/src/main/java/org/tron/program/SolidityNode.java (2)

177-183: ⚠️ Potential issue | 🟡 Minor

sleep swallows InterruptedException and drops the interrupt flag.

shutdown() relies on ExecutorServiceManager.shutdownAndAwaitTerminationshutdownNow() interrupting workers. Catching Exception and only logging e.getMessage() here clears the interrupt status, so getBlock / processSolidityBlock may iterate one more while (flag) cycle and even re-enter blockQueue.put(...) / blockQueue.take() before noticing flag = false, slowing teardown.

🛡️ Suggested change
   public void sleep(long time) {
     try {
       Thread.sleep(time);
-    } catch (Exception e1) {
-      logger.error(e1.getMessage());
+    } catch (InterruptedException e) {
+      Thread.currentThread().interrupt();
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@framework/src/main/java/org/tron/program/SolidityNode.java` around lines 177
- 183, The sleep method in SolidityNode currently catches Exception and swallows
InterruptedException, clearing the thread's interrupt status; change the catch
to specifically catch InterruptedException, call
Thread.currentThread().interrupt() to restore the interrupt flag, optionally log
the interruption with the exception, and return immediately so workers in
getBlock/processSolidityBlock and blocking calls (blockQueue.put/take) can
observe the interruption during
ExecutorServiceManager.shutdownAndAwaitTermination → shutdownNow() triggered
from shutdown().

138-158: ⚠️ Potential issue | 🟡 Minor

Avoid signaling normal shutdown via bare RuntimeException.

Typo "Solitity" is now fixed, 👍. The remaining concern stands: callers (getBlock, loopProcessBlock, processSolidityBlock) all catch (Exception e) and logger.error(e.getMessage()), so a clean shutdown surfaces as ERROR: SolidityNode is closing. and is indistinguishable from a real failure. In loopProcessBlock the rethrow at line 133 also propagates up out of the try block without being suppressed during shutdown.

Either define a private sentinel type (e.g. class ShuttingDownException extends RuntimeException) and special-case it (or skip logging), or change these helpers to return Optional<Block> / OptionalLong and have callers check flag and break.

Also applies to: 160-175

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@framework/src/main/java/org/tron/program/SolidityNode.java` around lines 138
- 158, Replace the bare RuntimeException used to signal normal shutdown with a
sentinel exception and handle it specially: add a private static class
ShuttingDownException extends RuntimeException (no stack-trace needed) and throw
new ShuttingDownException() from getBlockByNum (and the similar shutdown sites
around 160-175) instead of new RuntimeException("SolidityNode is closing.");
then update callers such as loopProcessBlock, processSolidityBlock and getBlock
to catch ShuttingDownException separately (or rethrow it) so they do not log it
as an error — treat it as a normal shutdown signal (skip logger.error and
break/return) while other Exceptions continue to be logged and handled as
before.
framework/src/main/java/org/tron/core/net/TronNetDelegate.java (1)

236-245: ⚠️ Potential issue | 🔴 Critical

pushVerifiedBlock succeeds silently when processBlock enters the hitDown branch.

When processBlock hits the shutdown condition (lines 248-258), it sets hitDown = true, unparks hitThread, and returns normally — no exception. pushVerifiedBlock then logs "Push block cost: ..." and returns. The caller in SolidityNode.loopProcessBlock (lines 121-136) treats this as a successful push and proceeds to saveLatestSolidifiedBlockNum(blockNum), advancing LATEST_SOLIDIFIED_BLOCK_NUM to a block number that was never persisted to BlockStore. Depending on the race between hitThread's System.exit(0) and the caller's save, the DB ends up in an inconsistent state with misleading INFO logs.

Either propagate a sentinel (e.g. dedicated P2pException TypeEnum or a HitDownException) from processBlock so the caller can break without saving, or check isHitDown() here before logging success and have callers honor it.

🛡️ One possible shape
   public void pushVerifiedBlock(BlockCapsule block) throws P2pException {
     block.generatedByMyself = true;
     long start = System.currentTimeMillis();
     processBlock(block, true);
+    if (hitDown) {
+      throw new P2pException(TypeEnum.SHUTDOWN, "shutdown condition reached");
+    }
     logger.info("Push block cost: {} ms, blockNum: {}, blockHash: {}, trx count: {}.",
         System.currentTimeMillis() - start,
         block.getNum(),
         block.getBlockId(),
         block.getTransactions().size());
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@framework/src/main/java/org/tron/core/net/TronNetDelegate.java` around lines
236 - 245, pushVerifiedBlock currently treats processBlock return as success
even when processBlock sets hitDown and unparks hitThread; update
pushVerifiedBlock to detect shutdown and avoid logging/success behavior or
change processBlock to propagate a sentinel exception: either (A) have
processBlock throw a dedicated P2pException subtype (e.g., HitDownException /
TypeEnum.HIT_DOWN) when it enters the hitDown branch and let pushVerifiedBlock
rethrow it so callers such as SolidityNode.loopProcessBlock can skip
saveLatestSolidifiedBlockNum, or (B) after calling processBlock(block, true)
check process.isHitDown() (or the hitDown flag exposed) and if true skip the
success log and return/throw so callers do not persist the unprocessed block;
update SolidityNode.loopProcessBlock to honor the exception/flag and avoid
advancing LATEST_SOLIDIFIED_BLOCK_NUM when hitDown is observed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@framework/src/main/java/org/tron/core/net/TronNetDelegate.java`:
- Around line 236-245: pushVerifiedBlock currently treats processBlock return as
success even when processBlock sets hitDown and unparks hitThread; update
pushVerifiedBlock to detect shutdown and avoid logging/success behavior or
change processBlock to propagate a sentinel exception: either (A) have
processBlock throw a dedicated P2pException subtype (e.g., HitDownException /
TypeEnum.HIT_DOWN) when it enters the hitDown branch and let pushVerifiedBlock
rethrow it so callers such as SolidityNode.loopProcessBlock can skip
saveLatestSolidifiedBlockNum, or (B) after calling processBlock(block, true)
check process.isHitDown() (or the hitDown flag exposed) and if true skip the
success log and return/throw so callers do not persist the unprocessed block;
update SolidityNode.loopProcessBlock to honor the exception/flag and avoid
advancing LATEST_SOLIDIFIED_BLOCK_NUM when hitDown is observed.

In `@framework/src/main/java/org/tron/program/SolidityNode.java`:
- Around line 177-183: The sleep method in SolidityNode currently catches
Exception and swallows InterruptedException, clearing the thread's interrupt
status; change the catch to specifically catch InterruptedException, call
Thread.currentThread().interrupt() to restore the interrupt flag, optionally log
the interruption with the exception, and return immediately so workers in
getBlock/processSolidityBlock and blocking calls (blockQueue.put/take) can
observe the interruption during
ExecutorServiceManager.shutdownAndAwaitTermination → shutdownNow() triggered
from shutdown().
- Around line 138-158: Replace the bare RuntimeException used to signal normal
shutdown with a sentinel exception and handle it specially: add a private static
class ShuttingDownException extends RuntimeException (no stack-trace needed) and
throw new ShuttingDownException() from getBlockByNum (and the similar shutdown
sites around 160-175) instead of new RuntimeException("SolidityNode is
closing."); then update callers such as loopProcessBlock, processSolidityBlock
and getBlock to catch ShuttingDownException separately (or rethrow it) so they
do not log it as an error — treat it as a normal shutdown signal (skip
logger.error and break/return) while other Exceptions continue to be logged and
handled as before.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6ed13c09-5186-4dfa-a41f-48a7572e20f7

📥 Commits

Reviewing files that changed from the base of the PR and between f0a8f0f and 50c76de.

📒 Files selected for processing (6)
  • framework/src/main/java/org/tron/core/db/Manager.java
  • framework/src/main/java/org/tron/core/net/TronNetDelegate.java
  • framework/src/main/java/org/tron/program/FullNode.java
  • framework/src/main/java/org/tron/program/SolidityNode.java
  • framework/src/test/java/org/tron/core/db/ManagerTest.java
  • framework/src/test/java/org/tron/program/SolidityNodeTest.java
💤 Files with no reviewable changes (3)
  • framework/src/test/java/org/tron/core/db/ManagerTest.java
  • framework/src/main/java/org/tron/core/db/Manager.java
  • framework/src/test/java/org/tron/program/SolidityNodeTest.java

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (3)
framework/src/main/java/org/tron/program/SolidityNode.java (3)

166-183: ⚠️ Potential issue | 🟡 Minor

Don't use a plain RuntimeException for normal shutdown.

This is an expected stop signal, but the callers catch it as a generic failure, log at ERROR, and sleep once more before exiting. A dedicated shutdown exception, or a sentinel return that lets the loops break cleanly, would keep shutdown quiet and deterministic.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@framework/src/main/java/org/tron/program/SolidityNode.java` around lines 166
- 183, Replace the thrown generic RuntimeException in getLastSolidityBlockNum
with a clear shutdown signal: either create and throw a dedicated unchecked
exception (e.g., NodeShutdownException or SolidityNodeShutdownException) so
callers can catch that specific type instead of treating it as an error, or
change the method to return a sentinel value (e.g., -1) and update callers to
check for that sentinel and break their loops cleanly; update references to flag
and any callers of getLastSolidityBlockNum to handle the new exception or
sentinel consistently so shutdown is deterministic and not logged as an error.

81-82: ⚠️ Potential issue | 🟠 Major

Use ExecutorServiceManager.submit(...) for these workers.

ExecutorService.submit hides uncaught Throwable inside the returned Future, so either sync loop can die silently. Route both tasks through the manager wrapper instead.

Proposed fix
-      getBlockExecutor.submit(this::getBlock);
-      processBlockExecutor.submit(this::processSolidityBlock);
+      ExecutorServiceManager.submit(getBlockExecutor, this::getBlock);
+      ExecutorServiceManager.submit(processBlockExecutor, this::processSolidityBlock);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@framework/src/main/java/org/tron/program/SolidityNode.java` around lines 81 -
82, Replace direct calls to getBlockExecutor.submit(this::getBlock) and
processBlockExecutor.submit(this::processSolidityBlock) with the
ExecutorServiceManager.submit(...) wrapper so uncaught Throwables are not
swallowed by plain ExecutorService futures; locate the invocations around
getBlockExecutor and processBlockExecutor in SolidityNode and call
ExecutorServiceManager.submit(getBlockExecutor, this::getBlock) and
ExecutorServiceManager.submit(processBlockExecutor, this::processSolidityBlock)
(or the project’s equivalent static/instance submit method) to ensure exceptions
in getBlock and processSolidityBlock are propagated/handled by the manager.

186-191: ⚠️ Potential issue | 🟡 Minor

Preserve interrupts in sleep().

Catching Exception here clears InterruptedException, which works against the shutdown path. Catch InterruptedException explicitly, restore the interrupt flag, and return.

Proposed fix
   public void sleep(long time) {
     try {
       Thread.sleep(time);
-    } catch (Exception e1) {
-      logger.error(e1.getMessage());
+    } catch (InterruptedException e) {
+      Thread.currentThread().interrupt();
+      logger.info("sleep interrupted.");
+    } catch (Exception e) {
+      logger.error(e.getMessage());
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@framework/src/main/java/org/tron/program/SolidityNode.java` around lines 186
- 191, The sleep(long time) method currently catches Exception which swallows
InterruptedException; change it to catch InterruptedException explicitly in
SolidityNode.sleep, call Thread.currentThread().interrupt() to restore the
interrupt flag, and return immediately instead of continuing, and remove the
broad Exception catch so other runtime exceptions propagate; keep the logger
usage for non-interrupt cases if needed (e.g., log the interruption via
logger.warn or logger.debug).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@framework/src/main/java/org/tron/program/SolidityNode.java`:
- Around line 166-183: Replace the thrown generic RuntimeException in
getLastSolidityBlockNum with a clear shutdown signal: either create and throw a
dedicated unchecked exception (e.g., NodeShutdownException or
SolidityNodeShutdownException) so callers can catch that specific type instead
of treating it as an error, or change the method to return a sentinel value
(e.g., -1) and update callers to check for that sentinel and break their loops
cleanly; update references to flag and any callers of getLastSolidityBlockNum to
handle the new exception or sentinel consistently so shutdown is deterministic
and not logged as an error.
- Around line 81-82: Replace direct calls to
getBlockExecutor.submit(this::getBlock) and
processBlockExecutor.submit(this::processSolidityBlock) with the
ExecutorServiceManager.submit(...) wrapper so uncaught Throwables are not
swallowed by plain ExecutorService futures; locate the invocations around
getBlockExecutor and processBlockExecutor in SolidityNode and call
ExecutorServiceManager.submit(getBlockExecutor, this::getBlock) and
ExecutorServiceManager.submit(processBlockExecutor, this::processSolidityBlock)
(or the project’s equivalent static/instance submit method) to ensure exceptions
in getBlock and processSolidityBlock are propagated/handled by the manager.
- Around line 186-191: The sleep(long time) method currently catches Exception
which swallows InterruptedException; change it to catch InterruptedException
explicitly in SolidityNode.sleep, call Thread.currentThread().interrupt() to
restore the interrupt flag, and return immediately instead of continuing, and
remove the broad Exception catch so other runtime exceptions propagate; keep the
logger usage for non-interrupt cases if needed (e.g., log the interruption via
logger.warn or logger.debug).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a6d5da9e-9a68-406f-be7d-e7ad20310629

📥 Commits

Reviewing files that changed from the base of the PR and between 50c76de and 698950d.

📒 Files selected for processing (3)
  • framework/src/main/java/org/tron/core/db/Manager.java
  • framework/src/main/java/org/tron/program/SolidityNode.java
  • framework/src/test/java/org/tron/core/db/ManagerTest.java
💤 Files with no reviewable changes (2)
  • framework/src/test/java/org/tron/core/db/ManagerTest.java
  • framework/src/main/java/org/tron/core/db/Manager.java

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
framework/src/main/java/org/tron/core/net/TronNetDelegate.java (1)

236-245: ⚠️ Potential issue | 🔴 Critical

Still reports a successful push after shutdown short-circuit

At Line 239, processBlock(...) can return early when hitDown is reached (Lines 248-262), but this method still proceeds to the success-style log at Line 240. That keeps the “processed successfully” ambiguity alive for callers and logs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@framework/src/main/java/org/tron/core/net/TronNetDelegate.java` around lines
236 - 245, The pushVerifiedBlock method currently logs a successful push
unconditionally even though processBlock(block, true) can short-circuit when
hitDown is reached; change processBlock to return a boolean (true if fully
processed, false if short-circuited by hitDown) and update pushVerifiedBlock to
capture that return value and only emit the success-style logger.info (and keep
block.generatedByMyself as appropriate) when the result is true; if false, emit
a distinct shutdown/short-circuit log and avoid reporting the block as
successfully processed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@framework/src/main/java/org/tron/core/net/TronNetDelegate.java`:
- Around line 236-245: The pushVerifiedBlock method currently logs a successful
push unconditionally even though processBlock(block, true) can short-circuit
when hitDown is reached; change processBlock to return a boolean (true if fully
processed, false if short-circuited by hitDown) and update pushVerifiedBlock to
capture that return value and only emit the success-style logger.info (and keep
block.generatedByMyself as appropriate) when the result is true; if false, emit
a distinct shutdown/short-circuit log and avoid reporting the block as
successfully processed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ed545f03-5841-4cda-94ff-6d8d9146dee3

📥 Commits

Reviewing files that changed from the base of the PR and between 698950d and 56c7224.

📒 Files selected for processing (1)
  • framework/src/main/java/org/tron/core/net/TronNetDelegate.java

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (3)
framework/src/main/java/org/tron/program/SolidityNode.java (3)

193-198: ⚠️ Potential issue | 🟠 Major

Preserve thread interrupt status in sleep(long).

Line 196 catches broad Exception and drops interrupts. That makes shutdown via executor interruption less reliable/slower.

Suggested patch
   public void sleep(long time) {
     try {
       Thread.sleep(time);
-    } catch (Exception e1) {
-      logger.error(e1.getMessage());
+    } catch (InterruptedException e) {
+      Thread.currentThread().interrupt();
+      logger.info("Sleep interrupted.");
+    } catch (Exception e1) {
+      logger.error("Sleep failed.", e1);
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@framework/src/main/java/org/tron/program/SolidityNode.java` around lines 193
- 198, The sleep(long) method in class SolidityNode currently catches a broad
Exception and swallows interrupts; modify sleep(long) to catch
InterruptedException separately, call Thread.currentThread().interrupt() to
preserve the thread's interrupt status and then return or rethrow as
appropriate, while keeping the existing catch-all for other Exceptions to handle
non-interrupt errors; update references inside SolidityNode.sleep(long) so
interrupted shutdown via executor interruption is honored.

88-89: ⚠️ Potential issue | 🟠 Major

Use ExecutorServiceManager.submit(...) to avoid silent worker death.

Line 88 and Line 89 still use raw ExecutorService.submit(...); uncaught throwables can get trapped in FutureTask and kill the worker without process-level handling.

Suggested patch
-      getBlockExecutor.submit(this::getBlock);
-      processBlockExecutor.submit(this::processSolidityBlock);
+      ExecutorServiceManager.submit(getBlockExecutor, this::getBlock);
+      ExecutorServiceManager.submit(processBlockExecutor, this::processSolidityBlock);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@framework/src/main/java/org/tron/program/SolidityNode.java` around lines 88 -
89, The code in SolidityNode is submitting runnables via
ExecutorService.submit(...) which can swallow exceptions; replace those calls
with ExecutorServiceManager.submit(...) so submitted tasks get wrapped/handled
by the central manager (use the same task/lambda you currently pass to
ExecutorService.submit and call ExecutorServiceManager.submit(...) instead),
ensuring you update any imports and remove direct ExecutorService.submit(...)
usages in the SolidityNode class so uncaught throwables are propagated/handled
by the manager.

173-174: ⚠️ Potential issue | 🟡 Minor

Avoid using generic RuntimeException for normal shutdown flow.

Line 173 and Line 190 throw generic runtime errors for graceful stop; these propagate into generic catch (Exception) paths and get logged as failures, creating noisy false-error shutdown logs.

Also applies to: 190-190

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@framework/src/main/java/org/tron/program/SolidityNode.java` around lines 173
- 174, Replace the generic RuntimeException used to signal a normal/graceful
shutdown in SolidityNode with a dedicated exception type (e.g.,
GracefulShutdownException or use InterruptedException) so shutdown control flow
is explicit; update the throw sites in SolidityNode (the lines currently
throwing RuntimeException around the graceful stop) to throw that specific
exception and adjust any surrounding catch blocks that currently catch Exception
to handle the new exception separately (or rethrow it) so it isn't logged as an
error in normal shutdown paths.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@framework/src/main/java/org/tron/program/SolidityNode.java`:
- Around line 193-198: The sleep(long) method in class SolidityNode currently
catches a broad Exception and swallows interrupts; modify sleep(long) to catch
InterruptedException separately, call Thread.currentThread().interrupt() to
preserve the thread's interrupt status and then return or rethrow as
appropriate, while keeping the existing catch-all for other Exceptions to handle
non-interrupt errors; update references inside SolidityNode.sleep(long) so
interrupted shutdown via executor interruption is honored.
- Around line 88-89: The code in SolidityNode is submitting runnables via
ExecutorService.submit(...) which can swallow exceptions; replace those calls
with ExecutorServiceManager.submit(...) so submitted tasks get wrapped/handled
by the central manager (use the same task/lambda you currently pass to
ExecutorService.submit and call ExecutorServiceManager.submit(...) instead),
ensuring you update any imports and remove direct ExecutorService.submit(...)
usages in the SolidityNode class so uncaught throwables are propagated/handled
by the manager.
- Around line 173-174: Replace the generic RuntimeException used to signal a
normal/graceful shutdown in SolidityNode with a dedicated exception type (e.g.,
GracefulShutdownException or use InterruptedException) so shutdown control flow
is explicit; update the throw sites in SolidityNode (the lines currently
throwing RuntimeException around the graceful stop) to throw that specific
exception and adjust any surrounding catch blocks that currently catch Exception
to handle the new exception separately (or rethrow it) so it isn't logged as an
error in normal shutdown paths.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e13b5f38-4b4d-444e-8a22-7691889ec815

📥 Commits

Reviewing files that changed from the base of the PR and between 56c7224 and 86be278.

📒 Files selected for processing (2)
  • framework/src/main/java/org/tron/core/net/TronNetDelegate.java
  • framework/src/main/java/org/tron/program/SolidityNode.java

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
framework/src/main/java/org/tron/program/SolidityNode.java (2)

88-89: ⚠️ Potential issue | 🟠 Major

Use ExecutorServiceManager.submit(...) to avoid silent worker death.

At Line 88 and Line 89, raw ExecutorService.submit(...) can hide fatal task failures inside the returned Future, which risks silent sync stoppage. Please route submissions through the project wrapper here as well.

Proposed fix
-      getBlockExecutor.submit(this::getBlock);
-      processBlockExecutor.submit(this::processSolidityBlock);
+      ExecutorServiceManager.submit(getBlockExecutor, this::getBlock);
+      ExecutorServiceManager.submit(processBlockExecutor, this::processSolidityBlock);
#!/bin/bash
# Verify this class uses raw submit and compare with project convention.
rg -n -C2 'getBlockExecutor\.submit|processBlockExecutor\.submit' framework/src/main/java/org/tron/program/SolidityNode.java
rg -n -C1 'ExecutorServiceManager\.submit\(' --type=java
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@framework/src/main/java/org/tron/program/SolidityNode.java` around lines 88 -
89, The code submits tasks with raw ExecutorService.submit using
getBlockExecutor.submit(this::getBlock) and
processBlockExecutor.submit(this::processSolidityBlock); replace these with the
project wrapper by routing submissions through
ExecutorServiceManager.submit(...) so task failures aren't swallowed. Locate the
calls to getBlockExecutor and processBlockExecutor in SolidityNode and change
them to use ExecutorServiceManager.submit(getBlockExecutor, this::getBlock) and
ExecutorServiceManager.submit(processBlockExecutor, this::processSolidityBlock)
(or the wrapper's equivalent overload) so the manager can handle exceptions and
logging.

196-201: ⚠️ Potential issue | 🟠 Major

Preserve interrupt status in sleep() for clean shutdown behavior.

At Line 199, catching generic Exception drops InterruptedException state; this can slow or distort shutdown flow for worker threads.

Proposed fix
   public void sleep(long time) {
     try {
       Thread.sleep(time);
-    } catch (Exception e1) {
-      logger.error(e1.getMessage());
+    } catch (InterruptedException e1) {
+      Thread.currentThread().interrupt();
+      logger.info("Sleep interrupted.");
+    } catch (Exception e1) {
+      logger.error(e1.getMessage(), e1);
     }
   }
#!/bin/bash
# Verify interrupt swallowing in sleep() and its call sites.
rg -n -C3 'void sleep\(long time\)|Thread\.sleep|catch \(Exception e1\)' framework/src/main/java/org/tron/program/SolidityNode.java
rg -n -C2 'sleep\(exceptionSleepTime\)|sleep\(BLOCK_PRODUCED_INTERVAL\)' framework/src/main/java/org/tron/program/SolidityNode.java
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@framework/src/main/java/org/tron/program/SolidityNode.java` around lines 196
- 201, The sleep(long time) method currently catches Exception and swallows
InterruptedException; change the catch to specifically handle
InterruptedException by restoring the thread's interrupt status
(Thread.currentThread().interrupt()) and logging appropriately, and handle other
unexpected Throwables separately if needed; locate the sleep(long time) method
(uses Thread.sleep(time) and logger.error) and replace the generic catch with a
catch (InterruptedException ie) { Thread.currentThread().interrupt();
logger.error("Interrupted while sleeping", ie); } plus an optional catch
(Exception e) { logger.error("Unexpected error in sleep", e); } to preserve
shutdown behavior.
🧹 Nitpick comments (1)
framework/src/main/java/org/tron/program/SolidityNode.java (1)

83-97: Consider making run() idempotent.

If run() is called more than once, it can enqueue duplicate long-lived workers on the same executors. A simple started-flag guard would make lifecycle safer.

Possible refactor
+  private final java.util.concurrent.atomic.AtomicBoolean started = new java.util.concurrent.atomic.AtomicBoolean(false);
+
   public void run() {
+    if (!started.compareAndSet(false, true)) {
+      logger.warn("SolidityNode.run() called more than once; ignoring.");
+      return;
+    }
     try {
       databaseGrpcClient = new DatabaseGrpcClient(CommonParameter.getInstance().getTrustNodeAddr());
       remoteBlockNum.set(getLastSolidityBlockNum());
@@
     } catch (Exception e) {
+      started.set(false);
       logger.error("Failed to start solid node, address: {}.",
           CommonParameter.getInstance().getTrustNodeAddr());
       throw new TronError(e, TronError.ErrCode.SOLID_NODE_INIT);
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@framework/src/main/java/org/tron/program/SolidityNode.java` around lines 83 -
97, run() can be invoked multiple times and will submit duplicate long-lived
tasks to getBlockExecutor and processBlockExecutor; make it idempotent by adding
a private AtomicBoolean (e.g., started) checked at the top of run() and set
atomically (compareAndSet(false, true)) so the body only executes once, and
return immediately if already started; ensure the existing initialization of
databaseGrpcClient, remoteBlockNum.set(...), and the two submit(...) calls
(getBlockExecutor.submit(this::getBlock) and
processBlockExecutor.submit(this::processSolidityBlock)) are guarded by this
flag so duplicate workers are not enqueued.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@framework/src/main/java/org/tron/program/SolidityNode.java`:
- Around line 88-89: The code submits tasks with raw ExecutorService.submit
using getBlockExecutor.submit(this::getBlock) and
processBlockExecutor.submit(this::processSolidityBlock); replace these with the
project wrapper by routing submissions through
ExecutorServiceManager.submit(...) so task failures aren't swallowed. Locate the
calls to getBlockExecutor and processBlockExecutor in SolidityNode and change
them to use ExecutorServiceManager.submit(getBlockExecutor, this::getBlock) and
ExecutorServiceManager.submit(processBlockExecutor, this::processSolidityBlock)
(or the wrapper's equivalent overload) so the manager can handle exceptions and
logging.
- Around line 196-201: The sleep(long time) method currently catches Exception
and swallows InterruptedException; change the catch to specifically handle
InterruptedException by restoring the thread's interrupt status
(Thread.currentThread().interrupt()) and logging appropriately, and handle other
unexpected Throwables separately if needed; locate the sleep(long time) method
(uses Thread.sleep(time) and logger.error) and replace the generic catch with a
catch (InterruptedException ie) { Thread.currentThread().interrupt();
logger.error("Interrupted while sleeping", ie); } plus an optional catch
(Exception e) { logger.error("Unexpected error in sleep", e); } to preserve
shutdown behavior.

---

Nitpick comments:
In `@framework/src/main/java/org/tron/program/SolidityNode.java`:
- Around line 83-97: run() can be invoked multiple times and will submit
duplicate long-lived tasks to getBlockExecutor and processBlockExecutor; make it
idempotent by adding a private AtomicBoolean (e.g., started) checked at the top
of run() and set atomically (compareAndSet(false, true)) so the body only
executes once, and return immediately if already started; ensure the existing
initialization of databaseGrpcClient, remoteBlockNum.set(...), and the two
submit(...) calls (getBlockExecutor.submit(this::getBlock) and
processBlockExecutor.submit(this::processSolidityBlock)) are guarded by this
flag so duplicate workers are not enqueued.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6c6ec84a-8de4-477a-b0ad-d73d007b57a2

📥 Commits

Reviewing files that changed from the base of the PR and between 86be278 and 5341ef6.

📒 Files selected for processing (2)
  • framework/src/main/java/org/tron/program/SolidityNode.java
  • framework/src/test/java/org/tron/program/SolidityNodeTest.java

@cubic-dev-ai
Copy link
Copy Markdown

cubic-dev-ai Bot commented Apr 29, 2026

You're iterating quickly on this pull request. To help protect your rate limits, cubic has paused automatic reviews on new pushes for now—when you're ready for another review, comment @cubic-dev-ai review.

…shutdown

- Convert SolidityNode from static utility to @conditional @component so it
  only registers when --solidity is passed
- Implement ApplicationListener<ContextClosedEvent> to set flag=false before
  any @PreDestroy method fires, stopping worker threads promptly
- Add pushVerifiedBlock() to TronNetDelegate to route solidity sync blocks
  through the conditional-shutdown check (<=, not ==, because a single
  batch write can advance the DB header past the target number)
- Guard saveLatestSolidifiedBlockNum in loopProcessBlock: skip the write when
  hitDown is true, preventing a phantom block-num from persisting when the
  block was never actually pushed to BlockStore
- Add unit tests covering lifecycle hooks, all retry paths, interrupt handling,
  and the hitDown save-guard; SolidityNode line coverage 28% -> 81%

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@317787106 317787106 force-pushed the feature/solidity_shutdown branch from 3c5c2d2 to 7de0f2a Compare April 30, 2026 03:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] SolidityNode supports conditional shutdown

2 participants