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
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import lombok.Getter;
import lombok.Setter;
Expand Down Expand Up @@ -490,6 +491,24 @@ public class CommonParameter {
public int jsonRpcMaxLogFilterNum = 20000;
@Getter
@Setter
public boolean adminRpcEnable = false;
@Getter
@Setter
public String adminListenAddress = Constant.LOCAL_HOST;
@Getter
@Setter
public int adminListenPort = 8575;
@Getter
@Setter
public List<String> adminVirtualHosts = new ArrayList<>(Collections.singletonList("localhost"));
@Getter
@Setter
public boolean ipcEnable = false;
@Getter
@Setter
public String ipcSocketDirectory = "";
@Getter
@Setter
public int maxTransactionPendingSize;
@Getter
@Setter
Expand Down
29 changes: 29 additions & 0 deletions common/src/main/java/org/tron/core/config/args/NodeConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
import com.typesafe.config.ConfigBeanFactory;
import com.typesafe.config.ConfigValueFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.tron.core.Constant;
import org.tron.core.exception.TronError;

// Node configuration bean for the "node" section of config.conf.
Expand Down Expand Up @@ -128,6 +130,7 @@ public int getValidContractProtoThreads() {
private HttpConfig http = new HttpConfig();
private RpcConfig rpc = new RpcConfig();
private JsonRpcConfig jsonrpc = new JsonRpcConfig();
private AdminConfig admin = new AdminConfig();
private NodeBackupConfig backup = new NodeBackupConfig();
private DynamicConfigSection dynamicConfig = new DynamicConfigSection();
private DnsConfig dns = new DnsConfig();
Expand Down Expand Up @@ -252,6 +255,32 @@ public static class JsonRpcConfig {
private long maxMessageSize = 4194304;
}

@Getter
@Setter
public static class AdminConfig {

private AdminIpcConfig ipc = new AdminIpcConfig();
private AdminRpcConfig rpc = new AdminRpcConfig();
}

@Getter
@Setter
public static class AdminIpcConfig {

private boolean enable = false;
private String socketDirectory = "";
}

@Getter
@Setter
public static class AdminRpcConfig {

private boolean enable = false;
private String listenAddress = Constant.LOCAL_HOST;
private int port = 8575;
private List<String> virtualHosts = new ArrayList<>(Collections.singletonList("localhost"));
}

@Getter
@Setter
public static class NodeBackupConfig {
Expand Down
26 changes: 26 additions & 0 deletions common/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,32 @@ node {
maxMessageSize = 4194304
}

# Administrative API settings. Disabled by default.
admin {
# Local Unix-domain socket administrative API.
ipc {
# Whether to enable the local Unix-domain socket admin API. Default: false.
enable = false
# Parent directory for the private .ipc directory. It must be an absolute path
# when set. Empty means output-directory. The node fails to start if the resulting socket
# path exceeds the portable Unix-domain socket path limit.
socketDirectory = ""
}

# Administrative JSON-RPC HTTP API.
rpc {
# Whether to enable the administrative JSON-RPC HTTP service. Default: false.
enable = false
# Address on which the service listens. Keep the default loopback address for security.
listenAddress = "127.0.0.1"
# TCP port on which the administrative JSON-RPC HTTP service listens. Default: 8575.
port = 8575
# Allowed HTTP Host header names. Matching is case-insensitive and ignores the port.
# IP address literals are always allowed. Use ["*"] only to explicitly allow any hostname.
virtualHosts = ["localhost"]
}
}

# Disabled API list (works for http, rpc and pbft, not jsonrpc). Case insensitive.
disabledApi = [
# "getaccount",
Expand Down
25 changes: 25 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 @@ -7,6 +7,8 @@

import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import java.util.Arrays;
import java.util.Collections;
import org.junit.Test;
import org.tron.core.exception.TronError;

Expand All @@ -30,6 +32,13 @@ public void testDefaults() {
assertEquals(8, nc.getMinConnections());
assertEquals(4, nc.getMaxFastForwardNum());
assertFalse(nc.isOpenFullTcpDisconnect());
assertFalse(nc.getAdmin().getIpc().isEnable());
assertEquals("", nc.getAdmin().getIpc().getSocketDirectory());
assertFalse(nc.getAdmin().getRpc().isEnable());
assertEquals("127.0.0.1", nc.getAdmin().getRpc().getListenAddress());
assertEquals(8575, nc.getAdmin().getRpc().getPort());
assertEquals(Collections.singletonList("localhost"),
nc.getAdmin().getRpc().getVirtualHosts());
// reference.conf matches code default: discovery disabled when not configured
assertFalse(nc.isDiscoveryEnable());
assertFalse(nc.isDiscoveryPersist());
Expand Down Expand Up @@ -79,6 +88,22 @@ public void testRpcSubBean() {
assertEquals(60071, nc.getRpc().getPBFTPort());
}

@Test
public void testAdminRpcAndIpcBinding() {
Config config = withRef(
"node.admin { ipc { enable = true, socketDirectory = \"/tmp/tron-ipc\" },"
+ " rpc { enable = true, listenAddress = \"127.0.0.2\", port = 18575,"
+ " virtualHosts = [\"admin.example.com\", \"localhost\"] } }");
NodeConfig nc = NodeConfig.fromConfig(config);
assertTrue(nc.getAdmin().getIpc().isEnable());
assertEquals("/tmp/tron-ipc", nc.getAdmin().getIpc().getSocketDirectory());
assertTrue(nc.getAdmin().getRpc().isEnable());
assertEquals("127.0.0.2", nc.getAdmin().getRpc().getListenAddress());
assertEquals(18575, nc.getAdmin().getRpc().getPort());
assertEquals(Arrays.asList("admin.example.com", "localhost"),
nc.getAdmin().getRpc().getVirtualHosts());
}

@Test
public void testBackupSubBean() {
Config config = withRef(
Expand Down
2 changes: 2 additions & 0 deletions framework/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ dependencies {
testImplementation group: 'org.springframework', name: 'spring-test', version: "${springVersion}"
testImplementation group: 'javax.portlet', name: 'portlet-api', version: '3.0.1'
implementation group: 'org.zeromq', name: 'jeromq', version: '0.5.3'
implementation group: 'com.kohlschutter.junixsocket', name: 'junixsocket-core', version: '2.10.1'
implementation group: 'org.jline', name: 'jline', version: '3.21.0'
api project(":chainbase")
api project(":protocol")
api project(":actuator")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.eclipse.jetty.server.ConnectionLimit;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.ErrorHandler;
import org.eclipse.jetty.server.handler.SizeLimitHandler;
import org.eclipse.jetty.servlet.ServletContextHandler;
Expand All @@ -39,6 +40,8 @@ public abstract class HttpService extends AbstractService {

protected Server apiServer;

protected String listenAddress;

protected String contextPath;

protected long maxRequestSize = 4 * 1024 * 1024; // 4MB
Expand Down Expand Up @@ -77,7 +80,13 @@ public CompletableFuture<Boolean> start() {
}

protected void initServer() {
this.apiServer = new Server(this.port);
this.apiServer = new Server();
ServerConnector connector = new ServerConnector(this.apiServer);
connector.setPort(this.port);
if (this.listenAddress != null) {
connector.setHost(this.listenAddress);
}
this.apiServer.addConnector(connector);
int maxHttpConnectNumber = Args.getInstance().getMaxHttpConnectNumber();
if (maxHttpConnectNumber > 0) {
this.apiServer.addBean(new ConnectionLimit(maxHttpConnectNumber, this.apiServer));
Expand Down
78 changes: 70 additions & 8 deletions framework/src/main/java/org/tron/core/config/args/Args.java
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@ public class Args extends CommonParameter {
@Getter
private static String configFilePath = "";

@Getter
private static String ipcSocketFile;

@Getter
private static String ipcExecCommand;

// Singleton config beans — populated at startup, read-only after init.
// New code can read directly from these beans instead of CommonParameter.
@Getter
Expand Down Expand Up @@ -159,6 +165,10 @@ public static void setParam(final String[] args, final String confFileName) {
Args.printHelp(jc);
exit(0);
}
List<ParameterDescription> assignedParameters = getAssignedParameters(jc);
if (tryApplyAttachParams(cmd, assignedParameters)) {
return;
}

// Resolve config file path
configFilePath = StringUtils.isNoneBlank(cmd.shellConfFileName)
Expand All @@ -169,7 +179,7 @@ public static void setParam(final String[] args, final String confFileName) {
applyConfigParams(config);

// 3. CLI overrides Config (highest priority, including --es → eventSubscribe)
applyCLIParams(cmd, jc);
applyCLIParams(cmd, assignedParameters);

// 4. Apply event config after CLI
applyEventConfig(eventConfig);
Expand All @@ -181,6 +191,47 @@ public static void setParam(final String[] args, final String confFileName) {
initLocalWitnesses(config, cmd);
}

private static List<ParameterDescription> getAssignedParameters(JCommander jc) {
return jc.getParameters().stream()
.filter(ParameterDescription::isAssigned)
.collect(Collectors.toList());
}

private static boolean tryApplyAttachParams(CLIParameter cmd,
List<ParameterDescription> assignedParameters) {
boolean attachAssigned = isParameterAssigned(assignedParameters, "ipcSocketFile");
if (!attachAssigned) {
if (isParameterAssigned(assignedParameters, "ipcExecCommand")) {
throwAttachParameterError("Error: --exec requires --attach <socket-path>");
}
return false;
}
if (isParameterAssigned(assignedParameters, "shellConfFileName")) {
throwAttachParameterError("Error: --attach cannot be combined with: --config");
}
if (StringUtils.isBlank(cmd.ipcSocketFile)) {
throwAttachParameterError("Error: --attach requires a non-empty <socket-path>");
}
// Node-only CLI options are irrelevant to the standalone IPC client and are ignored.
ipcSocketFile = cmd.ipcSocketFile;
ipcExecCommand = cmd.ipcExecCommand;
if (StringUtils.isNotEmpty(cmd.logbackPath)) {
PARAMETER.logbackPath = cmd.logbackPath;
}
return true;
}

private static void throwAttachParameterError(String message) {
System.err.println(message);
throw new TronError(message, TronError.ErrCode.PARAMETER_INIT);
}

private static boolean isParameterAssigned(List<ParameterDescription> assignedParameters,
String fieldName) {
return assignedParameters.stream()
.anyMatch(pd -> fieldName.equals(pd.getParameterized().getName()));
}

/**
* Bridge VmConfig bean values to CommonParameter fields.
* Temporary until Phase 2 moves fields into domain config objects.
Expand Down Expand Up @@ -561,6 +612,16 @@ private static void applyNodeConfig(NodeConfig nc) {
PARAMETER.jsonRpcMaxLogFilterNum = jsonrpc.getMaxLogFilterNum();
PARAMETER.jsonRpcMaxMessageSize = jsonrpc.getMaxMessageSize();

// ---- Admin RPC / IPC ----
NodeConfig.AdminIpcConfig adminIpc = nc.getAdmin().getIpc();
NodeConfig.AdminRpcConfig adminRpc = nc.getAdmin().getRpc();
PARAMETER.adminRpcEnable = adminRpc.isEnable();
PARAMETER.adminListenAddress = adminRpc.getListenAddress();
PARAMETER.adminListenPort = adminRpc.getPort();
PARAMETER.adminVirtualHosts = new ArrayList<>(adminRpc.getVirtualHosts());
PARAMETER.ipcEnable = adminIpc.isEnable();
PARAMETER.ipcSocketDirectory = adminIpc.getSocketDirectory();

// ---- P2P sub-bean ----
PARAMETER.nodeP2pVersion = nc.getP2p().getVersion();

Expand Down Expand Up @@ -769,14 +830,13 @@ public static void applyConfigParams(
* Apply CLI parameters that were explicitly passed.
* Only assigned parameters override Config values.
*/
private static void applyCLIParams(CLIParameter cmd, JCommander jc) {
Set<String> assigned = jc.getParameters().stream()
.filter(ParameterDescription::isAssigned)
private static void applyCLIParams(CLIParameter cmd,
List<ParameterDescription> assignedParameters) {
Set<String> assigned = assignedParameters.stream()
.map(ParameterDescription::getLongestName)
.collect(Collectors.toSet());

jc.getParameters().stream()
.filter(ParameterDescription::isAssigned)
assignedParameters.stream()
.filter(pd -> {
try {
return CLIParameter.class.getDeclaredField(pd.getParameterized().getName())
Expand Down Expand Up @@ -946,6 +1006,8 @@ public static void clearParam() {
rateLimiterConfig = null;
metricsConfig = null;
eventConfig = null;
ipcSocketFile = null;
ipcExecCommand = null;
}

// getProposalExpirationTime removed — logic moved to BlockConfig.fromConfig()
Expand Down Expand Up @@ -1292,7 +1354,8 @@ private static String getCommitIdAbbrev() {

private static Map<String, String[]> getOptionGroup() {
String[] tronOption = new String[] {"version", "help", "shellConfFileName", "logbackPath",
"eventSubscribe", "solidityNode", "keystoreFactory"};
"eventSubscribe", "solidityNode", "keystoreFactory", "ipcSocketFile",
"ipcExecCommand"};
String[] dbOption = new String[] {"outputDirectory"};
String[] witnessOption = new String[] {"witness", "privateKey"};
String[] vmOption = new String[] {"debug"};
Expand All @@ -1315,4 +1378,3 @@ private static Map<String, String[]> getOptionGroup() {
return optionGroupMap;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ public class CLIParameter {
@Parameter(names = {"--keystore-factory"}, description = "running KeystoreFactory")
public boolean keystoreFactory;

@Parameter(names = {"--attach"},
description = "running an IPC client to interact with FullNode")
public String ipcSocketFile;

@Parameter(names = {"--exec"},
description = "execute one Admin IPC command and exit (requires --attach)")
public String ipcExecCommand;

@Deprecated
@Parameter(names = {"--fast-forward"})
public boolean fastForward;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.tron.core.services.admin;

import com.googlecode.jsonrpc4j.JsonRpcError;
import com.googlecode.jsonrpc4j.JsonRpcErrors;
import com.googlecode.jsonrpc4j.JsonRpcMethod;
import com.googlecode.jsonrpc4j.JsonRpcParam;
import org.tron.core.exception.jsonrpc.JsonRpcInvalidParamsException;

public interface AdminJsonRpc {

@JsonRpcMethod("admin_example")
@JsonRpcErrors({
@JsonRpcError(exception = JsonRpcInvalidParamsException.class, code = -32602, data = "{}"),
})
String adminExample(@JsonRpcParam("param1") String param1, @JsonRpcParam("param2") String param2)
throws JsonRpcInvalidParamsException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.tron.core.services.admin;

import org.springframework.stereotype.Component;
import org.tron.core.exception.jsonrpc.JsonRpcInvalidParamsException;

@Component
public class AdminJsonRpcImpl implements AdminJsonRpc {
@Override
public String adminExample(String param1, String param2) throws JsonRpcInvalidParamsException {
if ("".equals(param1) || "".equals(param2)) {
throw new JsonRpcInvalidParamsException("param1 or param2 should not be empty");
}
return param1 + ":" + param2;
}
}
Loading
Loading