Skip to content

feat(framework): support admin JSON-RPC over IPC and HTTP - #82

Closed
317787106 wants to merge 24 commits into
developfrom
feature/admin_rpc
Closed

317787106 wants to merge 24 commits into
developfrom
feature/admin_rpc

Conversation

@317787106

@317787106 317787106 commented Jul 31, 2026

Copy link
Copy Markdown
Owner

What does this PR do?

This PR adds the Admin JSON-RPC transport foundation requested by tronprotocol/java-tron#6497. A single annotated AdminJsonRpc interface is shared by an HTTP endpoint and a local Unix-domain-socket service. The initial admin_example method verifies typed command dispatch and annotation-based JSON-RPC error handling; additional administrative methods can be added to the same interface.

In this PR, JSON-RPC defines the shared API and message format, while HTTP and IPC are the two transports:

                Admin JSON-RPC API
                 /             \
        HTTP transport     IPC transport
          POST /admin      Unix domain socket

Admin HTTP service

The HTTP service is available to FullNode processes at POST /admin and is disabled by default. It binds Jetty to the configured address and port. Enabling it on a non-loopback address emits a warning.

The servlet validates the Host header against the configured virtual-host allowlist, while accepting IPv4 and IPv6 literals. It accepts application/json, application/json-rpc, and application/*+json media types, and rejects unsupported content types with HTTP 415. JSON-RPC parsing reuses a constrained object mapper with nesting-depth and token-count limits. JSON-RPC results, including protocol errors, use HTTP 200 responses.

The HTTP transport configuration is:

node.admin.http {
  enable = false
  listenAddress = "127.0.0.1"
  port = 8575
  virtualHosts = ["localhost"]
}

Admin IPC service

The IPC service is also FullNode-only and disabled by default. By default it creates an endpoint at:

<output-directory>/.ipc/<pid>.sock

node.admin.ipc.socketDirectory can select another socket root and must be an existing absolute directory on a POSIX-compatible filesystem. The service creates the private .ipc directory with owner-only access and sets the socket to 0600. It refuses to replace a symbolic link or non-directory at the private directory path.

The complete encoded socket path is limited to 100 bytes for portability across supported Unix-domain-socket implementations. If the path is longer, startup fails with guidance to configure a shorter node.admin.ipc.socketDirectory; it does not silently relocate the endpoint.

node.admin.ipc {
  enable = false
  socketDirectory = ""
}

IPC uses newline-delimited, single-line JSON-RPC messages and the same AdminJsonRpc, error resolver, and constrained JSON mapper as HTTP. Request size is bounded by the existing node.rpc.maxMessageSize value, whose default is 4 MiB. Each client has a ten-minute idle timeout. The bounded client executor supports concurrent console sessions and immediately closes connections that arrive after all handlers are occupied. Unexpected accept failures use a five-second retry delay.

Startup failures and normal shutdown both clean up owned sockets and the private directory. Shutdown closes active clients before stopping executors so blocked native socket reads do not unnecessarily delay node termination.

IPC console

The FullNode executable can attach to an active node without initializing another node instance:

java -jar FullNode.jar --attach <socket-path>

A single command can be executed for scripting:

java -jar FullNode.jar --attach <socket-path> --exec "<command> [arguments]"

Attach mode is handled immediately after CLI argument parsing and before CommonParameter, Logback, database, witness, or node services are initialized. --exec requires --attach, an empty socket path is rejected, and attach mode cannot be combined with --config.

The JLine console derives command names, parameter names, and parameter types from the annotated Admin API. It supports quoted arguments, typed JSON conversion, sorted help, canonical command completion, formatted JSON results, help, exit, and quit. One-shot execution returns a non-zero process status for invalid commands, JSON-RPC errors, communication failures, disconnection before a response, or the 30-second response timeout.

Supporting changes

HttpService now supports binding a service to a specific listen address. The regular JSON-RPC servlet and the Admin transports share the new constrained JsonRpcMapper, and supported JSON media-type matching is centralized in JsonRpcMediaType.

The framework adds JLine for the interactive console and junixsocket for Unix-domain-socket support. Dependency verification metadata is updated accordingly.

Why are these changes required?

Administrative operations need a local, scriptable interface without starting a second node or exposing the existing public APIs as privileged management endpoints. The Unix-domain socket provides a private local transport, while the optional HTTP endpoint supports controlled integration when explicitly enabled.

Sharing one typed Admin API across both transports keeps command names, parameters, results, and JSON-RPC errors consistent. Explicit address binding, virtual-host validation, parser limits, filesystem permissions, bounded clients, and deterministic cleanup provide safer operational defaults.

Testing

The PR adds or updates tests covering:

  • Admin configuration defaults, binding, and attach-mode CLI validation.
  • HTTP listen-address handling, loopback classification, virtual-host validation, JSON media types, and parser limits.
  • IPC path resolution and encoded-length limits, absolute-directory validation, POSIX permissions, stale-path safety, startup rollback, and shutdown cleanup.
  • IPC request limits, annotated errors, concurrent clients, idle timeouts, overload rejection, and active-client shutdown.
  • Console command discovery, typed arguments, quoted whitespace, completion, help, response formatting, one-shot exit codes, timeouts, and disconnect behavior.
  • FullNode startup ordering to ensure attach mode does not initialize node logging.

The related Admin HTTP, IPC, CLI, configuration, and FullNode tests, together with production and test checkstyle checks, passed during development.

Follow-up

Runtime parameter export is split into stacked Draft PR #83. Peer management commands will be implemented separately on feature/peer_management using the Admin transports introduced here.

@317787106
317787106 force-pushed the feature/admin_rpc branch from e299fd9 to 147613e Compare July 31, 2026 09:13
@317787106 317787106 changed the title Feature/admin rpc feat(framework): support admin rpc Jul 31, 2026
@317787106 317787106 changed the title feat(framework): support admin rpc feat(framework): support admin IPC and RPC Aug 4, 2026
@317787106 317787106 changed the title feat(framework): support admin IPC and RPC feat(framework): support admin JSON-RPC over IPC and HTTP Aug 21, 2026
…me (tronprotocol#6950)

* chore(deps): upgrade grpc-java from 1.83.0 to 1.83.1

1. bump grpcVersion to 1.83.1 to pick up the upstream fix for
   grpc/grpc-java#12930 (PR grpc/grpc-java#12942), which enforces
   connection.remote().maxActiveStreams(maxStreams) at handler startup
2. drop GrpcNettyMaxConcurrentStreamsLimiter, the local protocol-negotiator
   shim that applied the same limit while 1.83.0 left the remote endpoint
   unbounded until the client acknowledged SETTINGS

* chore(deps): upgrade jackson from 2.18.6 to 2.18.10

bump jackson-databind from 2.18.6 to 2.18.10 to pick up cumulative fixes from the 2.18.x line

* chore(deps): upgrade logback to 1.3.16 and slf4j to 2.0.17

1. bump logback-classic from 1.2.13 to 1.3.16 and slf4j-api,
   jcl-over-slf4j, jul-to-slf4j from 1.7.36 to 2.0.17; logback 1.3
   requires the slf4j 2.0 provider model, and 1.3.16 is the last 1.3.x
   release and the ceiling for the x86_64 JDK 8 build, since 1.5.x
   requires JDK 11
2. rename DelayingShutdownHook to DefaultShutdownHook in the toolkit
   logback.xml; logback 1.3 removed the old class and only auto-maps
   the legacy name with a startup warning
3. drop the CONSOLE appender from the toolkit logback.xml; no logger
   ever referenced it, so it never emitted output on 1.2 either, and
   logback 1.3 now flags it with an unreferenced-appender warning
4. accept one known 1.3.x behavior change: SizeAndTimeBasedRollingPolicy
   now throttles its maxFileSize comparison to once per 60s
   (SimpleInvocationGate) instead of the adaptive ~100-800ms gate of
   1.2.13, so under sustained heavy logging a file can overshoot the
   500MB cap by up to 60s of writes before the %i rollover fires;
   time-based rollover and totalSizeCap/maxHistory cleanup are ungated
   and unaffected
5. note for operators running a custom --log-config file: well-formed
   1.2-era configs using standard elements keep working unchanged
   (jmxConfigurator degrades to an ignored-property warning, the legacy
   shutdown hook name is auto-mapped), and malformed XML still fails
   fast via TronError(LOG_LOAD) exactly as on 1.2; however, a config
   that references an uninstantiable class (e.g. a custom appender
   missing from the classpath) now aborts the whole appender-ref phase
   instead of losing just that one appender, so the node starts with no
   log output while the ERROR statuses are printed to stdout by
   LogService

* chore(deps): upgrade commons-lang3/collections4 and drop commons-math

1. bump commons-lang3 from 3.4 to 3.20.0; the runtime classpath already
   resolved 3.18.0 through libp2p 2.2.9's transitive requirement, so
   align the declaration with what actually ships and move past the
   CVE-2025-48924 range that the nominal 3.4 still sits in
2. bump commons-collections4 from 4.1 to 4.6.0
3. remove commons-math 2.2; no source file imports
   org.apache.commons.math and nothing else in the dependency graph
   requests it

* chore(deps): remove joda-time and use JDK time APIs

1. drop the joda-time 2.3 dependency.
2. replace the six new DateTime(millis) log-formatting call sites in
   DynamicPropertiesStore, DposTask and DposService with a new
   Time.getIsoTimeString helper backed by java.time; its formatter
   (yyyy-MM-dd'T'HH:mm:ss.SSSXXX in the system zone) reproduces joda's
   DateTime.toString() output byte for byte where the JDK and joda 2.3
   time-zone databases agree (UTC nodes are unaffected); zones whose
   rules changed after joda's 2013-era tzdb, e.g. Europe/Moscow, now
   render the corrected offset for the same instant.
3. replace DateTime.now() day arithmetic in four test classes with the
   java.time equivalent, ZonedDateTime.now().minusDays(n)/plusDays(n)
   .toInstant().toEpochMilli(), keeping joda's calendar semantics
   one-to-one, and map plain DateTime.now().getMillis() to
   System.currentTimeMillis()
* feat(api): sanitize HTTP API error responses

Standard HTTP error paths used to expose internal details to clients:
Util.processError prefixed every message with the Java exception class
name, several servlets printed raw Throwable.getMessage() directly, and
the two solidity query endpoints returned bare-text error bodies.

Centralize the client-facing text decision in Util.processError:

* keep the raw non-blank message only for the exact runtime types
  JsonFormat.ParseException, ContractValidateException and
  MaintenanceUnavailableException; a null, empty or whitespace-only
  message falls back to "internal server error"
* preserve the events-deprecation message only for the exact
  IllegalArgumentException type carrying EVENTS_DEPRECATED_MSG
* write the fixed rate-limit and INVALID address messages, along with
  existing GetBlock validation messages, through the package-private
  writeAuditedError helper; these audited callers bypass exception
  classification, and printErrorMsg is private to the shared writer
* return {"Error":"internal server error"} for every other exception,
  with no exception class name

Client-visible changes:

* all processError-based error bodies lose the "class <FQCN> : "
  prefix; unclassified raw messages become "internal server error"
* the rate-limit rejection body becomes
  {"Error":"lack of computing resources"} on every endpoint extending
  RateLimiterServlet, including full-node, solidity and PBFT /jsonrpc
* gettransactionbyid / gettransactioninfobyid on solidity return
  standard {"Error":...} JSON instead of bare text
* validateaddress, getBrokerage and getReward replace leaked library
  messages in their failure branches with existing fixed texts; the
  "INVALID address" body is now written via writeAuditedError and loses
  the space after the colon
* getblock keeps its exact error bodies (refactor only)

Cover Solidity transaction and transaction-info GET/POST input errors,
backend failures, successful lookups and missing records directly with
mocked Wallet calls and in-memory requests and responses. Replace the
transaction servlet tests that accidentally exercised POST in both cases,
changed global stdout and used a shared temporary response file.

Verify both endpoint and global rate-limit rejections across the three
JSON-RPC servlet variants, including status, response body and the absence
of business dispatch on rejection.

HTTP status codes, success responses, request validation rules and
gRPC behavior are unchanged. JSON-RPC behavior is unchanged except for
the shared HTTP rate-limit response described above.

Closes tronprotocol#6936

* fix(api): keep server-side failure logging at error level

The previous commit routed four catch-all blocks through the shared
processError entry point, which logs at debug. Those four catches cover
server-side work only: getburntrx, getnodeinfo and getpendingsize read
no request parameters, and in getreward malformed addresses are already
handled by the preceding DecoderException | IllegalArgumentException
catch. Their failures therefore left no trace under the default log
configuration, where the API topic is INFO.

Add a dedicated processServerError entry point that logs at error and
then applies the same sanitization, and use it at those four call sites.
Logging the exception once inside the helper keeps a single record at
any log level, instead of pairing an error log in the servlet with the
debug log in the shared path.

The shared Exception entry point keeps debug on purpose: its callers
also cover request parsing, so an unauthenticated client can fail it
cheaply and repeatedly, and an unconditional stack trace per request
would amplify that into log pressure. Distinguishing client from server
faults on that path is the parameter/internal split tracked as follow-up
in tronprotocol#6936.

Client-facing responses are unchanged.
@317787106 317787106 closed this Sep 16, 2026
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.

2 participants