Skip to content

feat(API): refactor merge http servlets - #6976

Open
SeriousCoding789 wants to merge 2 commits into
tronprotocol:release_v4.8.3from
Little-Peony:refactor_merge_http_servlets
Open

SeriousCoding789 wants to merge 2 commits into
tronprotocol:release_v4.8.3from
Little-Peony:refactor_merge_http_servlets

Conversation

@SeriousCoding789

Copy link
Copy Markdown
Contributor

What does this PR do?

Implements #6922.

develop serves the four HTTP surfaces (FULL, SOLIDITY, PBFT, and the standalone SolidityNode) with parallel sets of servlets and four hand-written registration lists. This PR collapses them onto one servlet per endpoint:

  • Deletes the 100 per-surface servlets (*OnSolidityServlet, *OnPBFTServlet, http/solidity/*SolidityServlet). 98 of them carry no logic at all — the whole class body is walletOnSolidity.futureGet(() -> super.doGet(req, resp)), i.e. a class per endpoint whose only job is to switch the read cursor.
  • Replaces them with a cursor filter (WalletCursorFilter + SolidityCursorFilter / PbftCursorFilter) mounted on /* of the solidity and pbft ports. Switching the cursor is a per-request, thread-level concern; it does not need a subclass per endpoint.
  • Replaces the four registration lists with @HttpApi / @HttpApiExcluded declared on the servlet itself, and derives a read-only HttpApiRegistry from them by classpath scan. Adding an endpoint becomes a one-place change instead of a four-place change.
  • The registry is validated at startup, before Jetty binds. Duplicate or malformed suffixes, a non-READ endpoint on a cursor surface, a servlet declaring neither annotation or both, an endpoint declared on a nested or abstract class, and a missing @Component all fail the node with TronError(API_SERVER_INIT) instead of silently dropping an endpoint.

356 files changed, +2136 / −4751.

Why are these changes required?

Duplicating an endpoint across surfaces is not free — it drifts silently, and two live examples on develop came out of this work:

  • The standalone SolidityNode's own copy of gettransactioninfobyid never picked up the visible=trueconvertLogAddressToTronAddress step the base servlet has, so it returns log[].address in hex where FullNode returns base58.
  • The PBFT registration list has been out of step with the other three surfaces for years: 5 sapling endpoints that were taken off FULL/SOLIDITY in 2020 stayed active on PBFT, and 2 read endpoints the other three surfaces expose were never mounted there.

Both are "change one place, forget the other" bugs. With one servlet per endpoint and a derived registry, a surface can no longer fall behind on its own.

Behaviour differences vs develop

Every per-surface servlet was classified by whether its body contains futureGet: 98 pure cursor delegations (cannot drift) and 2 hand-copied implementations (can). Per-surface result:

Surface Per-endpoint logic Endpoint set
FULL unchanged 120 → 120, no change
SOLIDITY equivalent (delegation → filter) 44 → 44, no change
SOLIDITY_NODE 2 endpoints differ, see below 44 → 44, no change
PBFT equivalent (delegation → filter) 47 → 44, −5 / +2

Client-visible changes, all deliberate and worth a release note:

  1. Standalone SolidityNode /walletsolidity/gettransactioninfobyid — with visible=true on a transaction that has logs, log[].address changes from hex to Tron base58, matching FullNode. This is the drift fix above; visible=false and log-free transactions are unaffected.
  2. PBFT port drops 5 sapling endpointsgetmerkletreevoucherinfo, isspend, scanandmarknotebyivk, scannotebyivk, scannotebyovk now return 404 on /walletpbft/*. They were disabled on every other surface in 2020; PBFT is catching up, not regressing.
  3. PBFT port gains 2 read endpointsgetpaginatednowwitnesslist and gettransactioninfobyblocknum, which FULL / SOLIDITY / SolidityNode already expose. Pure addition.

Error responses on the two former SolidityNode copies now go through Util.processError (the standard {"Error": ...} body) instead of writing the raw exception message into the response.

This PR has been tested by:

  • Unit Tests — 24 new tests, all passing:

    • HttpApiRegistryTest (17). Twelve drive one validation branch each through a fixture package under http/regtest/* and assert the boot failure it produces: a servlet declaring neither annotation or both, an endpoint on a nested or abstract class, a duplicate (surface, suffix), a blank suffix, a / in a suffix, a * or whitespace suffix, a missing @Component, an empty surface list, and a non-READ endpoint on a cursor surface. One builds a valid fixture package. The remaining four are the mount-parity tests: each service's mounted path set equals the registry's derived set for its surface, so an endpoint cannot be declared and left unmounted, or mounted without being declared.
    • CursorFilterInstallationTest (4) runs each service's real addFilter and asserts solidity and pbft install exactly one cursor filter on /*, while the FULL port and the standalone SolidityNode install none — a cursor filter on either of those would take the port off HEAD.
    • WalletCursorFilterTest (3) asserts the cursor is set before chain.doFilter and reset in finally including when the servlet throws, and that the PBFT subclass switches to the PBFT cursor rather than SOLIDITY.

    There is no frozen route snapshot: the registry is validated in full when the class is first touched — before Jetty binds, and for every surface whether or not the node enables it — so the invariants are enforced at boot and the tests drive the failure paths rather than re-asserting them over the live table.

  • Manual Testing — brought up a private chain and checked the mounted endpoint set on every port, including that endpoints marked @HttpApiExcluded are unreachable.

Follow up

  • Grouping the servlet package by function, as raised in the issue discussion. Kept out of this PR so the diff stays a mechanical de-duplication and the endpoint set remains directly diffable; worth doing once this lands.
  • Publishing the per-endpoint inventory and the PBFT surface changelog alongside the release note.

Extra details

@HttpApi / @HttpApiExcluded are deliberately not @Inherited, and the registry reads them with getDeclaredAnnotation only. Inheritable exposure is exactly what produced the 100 wrapper classes this PR removes — a subclass must never silently inherit its parent's surface set.

Replace the hand-maintained servlet wiring across the FullNode, solidity
and PBFT HTTP surfaces with a single registry derived from @httpapi
annotations, and validate it at startup.

- Introduce HttpApiRegistry as the single source of truth for which
  endpoint is mounted on which port, with what access level.
- Drive the FullNode, solidity, SolidityNode and PBFT HTTP services from
  the registry instead of per-service servlet lists.
- Group servlets into a subpackage and flatten the solidity and PBFT
  service packages.
- Serve solidity, SolidityNode and PBFT endpoints with the shared base
  servlets; add cursor filters on the solidity and PBFT ports.
- Fail fast on any registry initialisation error, and align the
  lite-fullnode history gate with the set of endpoints actually mounted.
- Set shielded contract parameter endpoints to BUILD access, and restore
  the shielded transaction endpoints missing from the PBFT port.
- Idle out held connections once maxHttpConnectNumber is reached.

# Conflicts:
#	framework/src/main/java/org/tron/core/services/http/servlets/Util.java
#	framework/src/test/java/org/tron/core/services/http/solidity/GetTransactionByIdSolidityServletTest.java
@Little-Peony
Little-Peony force-pushed the refactor_merge_http_servlets branch from 6516c41 to 2544637 Compare September 17, 2026 07:10
Reconcile the error-sanitizing changes from tronprotocol#6954 with the servlet
package restructure on this branch, which left the test sources
referencing classes that had moved or been removed.

- Move UtilProcessErrorTest alongside Util in the servlets subpackage.
- Import RateLimiterServlet in JsonRpcRateLimiterServletTest, which no
  longer shares a package with it.
- Drop GetTransactionInfoByIdSolidityServletTest: the solidity-specific
  servlet it exercised was removed with the registry refactor, and its
  sibling GetTransactionByIdSolidityServletTest was already deleted in
  the same commit. The sanitized-error behaviour it asserted is covered
  by Util.processError and UtilProcessErrorTest.
}

@Test
public void testFullNodeServiceMountsExactlyTheRegistry() throws Exception {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[SHOULD] Mount-parity tests are self-referential — no frozen route snapshot guards against annotation edits

The four mount-parity tests assert that each service's mounted path set equals the set derived from HttpApiRegistry — but mounting is also driven by that same registry. Expected and actual share one source of truth, so a mistaken @HttpApi edit (wrong surface list, typo'd suffix) silently drops or moves an endpoint and both sides still agree; the suite stays green while the public API surface changes. The PR description acknowledges there is no frozen route snapshot and argues boot-time validation covers it, but validation checks shape (duplicates, READ-on-cursor, annotations present), not membership of the actual endpoint set.

Suggestion: check in a frozen per-surface suffix snapshot (a literal list per surface that a test diffs against the derived registry) so any endpoint-set change requires an explicit, reviewable test update. Fine as a follow-up, but please track it — this is the one guard that catches accidental surface drift.

if (maxHttpConnectNumber > 0) {
this.apiServer.addBean(new ConnectionLimit(maxHttpConnectNumber, this.apiServer));
ConnectionLimit connectionLimit = new ConnectionLimit(maxHttpConnectNumber, this.apiServer);
connectionLimit.setIdleTimeout(CONNECTION_LIMIT_IDLE_TIMEOUT_MS);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[NIT] Undeclared behavior change: 5s idle timeout while the connection limit is reached

This setIdleTimeout(5000) is a runtime behavior change unrelated to the servlet merge: once maxHttpConnectNumber is reached, existing idle connections are now closed after 5s (previously the Jetty default applied). It is not among the client-visible changes declared in the PR description, so it will surprise anyone reviewing the release note or bisecting a connection-churn regression.

Suggestion: declare it in the PR description (and release note) with its rationale, or split it into its own PR — it is small enough to review on its own merits.

@Autowired
public PbftCursorFilter(Manager dbManager) {
super(dbManager, Chainbase.Cursor.PBFT);
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[NIT] Fifth client-visible difference not declared: PBFT-port getBrokerage/getReward now read the PBFT view

On the PBFT port, getBrokerage and getReward were previously mounted as full-node servlets reading the HEAD view; routing everything through this filter means they now answer from the PBFT cursor. For these two endpoints the returned values can legitimately differ from HEAD, so this is a client-visible behavior change beyond the three declared in the PR description (log address format, PBFT −5/+2 endpoints, error format).

Suggestion: add it to the behavior-differences list in the PR description and the release note — operators watching brokerage/reward values on the PBFT port should know the view changed.

* {@link Inherited}, asserted below, so a servlet subclass can never inherit its parent's
* exposure — the failure mode the removed cursor-wrapper subclasses would otherwise reintroduce.
*/
public final class HttpApiRegistry {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[NIT] PBFT endpoint removals need the changelog/endpoint-inventory follow-up to land with the release

(Cross-cutting process note anchored at the registry, which now defines the PBFT surface.)

Five endpoints that were live on /walletpbft/* (the sapling voucher/scan family) now return 404. The PR description flags this as deliberate and lists "publishing the per-endpoint inventory and the PBFT surface changelog" as a follow-up — but a 404 on a previously live endpoint is exactly the kind of change integrators discover by breakage. If the inventory/changelog slips past the release, the removal becomes an undocumented breaking change.

Suggestion: file a tracking issue for the endpoint inventory + PBFT changelog and link it from the PR description, so the follow-up has an owner and a deadline tied to the release.

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface HttpApi {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[NIT] Description & metadata hygiene (4 items rolled up)

Grouped as one comment since all are doc/metadata asks rather than code defects. (Cross-cutting — anchored at the new annotation as a representative added line.)

  1. PR description, behavior-difference item 4 (error format). It says the two former SolidityNode copies now go through Util.processError "instead of writing the raw exception message into the response", but both old copies already used processError at the merge base; the error-format behavior is unchanged. Suggestion: drop or reword that bullet so the declared change list stays accurate.

  2. Branch name refactor_merge_http_servlets. Missing the <type>/ separator used across the repo (feature/, fix/, …). Not worth re-opening the PR over, but worth matching the convention next time.

  3. First commit message body retains a # Conflicts: block. The merge-conflict scaffolding comment will live in git log forever; if the commits are ever rebased or squashed, please drop it.

  4. Validation-timing wording. The description says the registry is validated "before Jetty binds"; precisely, validation is triggered lazily on first touch of the registry class during HttpService.start() (still before bind, as claimed) — a one-line clarification would save future readers a grep.

Suggestion: fix the description bullet and, on the next history rewrite, the commit body; nothing here blocks merge.

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.

[Feature] Deduplicate HTTP servlet stacks with cursor filters and a declarative endpoint registry

4 participants