Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
a068c83
feat(framework): support admin RPC
317787106 Aug 4, 2026
cbdb3d7
Merge branch 'develop' into feature/admin_rpc
317787106 Aug 4, 2026
38fc2bd
support multi client
317787106 Aug 4, 2026
e93fdad
Merge branch 'develop' into feature/admin_rpc
317787106 Aug 6, 2026
cf7307c
log warn if admin listen address not restirct on 127.0.0.1
317787106 Aug 6, 2026
667c7f4
extract annoation; add Exportable; remove committee from Exportable; …
317787106 Aug 6, 2026
95828db
fix bug from claude
317787106 Aug 7, 2026
95437eb
use isLoopbackListenAddress instead of 127.0.0.1; add sleep for ipcse…
317787106 Aug 7, 2026
75b4061
move IpcClient.start ahead of LogService.load; write sock file in out…
317787106 Aug 7, 2026
96964b5
fix the UNAVAILABLE of p2pConfig when export it
317787106 Aug 7, 2026
5731609
simply IpcService
317787106 Aug 7, 2026
a709dd6
refactor(api): split runtime parameters into follow-up
317787106 Aug 7, 2026
119420d
feat(api): expose runtime parameters over admin rpc
317787106 Aug 7, 2026
e1f53c5
refactor(api): simplify runtime exporter ownership
317787106 Aug 7, 2026
b281188
fix(api): harden runtime parameter snapshots
317787106 Aug 12, 2026
8f7896d
fix(api): export genesis addresses as base58
317787106 Aug 12, 2026
82fbf78
docs(api): explain explicit genesis snapshot
317787106 Aug 12, 2026
50b41c2
create directory for sock file; reorg admin config, add ipc.socketDir…
317787106 Aug 12, 2026
66b60f4
reorg admin config, add ipc.socketDirectory
317787106 Aug 12, 2026
300fd6e
add admin.rpc.virtualHosts; output error message if --attach is invalid
317787106 Aug 12, 2026
f0a6a91
add read timeout for client; set isRunning = false when inits;
317787106 Aug 13, 2026
ac1516b
optimize getSocketPathLength; IpcClient don't use Slf4j
317787106 Aug 13, 2026
1d45c7e
Merge branch 'feature/admin_rpc' into feature/admin_rpc_runtime_param…
317787106 Aug 13, 2026
0dc70a4
Merge branch 'develop' into feature/admin_rpc
317787106 Aug 21, 2026
906fb18
rename node.admin.rpc to node.admin.http
317787106 Aug 21, 2026
c569402
update some comment
317787106 Aug 21, 2026
10bde24
Merge branch 'feature/admin_rpc' into feature/admin_rpc_runtime_param…
317787106 Aug 21, 2026
8bdfb71
optimize shutdownExecutors
317787106 Aug 21, 2026
0ac21ee
Merge branch 'feature/admin_rpc' into feature/admin_rpc_runtime_param…
317787106 Aug 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 172 additions & 0 deletions common/src/main/java/org/tron/common/parameter/CommonParameter.java

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions common/src/main/java/org/tron/common/parameter/Exportable.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.tron.common.parameter;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Marks a {@link CommonParameter} field as safe to expose through the admin runtime-parameter
* API. Unmarked fields are excluded by default.
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Exportable {

}
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 AdminHttpConfig http = new AdminHttpConfig();
}

@Getter
@Setter
public static class AdminIpcConfig {

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

@Getter
@Setter
public static class AdminHttpConfig {

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
13 changes: 13 additions & 0 deletions common/src/main/java/org/tron/core/config/args/Storage.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.google.protobuf.ByteString;
import com.typesafe.config.Config;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -46,8 +47,11 @@ public class Storage {
private static final String DEFAULT_INDEX_SWITCH = "on";

// Optional per-tier LevelDB option overrides, read from StorageConfig bean
@Getter
private StorageConfig.DbOptionOverride defaultDbOption;
@Getter
private StorageConfig.DbOptionOverride defaultMDbOption;
@Getter
private StorageConfig.DbOptionOverride defaultLDbOption;

/**
Expand Down Expand Up @@ -85,6 +89,7 @@ public class Storage {
@Setter
private boolean checkpointSync;

@Getter
private Options defaultDbOptions;

@Getter
Expand Down Expand Up @@ -127,10 +132,18 @@ public String getCacheStrategy(CacheType dbName) {
return this.cacheStrategies.getOrDefault(dbName, CacheStrategies.getCacheStrategy(dbName));
}

public Map<CacheType, String> getCacheStrategies() {
return new HashMap<>(cacheStrategies);
}

public Sha256Hash getDbRoot(String dbName, Sha256Hash defaultV) {
return this.dbRoots.getOrDefault(dbName, defaultV);
}

public Map<String, Sha256Hash> getDbRoots() {
return new HashMap<>(dbRoots);
}

/**
* Accepts raw storage Config sub-tree because merkleRoot has dynamic keys
* (database names) that ConfigBeanFactory cannot bind to fixed bean fields.
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.
http {
# 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().getHttp().isEnable());
assertEquals("127.0.0.1", nc.getAdmin().getHttp().getListenAddress());
assertEquals(8575, nc.getAdmin().getHttp().getPort());
assertEquals(Collections.singletonList("localhost"),
nc.getAdmin().getHttp().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 testAdminHttpAndIpcBinding() {
Config config = withRef(
"node.admin { ipc { enable = true, socketDirectory = \"/tmp/tron-ipc\" },"
+ " http { 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().getHttp().isEnable());
assertEquals("127.0.0.2", nc.getAdmin().getHttp().getListenAddress());
assertEquals(18575, nc.getAdmin().getHttp().getPort());
assertEquals(Arrays.asList("admin.example.com", "localhost"),
nc.getAdmin().getHttp().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 HTTP / IPC ----
NodeConfig.AdminIpcConfig adminIpc = nc.getAdmin().getIpc();
NodeConfig.AdminHttpConfig adminHttp = nc.getAdmin().getHttp();
PARAMETER.adminHttpEnable = adminHttp.isEnable();
PARAMETER.adminHttpListenAddress = adminHttp.getListenAddress();
PARAMETER.adminHttpListenPort = adminHttp.getPort();
PARAMETER.adminHttpVirtualHosts = new ArrayList<>(adminHttp.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
Loading
Loading