feat(API): refactor merge http servlets - #6976
SeriousCoding789 wants to merge 2 commits into
Conversation
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
6516c41 to
2544637
Compare
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 { |
There was a problem hiding this comment.
[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); |
There was a problem hiding this comment.
[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); | ||
| } |
There was a problem hiding this comment.
[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 { |
There was a problem hiding this comment.
[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 { |
There was a problem hiding this comment.
[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.)
-
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 usedprocessErrorat the merge base; the error-format behavior is unchanged. Suggestion: drop or reword that bullet so the declared change list stays accurate. -
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. -
First commit message body retains a
# Conflicts:block. The merge-conflict scaffolding comment will live ingit logforever; if the commits are ever rebased or squashed, please drop it. -
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.
What does this PR do?
Implements #6922.
developserves 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:*OnSolidityServlet,*OnPBFTServlet,http/solidity/*SolidityServlet). 98 of them carry no logic at all — the whole class body iswalletOnSolidity.futureGet(() -> super.doGet(req, resp)), i.e. a class per endpoint whose only job is to switch the read cursor.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.@HttpApi/@HttpApiExcludeddeclared on the servlet itself, and derives a read-onlyHttpApiRegistryfrom them by classpath scan. Adding an endpoint becomes a one-place change instead of a four-place change.READendpoint on a cursor surface, a servlet declaring neither annotation or both, an endpoint declared on a nested or abstract class, and a missing@Componentall fail the node withTronError(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
developcame out of this work:gettransactioninfobyidnever picked up thevisible=true→convertLogAddressToTronAddressstep the base servlet has, so it returnslog[].addressin hex where FullNode returns base58.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
developEvery 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:Client-visible changes, all deliberate and worth a release note:
/walletsolidity/gettransactioninfobyid— withvisible=trueon a transaction that has logs,log[].addresschanges from hex to Tron base58, matching FullNode. This is the drift fix above;visible=falseand log-free transactions are unaffected.getmerkletreevoucherinfo,isspend,scanandmarknotebyivk,scannotebyivk,scannotebyovknow return 404 on/walletpbft/*. They were disabled on every other surface in 2020; PBFT is catching up, not regressing.getpaginatednowwitnesslistandgettransactioninfobyblocknum, 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 underhttp/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-READendpoint 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 realaddFilterand 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 beforechain.doFilterand reset infinallyincluding 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
@HttpApiExcludedare unreachable.Follow up
Extra details
@HttpApi/@HttpApiExcludedare deliberately not@Inherited, and the registry reads them withgetDeclaredAnnotationonly. Inheritable exposure is exactly what produced the 100 wrapper classes this PR removes — a subclass must never silently inherit its parent's surface set.