Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v5

- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@v6

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/integration-test-single-node.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ jobs:
- name: Checkout java-tron
uses: actions/checkout@v5

- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@v6

- name: Set up JDK 8
uses: actions/setup-java@v5
with:
Expand Down
15 changes: 15 additions & 0 deletions .github/workflows/pr-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ jobs:
steps:
- uses: actions/checkout@v5

- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@v6

- name: Set up JDK ${{ matrix.java }}
uses: actions/setup-java@v5
with:
Expand Down Expand Up @@ -72,6 +75,9 @@ jobs:
steps:
- uses: actions/checkout@v5

- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@v6

- name: Set up JDK 17
uses: actions/setup-java@v5
with:
Expand Down Expand Up @@ -126,6 +132,9 @@ jobs:
- name: Checkout code
uses: actions/checkout@v5

- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@v6

- name: Check Java version
run: java -version

Expand Down Expand Up @@ -183,6 +192,9 @@ jobs:
- name: Checkout code
uses: actions/checkout@v5

- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@v6

- name: Check Java version
run: java -version

Expand Down Expand Up @@ -249,6 +261,9 @@ jobs:
with:
ref: ${{ github.event.pull_request.base.sha }}

- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@v6

- name: Cache Gradle packages
uses: actions/cache@v5
with:
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/pr-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ jobs:
steps:
- uses: actions/checkout@v5

- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@v6

- name: Set up Python
uses: actions/setup-python@v5
with:
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/release-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ jobs:
with:
ref: ${{ github.sha }}

- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@v6

- name: Cache Gradle packages
uses: actions/cache@v5
with:
Expand Down Expand Up @@ -121,6 +124,9 @@ jobs:
with:
ref: ${{ github.sha }}

- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@v6

- name: Set up Temurin 17
uses: actions/setup-java@v5
with:
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,9 @@ Fill in the private key of your SR account into the `localwitness` list in the c
<your_private_key>
]
```

> **Note**: Plain private keys in `localwitness` (and the `-p/--private-key` CLI flag) are deprecated and insecure — they are readable by anyone with access to the config file, process list, or shell history. Use the encrypted [`localwitnesskeystore`](https://tronprotocol.github.io/documentation-en/using_javatron/toolkit/) instead.

Check [Starting a Block Production Node](https://tronprotocol.github.io/documentation-en/using_javatron/installing_javatron/#starting-a-block-production-node) for more details.
You could also test the process by connecting to a testnet or setting up a private network.

Expand Down
4 changes: 1 addition & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ subprojects {
buildscript {
repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
maven {
url "https://plugins.gradle.org/m2/"
}
Expand All @@ -84,10 +83,9 @@ subprojects {
}

repositories {
mavenLocal()
mavenCentral()
maven { url 'https://repo.spring.io/plugins-release' }
maven { url 'https://jitpack.io' }
mavenLocal()
}

dependencies {
Expand Down
35 changes: 33 additions & 2 deletions common/src/main/java/org/tron/core/config/args/NodeConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,9 @@ public static class HttpConfig {
public static class RpcConfig {

public static final int DEFAULT_MAX_CONCURRENT_CALLS_PER_CONNECTION = 100;
// Secure built-in default for gRPC connection lifetime (idle & max age).
// Applied by postProcess() when the configured value is 0 (the default).
public static final long DEFAULT_MAX_CONNECTION_LIFETIME_IN_MILLIS = 60_000L;

private boolean enable = true;
private int port = 50051;
Expand Down Expand Up @@ -373,11 +376,39 @@ private void postProcess() {
rpc.maxConcurrentCallsPerConnection =
RpcConfig.DEFAULT_MAX_CONCURRENT_CALLS_PER_CONNECTION;
}
// node.rpc.maxRstStream and node.rpc.secondsPerWindow only take effect
// together (RpcService applies RST limiting only when both are > 0). A
// half-configured pair would silently disable RST_STREAM flood
// protection — warn loudly so the misconfiguration is visible.
if (rpc.maxRstStream < 0 || rpc.secondsPerWindow < 0) {
throw new TronError("node.rpc.maxRstStream and node.rpc.secondsPerWindow "
+ "must be non-negative, got: maxRstStream=" + rpc.maxRstStream
+ ", secondsPerWindow=" + rpc.secondsPerWindow, PARAMETER_INIT);
}
if ((rpc.maxRstStream > 0 && rpc.secondsPerWindow <= 0)
|| (rpc.maxRstStream <= 0 && rpc.secondsPerWindow > 0)) {
logger.warn("node.rpc.maxRstStream and node.rpc.secondsPerWindow only "
+ "take effect together: exactly one of them is set while the other "
+ "is 0, so RST_STREAM flood protection is DISABLED. Set both to "
+ "positive values to enable it, or leave both at 0 (got: "
+ "maxRstStream={}, secondsPerWindow={})",
rpc.maxRstStream, rpc.secondsPerWindow);
}
if (rpc.maxConnectionIdleInMillis < 0 || rpc.maxConnectionAgeInMillis < 0) {
throw new TronError("node.rpc.maxConnectionIdleInMillis and node.rpc.maxConnectionAgeInMillis "
+ "must be non-negative, got: maxConnectionIdleInMillis=" + rpc.maxConnectionIdleInMillis
+ ", maxConnectionAgeInMillis=" + rpc.maxConnectionAgeInMillis, PARAMETER_INIT);
}
// 0 (the default) means "use the secure built-in default" (60s), NOT
// unlimited: the old 0 -> Long.MAX_VALUE conversion silently turned an
// unset or explicitly-zero value into an unbounded connection lifetime.
// Negative values are rejected outright. Operators who want a longer
// lifetime must set an explicit positive value.
if (rpc.maxConnectionIdleInMillis == 0) {
rpc.maxConnectionIdleInMillis = Long.MAX_VALUE;
rpc.maxConnectionIdleInMillis = RpcConfig.DEFAULT_MAX_CONNECTION_LIFETIME_IN_MILLIS;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: When users follow reference.conf or docs/configuration.md, they are told that 0 means no limit, but this conversion makes both RPC limits 60 seconds. Update the configuration comments and documentation to describe the 60-second effective default and document the age setting consistently.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At common/src/main/java/org/tron/core/config/args/NodeConfig.java, line 401:

<comment>When users follow `reference.conf` or `docs/configuration.md`, they are told that `0` means no limit, but this conversion makes both RPC limits 60 seconds. Update the configuration comments and documentation to describe the 60-second effective default and document the age setting consistently.</comment>

<file context>
@@ -373,11 +376,32 @@ private void postProcess() {
+    // Operators who want a longer lifetime must set an explicit positive value.
     if (rpc.maxConnectionIdleInMillis == 0) {
-      rpc.maxConnectionIdleInMillis = Long.MAX_VALUE;
+      rpc.maxConnectionIdleInMillis = RpcConfig.DEFAULT_MAX_CONNECTION_LIFETIME_IN_MILLIS;
     }
     if (rpc.maxConnectionAgeInMillis == 0) {
</file context>

Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
}
if (rpc.maxConnectionAgeInMillis == 0) {
rpc.maxConnectionAgeInMillis = Long.MAX_VALUE;
rpc.maxConnectionAgeInMillis = RpcConfig.DEFAULT_MAX_CONNECTION_LIFETIME_IN_MILLIS;
}

// validateSignThreadNum: 0 = auto-detect
Expand Down
4 changes: 2 additions & 2 deletions common/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -295,10 +295,10 @@ node {
# HTTP/2 flow control window (bytes), default 1MB
flowControlWindow = 1048576

# Connection idle timeout (ms). Connections idle longer than this are gracefully terminated. 0 = no limit
# Connection idle timeout (ms). Connections idle longer than this are gracefully terminated. 0 = secure default (60 s)
maxConnectionIdleInMillis = 0

# Connection max age (ms). 0 = no limit
# Connection max age (ms). 0 = secure default (60 s)
maxConnectionAgeInMillis = 0

# Maximum gRPC message size in bytes (default 4194304, ~4MB).
Expand Down
93 changes: 91 additions & 2 deletions common/src/test/java/org/tron/core/config/args/NodeConfigTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,10 @@ public void testRpcDefaultsFromReference() {
assertEquals(NodeConfig.RpcConfig.DEFAULT_MAX_CONCURRENT_CALLS_PER_CONNECTION,
rpc.getMaxConcurrentCallsPerConnection());
assertEquals(1048576, rpc.getFlowControlWindow());
assertEquals(9223372036854775807L, rpc.getMaxConnectionIdleInMillis());
assertEquals(9223372036854775807L, rpc.getMaxConnectionAgeInMillis());
// reference.conf keeps 0 (the default marker); postProcess converts 0 to
// the secure built-in 60s default, so the effective default is 60s.
assertEquals(60000L, rpc.getMaxConnectionIdleInMillis());
assertEquals(60000L, rpc.getMaxConnectionAgeInMillis());
assertEquals(4194304, rpc.getMaxMessageSize());
assertEquals(8192, rpc.getMaxHeaderListSize());
assertEquals(1, rpc.getMinEffectiveConnection());
Expand Down Expand Up @@ -146,6 +148,93 @@ public void testRpcNegativeConcurrentCallsRejected() {
"node.rpc.maxConcurrentCallsPerConnection must be non-negative, got: -1"));
}

@Test
public void testRpcNegativeIdleAndAgeRejected() {
Config config = withRef(
"node { rpc { maxConnectionIdleInMillis = -1, maxConnectionAgeInMillis = 60000 } }");

TronError exception = assertThrows(TronError.class,
() -> NodeConfig.fromConfig(config));

assertTrue(exception.getMessage().contains(
"node.rpc.maxConnectionIdleInMillis and node.rpc.maxConnectionAgeInMillis "
+ "must be non-negative, got: maxConnectionIdleInMillis=-1"));

Config config2 = withRef(
"node { rpc { maxConnectionIdleInMillis = 60000, maxConnectionAgeInMillis = -5 } }");

exception = assertThrows(TronError.class,
() -> NodeConfig.fromConfig(config2));

assertTrue(exception.getMessage().contains(
"maxConnectionAgeInMillis=-5"));
}

@Test
public void testRpcExplicitZeroIdleAndAgeFallsBackToSecureDefault() {
// Explicit 0 no longer means unlimited: postProcess converts 0 to the
// secure built-in 60s default. Operators wanting a longer lifetime must
// set an explicit positive value.
Config config = withRef(
"node { rpc { maxConnectionIdleInMillis = 0, maxConnectionAgeInMillis = 0 } }");
NodeConfig nc = NodeConfig.fromConfig(config);
assertEquals(NodeConfig.RpcConfig.DEFAULT_MAX_CONNECTION_LIFETIME_IN_MILLIS,
nc.getRpc().getMaxConnectionIdleInMillis());
assertEquals(NodeConfig.RpcConfig.DEFAULT_MAX_CONNECTION_LIFETIME_IN_MILLIS,
nc.getRpc().getMaxConnectionAgeInMillis());
}

// ----- maxRstStream / secondsPerWindow: must be configured together -----

@Test
public void testRpcRstPairBothZeroOk() {
// Default: feature off. Must not throw.
NodeConfig nc = NodeConfig.fromConfig(withRef());
assertEquals(0, nc.getRpc().getMaxRstStream());
assertEquals(0, nc.getRpc().getSecondsPerWindow());
}

@Test
public void testRpcRstPairBothPositiveOk() {
Config config = withRef(
"node { rpc { maxRstStream = 1000, secondsPerWindow = 60 } }");
NodeConfig nc = NodeConfig.fromConfig(config);
assertEquals(1000, nc.getRpc().getMaxRstStream());
assertEquals(60, nc.getRpc().getSecondsPerWindow());
}

private static TronError rstError(String hocon) {
Config config = withRef("node { rpc { " + hocon + " } }");
return assertThrows(TronError.class, () -> NodeConfig.fromConfig(config));
}

@Test
public void testRpcRstPairHalfConfiguredWarnsButStarts() {
// Only maxRstStream set: RST flood protection stays disabled, startup
// continues with a warning.
Config config = withRef("node { rpc { maxRstStream = 100 } }");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: This test claims to verify that half-configured RST settings warn, but it only verifies startup continues and values remain unchanged. Capture the logger output and assert a WARN for each half-configured case so the security diagnostic cannot regress unnoticed.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At common/src/test/java/org/tron/core/config/args/NodeConfigTest.java, line 193:

<comment>This test claims to verify that half-configured RST settings warn, but it only verifies startup continues and values remain unchanged. Capture the logger output and assert a WARN for each half-configured case so the security diagnostic cannot regress unnoticed.</comment>

<file context>
@@ -146,6 +148,71 @@ public void testRpcNegativeConcurrentCallsRejected() {
+  public void testRpcRstPairHalfConfiguredWarnsButStarts() {
+    // Only maxRstStream set: RST flood protection stays disabled, startup
+    // continues with a warning.
+    Config config = withRef("node { rpc { maxRstStream = 100 } }");
+    NodeConfig nc = NodeConfig.fromConfig(config);
+    assertEquals(100, nc.getRpc().getMaxRstStream());
</file context>

NodeConfig nc = NodeConfig.fromConfig(config);
assertEquals(100, nc.getRpc().getMaxRstStream());
assertEquals(0, nc.getRpc().getSecondsPerWindow());

// Only secondsPerWindow set: same trap, same warn-and-continue.
config = withRef("node { rpc { secondsPerWindow = 60 } }");
nc = NodeConfig.fromConfig(config);
assertEquals(0, nc.getRpc().getMaxRstStream());
assertEquals(60, nc.getRpc().getSecondsPerWindow());
}

@Test
public void testRpcRstNegativeValuesRejected() {
TronError exception = rstError("maxRstStream = -1, secondsPerWindow = 60");
assertTrue(exception.getMessage().contains(
"must be non-negative, got: maxRstStream=-1"));

exception = rstError("maxRstStream = 100, secondsPerWindow = -5");
assertTrue(exception.getMessage().contains(
"must be non-negative, got: maxRstStream=100, secondsPerWindow=-5"));
}

@Test
public void testRpcUserOverrideExplicitValues() {
Config config = withRef(
Expand Down
Loading
Loading