Skip to content
Open
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
15 changes: 15 additions & 0 deletions common/src/main/java/org/tron/core/config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ node {
# default 100. Setting 0 also uses the secure default.
# maxConcurrentCallsPerConnection = 100

# Maximum RST_STREAM frames per connection per window; 0 uses the default of 1000.
# maxRstStream = 1000

# RST_STREAM counting window in seconds; 0 uses the default of 5.
# secondsPerWindow = 5

# The HTTP/2 flow control window, default 1MB
# flowControlWindow =

Expand All @@ -80,6 +86,15 @@ node {
> It now selects the secure default of 100. Configure an explicit positive value if a node
> requires more than 100 concurrent calls on one connection.

> **Upgrade note:** `maxRstStream = 0` and `secondsPerWindow = 0` no longer disable
> RST_STREAM flood protection. Each zero independently falls back to its secure default
> (1000 frames / 5 seconds), with a startup warning. Negative values and
> `maxRstStream = 2147483647` (`Integer.MAX_VALUE`, grpc-java's disable sentinel) are rejected.
> These are java-tron defaults; grpc-java itself defaults to no limit.
> Exceeding the limit closes that connection with `GOAWAY(ENHANCE_YOUR_CALM)`.
> Clients that frequently cancel calls, including deadline cancellations, may need explicit
> positive limits tuned to their workload; keep `maxRstStream` below `2147483647`.

## backup
You can customize backup options in the `node.backup` part of `config.conf`, which looks like:
```
Expand Down
29 changes: 27 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,8 @@ public static class HttpConfig {
public static class RpcConfig {

public static final int DEFAULT_MAX_CONCURRENT_CALLS_PER_CONNECTION = 100;
public static final int DEFAULT_MAX_RST_STREAM = 1000;
public static final int DEFAULT_SECONDS_PER_WINDOW = 5;

private boolean enable = true;
private int port = 50051;
Expand All @@ -224,8 +226,8 @@ public static class RpcConfig {
private long maxConnectionAgeInMillis = 0;
private int maxMessageSize = 4194304;
private int maxHeaderListSize = 8192;
private int maxRstStream = 0;
private int secondsPerWindow = 0;
private int maxRstStream = DEFAULT_MAX_RST_STREAM;
private int secondsPerWindow = DEFAULT_SECONDS_PER_WINDOW;
private int minEffectiveConnection = 1;
private boolean reflectionService = false;
private boolean trxCacheEnable = false;
Expand Down Expand Up @@ -373,6 +375,29 @@ private void postProcess() {
rpc.maxConcurrentCallsPerConnection =
RpcConfig.DEFAULT_MAX_CONCURRENT_CALLS_PER_CONNECTION;
}
if (rpc.maxRstStream < 0) {
throw new TronError("node.rpc.maxRstStream must be non-negative, got: "
+ rpc.maxRstStream, PARAMETER_INIT);
}
if (rpc.secondsPerWindow < 0) {
throw new TronError("node.rpc.secondsPerWindow must be non-negative, got: "
+ rpc.secondsPerWindow, PARAMETER_INIT);
}
// Only the frame count has a grpc-java disable sentinel; the window does not.
if (rpc.maxRstStream == Integer.MAX_VALUE) {
throw new TronError("node.rpc.maxRstStream must not be Integer.MAX_VALUE because grpc-java "
+ "treats it as disabling RST_STREAM flood protection", PARAMETER_INIT);
}
if (rpc.maxRstStream == 0) {
logger.warn("Configuring [node.rpc.maxRstStream] as 0 no longer disables RST_STREAM flood "
+ "protection; using the secure default of {}.", RpcConfig.DEFAULT_MAX_RST_STREAM);
rpc.maxRstStream = RpcConfig.DEFAULT_MAX_RST_STREAM;
}
if (rpc.secondsPerWindow == 0) {
logger.warn("Configuring [node.rpc.secondsPerWindow] as 0 no longer disables RST_STREAM flood "
+ "protection; using the secure default of {}.", RpcConfig.DEFAULT_SECONDS_PER_WINDOW);
rpc.secondsPerWindow = RpcConfig.DEFAULT_SECONDS_PER_WINDOW;
}
if (rpc.maxConnectionIdleInMillis == 0) {
rpc.maxConnectionIdleInMillis = Long.MAX_VALUE;
}
Expand Down
10 changes: 6 additions & 4 deletions common/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -308,11 +308,13 @@ node {
# Maximum header list size (bytes), default 8192
maxHeaderListSize = 8192

# RST_STREAM frames allowed per connection per period, 0 = no limit
maxRstStream = 0
# RST_STREAM frames allowed per connection per period. Integer.MAX_VALUE is rejected.
# 0 is accepted for backward compatibility and falls back to the secure default of 1000.
maxRstStream = 1000

# Seconds per period for gRPC RST_STREAM limit
secondsPerWindow = 0
# Seconds per period for the gRPC RST_STREAM limit.
# 0 is accepted for backward compatibility and falls back to the secure default of 5.
secondsPerWindow = 5

# Minimum effective connections required to broadcast transactions
minEffectiveConnection = 1
Expand Down
60 changes: 60 additions & 0 deletions common/src/test/java/org/tron/core/config/args/NodeConfigTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.tron.core.exception.TronError.ErrCode.PARAMETER_INIT;

import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
Expand Down Expand Up @@ -111,6 +112,8 @@ public void testRpcDefaultsFromReference() {
assertEquals(9223372036854775807L, rpc.getMaxConnectionAgeInMillis());
assertEquals(4194304, rpc.getMaxMessageSize());
assertEquals(8192, rpc.getMaxHeaderListSize());
assertEquals(NodeConfig.RpcConfig.DEFAULT_MAX_RST_STREAM, rpc.getMaxRstStream());
assertEquals(NodeConfig.RpcConfig.DEFAULT_SECONDS_PER_WINDOW, rpc.getSecondsPerWindow());
assertEquals(1, rpc.getMinEffectiveConnection());
// thread=0 in reference.conf triggers auto-detect in postProcess
assertTrue(rpc.getThread() > 0);
Expand Down Expand Up @@ -146,6 +149,63 @@ public void testRpcNegativeConcurrentCallsRejected() {
"node.rpc.maxConcurrentCallsPerConnection must be non-negative, got: -1"));
}

@Test
public void testRpcRstDefaultsMatchReference() {
NodeConfig.RpcConfig rpc = new NodeConfig.RpcConfig();
assertEquals(1000, rpc.getMaxRstStream());
assertEquals(5, rpc.getSecondsPerWindow());

Config reference = withRef();
assertEquals(rpc.getMaxRstStream(), reference.getInt("node.rpc.maxRstStream"));
assertEquals(rpc.getSecondsPerWindow(), reference.getInt("node.rpc.secondsPerWindow"));
}

@Test
public void testRpcZeroRstLimitsUseSecureDefaultsIndependently() {
int[][] cases = {
{0, 0, NodeConfig.RpcConfig.DEFAULT_MAX_RST_STREAM,
NodeConfig.RpcConfig.DEFAULT_SECONDS_PER_WINDOW},
{0, 10, NodeConfig.RpcConfig.DEFAULT_MAX_RST_STREAM, 10},
{5, 0, 5, NodeConfig.RpcConfig.DEFAULT_SECONDS_PER_WINDOW}
};
for (int[] values : cases) {
NodeConfig.RpcConfig rpc = NodeConfig.fromConfig(withRef(
"node.rpc { maxRstStream = " + values[0]
+ ", secondsPerWindow = " + values[1] + " }")).getRpc();
assertEquals(values[2], rpc.getMaxRstStream());
assertEquals(values[3], rpc.getSecondsPerWindow());
}
}

@Test
public void testRpcInvalidRstLimitsRejectedBeforeFallback() {
int[][] cases = {
{-1, 5}, {1000, -1}, {-1, 0}, {0, -1},
{Integer.MIN_VALUE, 5}, {1000, Integer.MIN_VALUE},
{Integer.MAX_VALUE, 5}, {Integer.MAX_VALUE, 0}
};
for (int[] values : cases) {
Config config = withRef("node.rpc { maxRstStream = " + values[0]
+ ", secondsPerWindow = " + values[1] + " }");
TronError exception = assertThrows(TronError.class, () -> NodeConfig.fromConfig(config));
assertEquals(PARAMETER_INIT, exception.getErrCode());
assertTrue(exception.getMessage().contains(values[1] < 0
? "node.rpc.secondsPerWindow" : "node.rpc.maxRstStream"));
}
}

@Test
public void testRpcExplicitPositiveRstLimitsPreserved() {
int[][] cases = {{5, 10}, {200, 30}, {1, 1}, {Integer.MAX_VALUE - 1, Integer.MAX_VALUE}};
for (int[] values : cases) {
NodeConfig.RpcConfig rpc = NodeConfig.fromConfig(withRef(
"node.rpc { maxRstStream = " + values[0]
+ ", secondsPerWindow = " + values[1] + " }")).getRpc();
assertEquals(values[0], rpc.getMaxRstStream());
assertEquals(values[1], rpc.getSecondsPerWindow());
}
}

@Test
public void testRpcUserOverrideExplicitValues() {
Config config = withRef(
Expand Down
13 changes: 13 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ node {
solidityPort = 50061
# Maximum concurrent calls per connection. 0 uses the secure default of 100.
maxConcurrentCallsPerConnection = 100
# Maximum RST_STREAM frames per connection per window. 0 uses the default of 1000.
maxRstStream = 1000
# RST_STREAM counting window in seconds. 0 uses the default of 5.
secondsPerWindow = 5
# Idle connection timeout (ms). 0 = no limit.
maxConnectionIdleInMillis = 0
# Minimum active connections required before broadcasting transactions.
Expand All @@ -118,6 +122,15 @@ node {
> It now selects the secure default of 100. Configure an explicit positive value if a client
> needs more than 100 concurrent calls on one connection.

> **Upgrade note:** `node.rpc.maxRstStream = 0` and `node.rpc.secondsPerWindow = 0`
> no longer disable RST_STREAM flood protection. Each zero independently falls back to its
> secure default (1000 frames / 5 seconds), with a startup warning. Negative values and
> `maxRstStream = 2147483647` (`Integer.MAX_VALUE`, grpc-java's disable sentinel) are rejected.
> These are java-tron defaults; grpc-java itself defaults to no limit.
> Exceeding the limit closes that connection with `GOAWAY(ENHANCE_YOUR_CALM)`.
> Clients that frequently cancel calls, including deadline cancellations, may need explicit
> positive limits tuned to their workload; keep `maxRstStream` below `2147483647`.

To disable an API endpoint that you do not want to expose publicly, set its `Enable` flag to `false` or add endpoints to `node.disabledApi`:

```hocon
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

package org.tron.common.application;

import static org.tron.core.exception.TronError.ErrCode.API_SERVER_INIT;

import io.grpc.Server;
import io.grpc.netty.NettyServerBuilder;
import io.grpc.protobuf.services.ProtoReflectionService;
Expand All @@ -26,6 +28,7 @@
import org.tron.common.es.ExecutorServiceManager;
import org.tron.common.parameter.CommonParameter;
import org.tron.core.config.args.Args;
import org.tron.core.exception.TronError;
import org.tron.core.services.filter.LiteFnQueryGrpcInterceptor;
import org.tron.core.services.ratelimiter.PrometheusInterceptor;
import org.tron.core.services.ratelimiter.RateLimiterInterceptor;
Expand Down Expand Up @@ -92,8 +95,15 @@ public CompletableFuture<Boolean> start() {
}

protected NettyServerBuilder initServerBuilder() {
NettyServerBuilder serverBuilder = NettyServerBuilder.forPort(this.port);
CommonParameter parameter = Args.getInstance();
int maxRstStream = parameter.getRpcMaxRstStream();
int secondsPerWindow = parameter.getRpcSecondsPerWindow();
// Validate before allocating the executor, including callers bypassing NodeConfig.
if (maxRstStream <= 0 || secondsPerWindow <= 0 || maxRstStream == Integer.MAX_VALUE) {
throw new TronError("Invalid gRPC RST_STREAM limit config: maxRstStream="
+ maxRstStream + ", secondsPerWindow=" + secondsPerWindow, API_SERVER_INIT);
}
NettyServerBuilder serverBuilder = NettyServerBuilder.forPort(this.port);
if (parameter.getRpcThreadNum() > 0) {
this.executorService = ExecutorServiceManager.newFixedThreadPool(
this.executorName, parameter.getRpcThreadNum());
Expand All @@ -107,10 +117,7 @@ protected NettyServerBuilder initServerBuilder() {
.maxConnectionAge(parameter.getMaxConnectionAgeInMillis(), TimeUnit.MILLISECONDS)
.maxInboundMessageSize(parameter.getMaxMessageSize())
.maxHeaderListSize(parameter.getMaxHeaderListSize());
if (parameter.getRpcMaxRstStream() > 0 && parameter.getRpcSecondsPerWindow() > 0) {
serverBuilder.maxRstFramesPerWindow(
parameter.getRpcMaxRstStream(), parameter.getRpcSecondsPerWindow());
}
serverBuilder.maxRstFramesPerWindow(maxRstStream, secondsPerWindow);

if (parameter.isRpcReflectionServiceEnable()) {
serverBuilder.addService(ProtoReflectionService.newInstance());
Expand Down
Loading
Loading