Skip to content

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

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

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

Conversation

@SeriousCoding789

@SeriousCoding789 SeriousCoding789 commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

What does this PR do?

Implements #6922.

release_v4.8.3 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 98 per-surface servlets (*OnSolidityServlet, *OnPBFTServlet, http/solidity/*SolidityServlet). 96 of them only switch the read cursor — the class body is walletOnSolidity.futureGet(() -> super.doGet(req, resp)), a class per endpoint; the other 2 are the standalone SolidityNode's hand-copied implementations.
  • Selects the read cursor per port instead. The solidity port mounts SolidityCursorFilter on /*. On the PBFT port, RateLimiterServlet selects the PBFT cursor once the request has passed rate limiting: a PBFT cursor is an offset from the live head, so selecting it before an admission that blocks would let reads pass the PBFT-finalized block while the head advances.
  • Keeps rate limiting per port. A shared servlet looks up its per-endpoint rate limiter by the port it serves, under the name the removed per-port class had (GetAccountOnSolidityServlet, GetAccountOnPBFTServlet, …), so existing rate.limiter.http entries keep applying and each port keeps its own quota. reference.conf and config.conf document the names.
  • 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.
  • Validates the registry before Jetty binds. HttpApiRegistry is built and validated the first time a service reads it, while HttpService.start() mounts servlets, and the check covers every surface whether or not the node enables it. Servlet beans are resolved from the Spring context in the same step; the port is opened only afterwards, by apiServer.start(). Duplicate or malformed suffixes, a non-READ endpoint on a cursor surface, a PBFT endpoint that does not extend RateLimiterServlet, 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.

367 files changed, +3006 / −4828.

Why are these changes required?

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

  • The standalone SolidityNode's own copy of gettransactioninfobyid never picked up the visible=true → convertLogAddressToTronAddress 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 the other surfaces 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 release_v4.8.3

Every per-surface servlet was classified by whether its body contains futureGet: 96 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 (cursor filter), except item 4 44 → 44, no change
SOLIDITY_NODE gettransactioninfobyid differs, item 1 44 → 44, no change
PBFT equivalent (cursor selected after rate limiting), except item 4 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 endpoints — getmerkletreevoucherinfo, 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 endpoints — getpaginatednowwitnesslist and gettransactioninfobyblocknum, which FULL / SOLIDITY / SolidityNode already expose. Pure addition.
  4. triggerconstantcontract / estimateenergy POST on the solidity and PBFT ports — the removed wrappers caught an IOException thrown by the servlet and only logged it; it now reaches Jetty, which answers with an error response. The servlets handle every request-processing failure themselves, so only a failing response writer raises it.

Rate limiting is unchanged: every endpoint keeps its limiter name and its own quota on each port.

This PR has been tested by:

  • Unit Tests — 31 tests in new classes and 7 new cases in RateLimiterServletTest, all passing:
    • HttpApiRegistryTest (22):
      • Fourteen 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 class or an abstract class, a duplicate (surface, suffix), a blank suffix, a / in a suffix, a * or whitespace suffix, a missing @Component, an empty surface list, a non-READ endpoint on a cursor surface, and a PBFT endpoint that does not extend RateLimiterServlet. One builds a valid fixture package.
      • Four 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. One more checks that each service tags its context with its surface, which selects the rate limiter and, on PBFT, the cursor.
      • Two independent checks against pre-refactor-routes.txt — the 255 endpoints the four hand-written lists mounted on release_v4.8.3 and the class each list mounted, extracted from those lists and not from the registry. The routes the registry derives must differ from it by exactly the reviewed PBFT delta above, and every endpoint must keep, on each port, the rate-limiter name of the class the old list mounted. An @HttpApi edit that adds, drops or moves an endpoint, or a naming change that orphans a limiter configuration, fails them.
    • RateLimiterServletTest (7 new): per-port limiter names; each port's limiter is built from its own configuration entry; one port's traffic is admitted only by that port's limiter; on PBFT the cursor is selected only after both limiters admit, never when either rejects, and is reset even when the endpoint throws; no other port selects a cursor in the servlet.
    • HttpApiStartupOrderTest (2): a bean-resolution failure while mounting leaves the port unbound; as a control, the same service binds its port once mounting succeeds.
    • CursorFilterInstallationTest (4): the solidity port installs exactly one cursor filter on /*; the FULL, PBFT and standalone SolidityNode ports install none.
    • WalletCursorFilterTest (2) and WalletOnCursorTest (1): the cursor is selected before the servlet runs and always reset.
  • Manual Testing — brought up a private chain and checked the mounted endpoint set on every port, including that the servlets marked @HttpApiExcluded are unreachable, except MetricsServlet, which stays mounted at /monitor/getstatsinfo.

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 98 wrapper classes this PR removes — a subclass must never silently inherit its parent's surface set.

Comment thread framework/src/main/java/org/tron/common/application/HttpService.java Outdated
Comment thread framework/src/main/java/org/tron/core/services/filter/PbftCursorFilter.java Outdated
Comment thread framework/src/main/java/org/tron/core/services/http/HttpApi.java

@waynercheung waynercheung left a comment

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.

Requesting changes for two reproduced regressions:

  • [MUST] PBFT cursor selection now happens before rate-limit admission, which can block, so reads can go beyond the PBFT-finalized height.
  • [MUST] Replacing the wrappers silently ignores legacy limiter configurations and merges per-surface quotas that used to be independent.

Details and suggested fixes are in the two inline comments.

[SHOULD] Before merging, please:

  • add the independent old-route fixture discussed in the existing thread, and the startup-order test promised on #6922;
  • reconcile the PR description and commit message with the current diff:
    • both former SolidityNode copies already used Util.processError at the merge base;
    • MetricsServlet is @HttpApiExcluded but is still reachable at /monitor/getstatsinfo;
    • no fixture covers an endpoint declared on an abstract class;
    • the commit body says the PBFT shielded endpoints are "restored", but they are removed;
  • document the intentional compatibility changes, including the IOException handling of triggerconstantcontract / estimateenergy on SOLIDITY/PBFT.

Validation: the PR's 24 new tests and 30 related existing tests pass locally. The two additional compatibility tests that reproduce the issues above fail consistently, and the control test with the old cursor order passes.

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 remove
  the five shielded endpoints that only the PBFT port still mounted.
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.
The mount-parity tests compare each service's mounts with the registry
that also drives the mounting, so an @httpapi edit that drops or moves
an endpoint keeps them green.

Add pre-refactor-routes.txt, the routes the four hand-written
registration lists mounted on release_v4.8.3, extracted from those
lists rather than from the registry. HttpApiRegistryTest now requires
the derived registry to differ from it by exactly the reviewed PBFT
delta: five shielded endpoints removed, two read endpoints added.
HttpApiRegistry no longer derives an audit matrix; the registry itself
is the only view derived from @httpapi.
Two regressions from replacing the per-surface wrapper servlets:

- Rate limiting: RateLimiterServlet keys its limiters by class name, so
  the shared servlets merged the quotas of the fullnode, solidity and
  PBFT ports and ignored *OnSolidityServlet / *OnPBFTServlet entries.
  Each service now tags its context with its surface, and a servlet
  builds and looks up one limiter per surface under the name the
  removed class had: <Name>OnSolidityServlet, <Name>OnPBFTServlet, and
  <Name>SolidityServlet for the two SolidityNode copies. reference.conf
  and config.conf document the names.
- PBFT cursor: a PBFT cursor is an offset from the live head, and the
  cursor filter selected it before a rate-limit admission that can
  block, so a waiting request could read past the PBFT-finalized block.
  RateLimiterServlet now selects it after both limiters admit, and the
  PBFT filter is removed; the registry requires PBFT endpoints to
  extend RateLimiterServlet. The solidity filter stays, since that view
  is resolved from the live head on every read.

pre-refactor-routes.txt records the class each old list mounted, and
HttpApiRegistryTest checks that every endpoint keeps that limiter name.
The registry is validated and every servlet bean resolved while a
service mounts its servlets, before the port is bound. Pin that order:
a bean-resolution failure while mounting leaves the port free, and the
same service binds it once mounting succeeds.
MetricsServlet is @HttpApiExcluded and stays mounted at
/monitor/getstatsinfo; say in the annotation's javadoc that excluded
means not registered through HttpApiRegistry, not unreachable.
@Little-Peony
Little-Peony force-pushed the refactor_merge_http_servlets branch from 4c759f5 to c1cc0a4 Compare September 29, 2026 10:25
@SeriousCoding789

Copy link
Copy Markdown
Contributor Author

Requesting changes for two reproduced regressions:

  • [MUST] PBFT cursor selection now happens before rate-limit admission, which can block, so reads can go beyond the PBFT-finalized height.
  • [MUST] Replacing the wrappers silently ignores legacy limiter configurations and merges per-surface quotas that used to be independent.

Details and suggested fixes are in the two inline comments.

[SHOULD] Before merging, please:

  • add the independent old-route fixture discussed in the existing thread, and the startup-order test promised on [Feature] Deduplicate HTTP servlet stacks with cursor filters and a declarative endpoint registry #6922;

  • reconcile the PR description and commit message with the current diff:

    • both former SolidityNode copies already used Util.processError at the merge base;
    • MetricsServlet is @HttpApiExcluded but is still reachable at /monitor/getstatsinfo;
    • no fixture covers an endpoint declared on an abstract class;
    • the commit body says the PBFT shielded endpoints are "restored", but they are removed;
  • document the intentional compatibility changes, including the IOException handling of triggerconstantcontract / estimateenergy on SOLIDITY/PBFT.

Validation: the PR's 24 new tests and 30 related existing tests pass locally. The two additional compatibility tests that reproduce the issues above fail consistently, and the control test with the old cursor order passes.

Thanks for the careful review and for reproducing both issues. Both MUST items are fixed in f78a695; the SHOULD items are below. The branch was force-pushed to correct the first commit's message; the content of the earlier commits is unchanged.

[MUST] PBFT cursor selected before rate-limit admission

The PBFT port no longer installs a cursor filter. RateLimiterServlet#service now selects the PBFT cursor only after both the per-endpoint and the global limiter admit the request, and resets it when the endpoint returns or throws. Each service tags its context with its surface, which is how the servlet knows a request is on PBFT, and the registry rejects a PBFT endpoint that does not extend RateLimiterServlet. The solidity filter stays: head.getSolidity() is resolved from the live head on every read, so selecting it before admission cannot move a read past the solidified block.

RateLimiterServletTest pins the order (endpoint limiter → global limiter → cursor → endpoint → reset), that no cursor is selected when either limiter rejects, and the reset when the endpoint throws; CursorFilterInstallationTest pins that the PBFT port installs no cursor filter.

[MUST] Per-surface rate-limit identity

Kept the existing behaviour. A servlet builds and looks up one limiter per surface, under the name the removed class had: <Name>Servlet on FULL, <Name>OnSolidityServlet on SOLIDITY, <Name>OnPBFTServlet on PBFT, and <Name>Servlet on the standalone SolidityNode except GetTransactionByIdSolidityServlet / GetTransactionInfoByIdSolidityServlet. Existing entries such as GetAccountOnPBFTServlet apply again, and each port keeps its own quota, including the default limiter built from rate.limiter.global.api.qps.

Every per-surface class the old lists mounted follows this naming. pre-refactor-routes.txt now also records the class each list mounted, and HttpApiRegistryTest checks that every endpoint still gets that limiter name on every surface. RateLimiterServletTest covers the config lookup and that a request is admitted only by its own port's limiter.

SHOULD items

  • Independent old-route fixture: pre-refactor-routes.txt (0c21d25) is extracted from the four hand-written lists on release_v4.8.3, and the derived registry must differ from it by exactly the PBFT −5 / +2 delta.
  • Startup-order test: HttpApiStartupOrderTest (0d7e8c9). A bean-resolution failure while mounting leaves the port unbound; as a control, the same service binds its port once mounting succeeds.
  • Description and commit message: the Util.processError bullet is removed; the description now says MetricsServlet stays mounted at /monitor/getstatsinfo, and the @HttpApiExcluded javadoc says excluded means not registered rather than unreachable (c1cc0a4); an abstract-class fixture is added (cb7f914); the first commit's body now says the PBFT shielded endpoints are removed.
  • Compatibility: the description lists the IOException handling of triggerconstantcontract / estimateenergy on SOLIDITY / PBFT, and notes that rate limiting is unchanged.

- RateLimiterServlet: a servlet mounted on a surface its @httpapi does
  not declare has no limiter under that surface's name, and a missing
  limiter skipped per-endpoint limiting; fall back to the class-name
  limiter instead.
- RateLimiterServletJettyTest: one servlet instance mounted in a tagged
  PBFT context and an untagged one, driven by real requests through
  jetty: the PBFT request uses the PBFT limiter and selects the cursor,
  the other uses the class-name limiter and no cursor.
- GetTransactionByIdServletTest and
  GetTransactionInfoByIdServletResponseTest: the response cases of the
  removed SolidityNode copies, run against the base servlets that now
  serve those endpoints on every surface.
* release_v4.8.3:
  refactor(framework): decouple Manager from TronJsonRpcImpl (tronprotocol#6990)
  refactor(api): replace response copy wrappers with Jetty content count (tronprotocol#6982)
  update a new version. version name:GreatVoyage-v4.8.2.2-1-gf3e81404fe,version code:18830 (tronprotocol#7008)
  feat: improve node stability and execution efficiency (tronprotocol#7007)
  chore(p2p): internalize libp2p v2.2.9 as a local `p2p` module (tronprotocol#6992)
  refactor(vm): remove unreachable trace compression path (tronprotocol#6997)
  fix(test): isolate cross-test state leaks and stabilize flaky suites (tronprotocol#6974)
  refactor(config): improve startup errors and remove inactive assertions (tronprotocol#6960)
  ci: optimize pull request checks (tronprotocol#6938)
  fix(config): remove inactive RocksDB options (tronprotocol#6944)

# Conflicts:
#	framework/src/main/java/org/tron/core/services/http/servlets/CreateShieldedTransactionWithoutSpendAuthSigServlet.java

@halibobo1205 halibobo1205 left a comment

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.

[MUST] Please check CI status and fix it.

@waynercheung waynercheung left a comment

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.

[MUST] Complete the test package migration and restore CI before merge.

At 483d44c532, :framework:compileTestJava fails with 35 compilation errors; I reproduced this locally. The release_v4.8.3 merge brought in three tests that remain in org.tron.core.services.http, while the classes they use have moved to org.tron.core.services.http.servlets:

  • JsonFormatIdentifierTest
  • JsonFormatUnicodeErrorTest
  • OutboundJsonTest

Please move these tests to org.tron.core.services.http.servlets, updating both the package declarations and the source paths. Updating imports alone is insufficient: JsonFormatUnicodeErrorTest accesses package-private members of JsonFormat (unescapeText, InvalidEscapeSequence) and the protected nested Tokenizer.

After applying only this package move in a local copy, the targeted tests covering both earlier MUST fixes pass. Please rerun the affected tests and the required CI checks on the updated HEAD; I'll complete the final re-review once those checks pass.

The release_v4.8.3 merge added JsonFormatIdentifierTest,
JsonFormatUnicodeErrorTest and OutboundJsonTest under
org.tron.core.services.http, while JsonFormat, Util and the servlets
they exercise live in org.tron.core.services.http.servlets. Move the
tests next to those classes so their package-private access compiles.
* release_v4.8.3:
  refactor(api): remove dead WalletExtension gRPC service and config (tronprotocol#6975)
@SeriousCoding789

Copy link
Copy Markdown
Contributor Author

@halibobo1205 @waynercheung thanks fixed it late.

@waynercheung waynercheung left a comment

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.

Re-reviewed at 46b5be8838.

The test package migration is complete, and both earlier MUST items are addressed: PBFT cursor selection happens after both rate limiters admit the request, with cleanup on exceptional paths; legacy limiter names and per-surface quotas are preserved.

I ran 110 relevant tests against the unmodified HEAD and five additional regression tests covering the production cursor calculation and actual limiter permit isolation. All passed. The PR's own tests also caught each of 15 mutations to the cursor ordering, per-surface limiter names, surface tagging and the registry PBFT check. I also independently checked the route baseline and legacy limiter names against the current target branch. Current CI checks are green.

[NIT] The rate.limiter.http comments in reference.conf / config.conf describe the FullNode, Solidity and SolidityNode naming conventions but omit PBFT. Please also document <Name>OnPBFTServlet, e.g. GetAccountOnPBFTServlet for /walletpbft/getaccount.

No remaining blocking findings from this review. Approved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

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

5 participants