chore(p2p): internalize libp2p v2.2.9 as a local p2p module - #14
chore(p2p): internalize libp2p v2.2.9 as a local p2p module#14barbatos2011 wants to merge 21 commits into
p2p module#14Conversation
Vendors tronprotocol/libp2p tag v2.2.9 (c564f263d310d7a964035d3b597634aba6bda86d) into a local `p2p` Gradle module, ahead of switching `common` off the external io.github.tronprotocol:libp2p Maven artifact. The source in this commit is byte-identical to `git archive v2.2.9 src/main`, so a reviewer can diff it directly against the upstream tag and confirm nothing was altered on the way in. Everything we change about it lands in the next commit, separately and for exactly that reason. Two arrangements differ from the upstream layout: - The example code moves out of src/main into its own `example` sourceSet. It still compiles, so an API change in main surfaces here too, but it is not packaged into p2p.jar and is not run as tests. - Generated protobuf sources are gitignored and rebuilt by :p2p:generateProto, so they stay out of the diff. p2p tracks rootProject.grpcVersion rather than pinning libp2p's own gRPC version, so the module cannot drift from the Netty the rest of the build resolves.
Four mechanical rewrites plus formatting, applied on top of the pristine v2.2.9 source added in the previous commit. Kept separate so commit 1 stays diffable against the upstream tag. - log. -> logger. (158 call sites). The root lombok.config sets lombok.log.fieldName=logger, so @slf4j generates `logger`, not `log`. - Math. -> StrictMath. (6 sites). CI enforces a check-math rule that rejects java.lang.Math anywhere in the tree, to keep arithmetic deterministic across JVMs and architectures. - BasicThreadFactory.builder() -> new BasicThreadFactory.Builder() (13 sites). builder() needs commons-lang3 3.12+; the project pins 3.4 globally. Rewriting to the 3.0 API keeps that pin rather than forcing a silent global upgrade. - toLowerCase()/toUpperCase() -> Locale.ROOT (4 sites). The root build enables errorprone StringCaseLocaleUsage as ERROR on every subproject except protocol and errorprone, so this is compile-forced. It is the one change here that is not purely cosmetic: behaviour is identical for ASCII but differs under a Turkish locale. Matches the project's own idiom in Args.java:1273. Formatting: google-java-format over the 9 vendored org/web3j/** files, which came in AOSP 4-space style, plus import reordering and hand fixes for the remainder. This takes :p2p:checkstyleMain from 605 violations to 0. No functional change beyond the Locale.ROOT note above.
Replaces io.github.tronprotocol:libp2p:2.2.9 with `api project(":p2p")`,
collapsing 17 lines of dependency plus excludes into one.
The dom4j exclusion tail (jaxen, stax-api, msv, xsdlib, relaxngDatatype,
pull-parser, xpp3) that used to sit on the libp2p dependency here does not
disappear: it arrives via the Aliyun and Route53 SDKs, which are now p2p's own
dependencies. The exclusions move with them, into a configurations.configureEach
block in p2p/build.gradle. Dropping them would silently re-admit artifacts the
project has excluded for years.
Removing a dependency also removes it as a version requester, so every version
the libp2p POM declared was checked against what :p2p now declares. All twelve
match, except two deliberate differences:
- commons-lang3: libp2p declared 3.18.0 at runtime scope, which won conflict
resolution against the root's 3.4 and put 3.18.0 on :framework:runtimeClasspath.
:p2p now declares 3.18.0 for the same reason. Pinning the root's 3.4 here --
which is what the module needs to *compile*, since the source uses the
3.0-compatible `new BasicThreadFactory.Builder()` -- would have shipped a 2015
release and reintroduced CVE-2025-48924.
- grpc-netty: libp2p pinned 1.81.0; :p2p tracks rootProject.grpcVersion (1.83.0)
so it cannot drift from the Netty the rest of the build resolves.
Adding a project to the dependency graph also needs three task-dependency edges
that an external jar did not, each of which Gradle reported as an
implicit_dependency and answered by disabling execution optimizations:
- framework's buildFullNodeJar and plugins' binaryRelease both zip up
runtimeClasspath and maintain a hand-written dependsOn list of project jars.
:common now exposes p2p via `api`, so p2p-1.0.0.jar is on both classpaths;
without the edge a parallel build could assemble the shipped fat jar before
:p2p:jar exists.
- p2p's own processExampleResources reads src/example/resources, which the
protobuf plugin claims as an output of generateExampleProto because
generatedFilesBaseDir points at $projectDir/src.
verification-metadata.xml gains three components that resolve once p2p compiles
in-tree: bcutil-jdk18on:1.84, gson:2.9.0 and gson-parent:2.9.0. Checksums were
taken from Maven Central and cross-checked against the published .sha1.
gson 2.9.0 is older than the 2.14.0 used elsewhere, and that is fine: it only
appears on :p2p's isolated compile classpath. :framework's runtimeClasspath
still resolves gson:2.9.0 -> 2.14.0.
Verified with :framework:dependencies and :framework:dependencyInsight on
runtimeClasspath: `project :p2p` present, no external libp2p artifact, gson at
2.14.0 and commons-lang3 at 3.18.0 -- the same versions the node shipped before.
A full build reports zero implicit_dependency warnings.
Ports all 23 of v2.2.9's test files to framework/src/test/java, following the project-wide convention already used by actuator, chainbase, consensus and common. Package names are preserved. None of the four rewrites from the previous commit applied: the test sources use none of those patterns. Build wiring: - The AWS Route53 and Aliyun SDKs plus dnsjava are added as testImplementation. They are implementation-scope in :p2p and so are not transitively visible here, but the DNS tests need them. - Those SDKs drag in the same dom4j tail that :p2p excludes module-wide. Without mirroring the exclusions, dependency verification fails on 9 artifacts. They are mirrored scoped to test configurations only, leaving the main runtime classpath untouched. - :framework:jacocoTestReport now includes p2p's class and source dirs. :p2p has no test sourceSet, so :p2p:jacocoTestReport emits nothing, and this report covered framework's classes only, leaving p2p at zero packages despite being exercised by these tests. Generated protobuf code is excluded, matching the checkstyle exclusion. Moving these into framework's test JVM changes their isolation requirements: the task uses forkEvery = 100, so up to 100 classes share a process, whereas in libp2p's own module each ran alone (and its CI never ran tests at all). Three places leaked process-wide state and now clean up after themselves: - ConnPoolServiceTest and SocketTest call ChannelManager.close(), which latches a static isShutdown that init() never clears. Left set, every later test that starts p2p gets a PeerClient whose connect() returns null and a ConnPoolService that skips reconnection -- an order-dependent failure that is painful to diagnose. Both teardowns reset it. - HandshakeServiceTest saves and restores Parameter.handlerList instead of clearing it, since that is the registry P2pService.register() appends to. - DnsManagerTest restores DnsManager's statics rather than leaving them pointing at mocks from a finished class. Four upstream tests were unreliable by construction rather than merely flaky: - NetUtilTest.testGetIP called three public IP-echo services and asserted all three returned the same string. That needs the network and assumes a single egress address. It now runs against a loopback HttpServer, which exercises the same fetch/parse/validate path deterministically and reaches the rejection branches too. - NetUtilTest.testGetLanIP compared getLanIP() against the source address the kernel picks for a socket to www.baidu.com. Those are different definitions -- interface enumeration versus the routing table -- and disagree on any multi-homed host. It now asserts the contract getLanIP() actually has, and needs no network. - NetUtilTest.testExternalIp dereferenced a result that is null when every IP-echo service fails. It now assumes a result before asserting the address is routable. - ConnPoolServiceTest.getNodes_orderByUpdateTimeDesc asserted the returned list was ordered by updateTime. getNodes() sorts, truncates to max(limit * 10, 50) candidates, then calls Collections.shuffle() -- with two nodes that assertion is a coin flip. It now asserts membership, and a new test covers the descending sort where it is observable: above the candidate bound. NodeHandlerTest also loses an unused org.checkerframework import that does not resolve on this classpath. Eight test classes are added for code the upstream suite did not reach: ByteArray, PublishService config validation and static-node publishing, the varint32 frame decoder that fronts every channel pipeline, AwsClient's change computation, AliClient's request/retry/pagination logic, HandshakeService's accept and reject branches, DnsManager's node filtering, Channel's value semantics, and the DisconnectCode to DisconnectReason mapping. Where a collaborator is genuinely external -- the Aliyun SDK, process-wide ChannelManager state -- it is mocked, so the logic under test is real and only the transport is faked. This takes p2p from 35% line coverage on the upstream tests alone to 60.90% (2142/3517), clearing the >60% changed-line gate. What is still uncovered needs a live connection: ConnPoolService.onConnect/onDisconnect/onMessage, NodeDetectService, PeerClient, Channel.init/send. Upstream's SocketTest for exactly that is entirely commented out, so it would take integration tests with real channels rather than more unit tests. Two pre-existing libp2p defects surfaced while writing these and are reported in the PR description rather than fixed here, since this PR claims no functional change: RootEntry.java:67 and Algorithm.java:121 both throw unchecked exceptions that escape a catch(DnsException) written to tolerate unparseable input, so one malformed TXT record aborts the whole publish or collection.
There was a problem hiding this comment.
34 issues found across 136 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="p2p/src/main/java/org/tron/p2p/discover/NodeManager.java">
<violation number="1" location="p2p/src/main/java/org/tron/p2p/discover/NodeManager.java:14">
P2: When `init()` is called again, this assignment abandons the running `KadService` while its executors and server remain active. Stop the existing discovery manager before replacing it; otherwise the new server cannot bind the UDP port and the new service cannot send discovery messages.</violation>
</file>
<file name="p2p/src/main/java/org/tron/p2p/connection/socket/PeerServer.java">
<violation number="1" location="p2p/src/main/java/org/tron/p2p/connection/socket/PeerServer.java:29">
P1: When shutdown runs while the asynchronous `start(port)` is still binding, this condition skips the close. The start thread then binds and waits on `closeFuture` indefinitely, leaving the listener and Netty threads active after `ChannelManager.close()` returns. Track shutdown state with proper synchronization, and have startup abort or close the channel when shutdown has already begun.</violation>
</file>
<file name="p2p/src/main/java/org/tron/p2p/dns/sync/RandomIterator.java">
<violation number="1" location="p2p/src/main/java/org/tron/p2p/dns/sync/RandomIterator.java:66">
P2: After `hasNext()` returns true, a normal `iterator.next()` performs a second DNS sync and discards the node prefetched into `cur`; it can even return `null` immediately after `hasNext()` returned true. Cache the look-ahead result and have `next()` consume it, or remove the `Iterator` implementation.</violation>
<violation number="2" location="p2p/src/main/java/org/tron/p2p/dns/sync/RandomIterator.java:90">
P2: When the iterator has no valid tree URLs, `pickTree()` calls `random.nextInt(0)` and every `next()` fails with `IllegalArgumentException`. Return `null` before selecting a random index when `clientTrees` is empty.</violation>
</file>
<file name="p2p/src/main/java/org/tron/p2p/discover/socket/DiscoverServer.java">
<violation number="1" location="p2p/src/main/java/org/tron/p2p/discover/socket/DiscoverServer.java:72">
P1: When `close()` runs before this asynchronous bind completes, it sees `channel == null` and returns after setting `shutdown`; this bind then still succeeds and waits indefinitely. Coordinate channel publication with shutdown, and close a channel bound after shutdown is requested or wait for the startup thread.</violation>
</file>
<file name="p2p/src/main/java/org/tron/p2p/dns/sync/SubtreeSync.java">
<violation number="1" location="p2p/src/main/java/org/tron/p2p/dns/sync/SubtreeSync.java:57">
P1: When a subtree TXT lookup temporarily returns no record, `resolveAll` discards that hash and marks the subtree complete. `ClientTree` then skips it while the root sequence is unchanged; throw on null before polling so the next sync retries it.</violation>
</file>
<file name="p2p/src/main/java/org/tron/p2p/dns/update/AliClient.java">
<violation number="1" location="p2p/src/main/java/org/tron/p2p/dns/update/AliClient.java:199">
P1: When a stale-record deletion fails after retries, `submitChanges` still reports publication success and leaves the old DNS record active. Check the return value and propagate a deletion failure before logging success.</violation>
</file>
<file name="p2p/src/main/java/org/tron/p2p/dns/tree/Algorithm.java">
<violation number="1" location="p2p/src/main/java/org/tron/p2p/dns/tree/Algorithm.java:31">
P1: When the public key’s X coordinate starts with a zero nibble, this slice drops that nibble and includes part of Y, so DNS tree publication produces an unusable public key. Left-pad the full public-key value to 128 hex characters before extracting X.</violation>
</file>
<file name="p2p/src/main/java/org/tron/p2p/utils/NetUtil.java">
<violation number="1" location="p2p/src/main/java/org/tron/p2p/utils/NetUtil.java:80">
P1: An advertised endpoint with a port outside 1–65535 passes `validNode`, then can throw `IllegalArgumentException` when discovery handles its socket address. Reject invalid ports in `validNode` before accepting the message.</violation>
<violation number="2" location="p2p/src/main/java/org/tron/p2p/utils/NetUtil.java:227">
P2: When a seed hostname is configured, `parseInetSocketAddress` performs a blocking JVM DNS lookup before `InetUtil` can submit its parallel, bounded lookups. Parse hostnames without resolving them here, or move hostname resolution entirely into `InetUtil`.</violation>
</file>
<file name="p2p/src/main/java/org/tron/p2p/dns/DnsNode.java">
<violation number="1" location="p2p/src/main/java/org/tron/p2p/dns/DnsNode.java:29">
P2: When a `DnsNode` is constructed with an ID, this call discards it, so `compress` omits `nodeId` and decompressed DNS entries cannot preserve peer identity. Pass the constructor's `id` to `Node`.</violation>
</file>
<file name="p2p/src/main/java/org/tron/p2p/discover/protocol/kad/NodeHandler.java">
<violation number="1" location="p2p/src/main/java/org/tron/p2p/discover/protocol/kad/NodeHandler.java:65">
P1: When an incumbent answers its eviction challenge, the replacement handler remains `ALIVE` without table membership and can never become active. Explicitly reject `replaceCandidate` when the incumbent survives.</violation>
<violation number="2" location="p2p/src/main/java/org/tron/p2p/discover/protocol/kad/NodeHandler.java:106">
P2: When an active peer fails the compatibility check, this transition marks its handler dead but leaves the peer in the routing table. Remove active entries when transitioning to `DEAD`, or keep the handler active until the table entry is removed.</violation>
<violation number="3" location="p2p/src/main/java/org/tron/p2p/discover/protocol/kad/NodeHandler.java:120">
P2: After a handler exhausts its initial ping retries, a later successful pong does not restore its retry budget. Reset `pingTrials` when a pong is accepted so recovered nodes receive the normal challenge budget.</violation>
</file>
<file name="p2p/src/main/java/org/tron/p2p/dns/tree/BranchEntry.java">
<violation number="1" location="p2p/src/main/java/org/tron/p2p/dns/tree/BranchEntry.java:24">
P2: When a DNS branch TXT value contains an empty or non-hash child, `parseEntry` accepts it and `SubtreeSync` queues it for resolution, so malformed publisher data can abort synchronization or silently omit leaves. Validate every child with the fixed-length base32 hash rules and reject the branch before returning it.</violation>
</file>
<file name="p2p/src/main/java/org/tron/p2p/utils/CollectionUtils.java">
<violation number="1" location="p2p/src/main/java/org/tron/p2p/utils/CollectionUtils.java:13">
P2: When `limit` is zero and `items` is non-empty, this loop returns every item instead of truncating to zero. Return an empty list before iterating when `limit == 0`.</violation>
</file>
<file name="p2p/src/main/java/org/tron/p2p/connection/socket/P2pChannelInitializer.java">
<violation number="1" location="p2p/src/main/java/org/tron/p2p/connection/socket/P2pChannelInitializer.java:58">
P2: When channel initialization throws, this catch logs and returns while leaving the Netty channel open with a partial pipeline. Close `ch` or rethrow the failure so Netty cannot retain an unusable peer connection.</violation>
</file>
<file name="p2p/src/main/java/org/tron/p2p/discover/socket/MessageHandler.java">
<violation number="1" location="p2p/src/main/java/org/tron/p2p/discover/socket/MessageHandler.java:65">
P1: When a remote UDP packet causes `eventHandler.handleEvent` to throw, this closes the shared UDP listener and takes discovery offline until `DiscoverServer` restarts it. Repeated packets can keep causing this outage; handle per-datagram failures without closing the listener.</violation>
</file>
<file name="p2p/src/main/java/org/tron/p2p/discover/protocol/kad/table/NodeBucket.java">
<violation number="1" location="p2p/src/main/java/org/tron/p2p/discover/protocol/kad/table/NodeBucket.java:34">
P1: When a bucket is full, `getLastSeen()` selects the newest entry instead of the least-recently-seen entry. Challenge the oldest entry (`sorted.get(sorted.size() - 1)`) so active peers are retained and stale peers are eligible for replacement.</violation>
</file>
<file name="p2p/src/main/java/org/tron/p2p/connection/ChannelManager.java">
<violation number="1" location="p2p/src/main/java/org/tron/p2p/connection/ChannelManager.java:66">
P2: After `close()` sets `isShutdown`, a later `init()` leaves it true, so `PeerClient.connectAsync()` returns null and the connection pool skips reconnection. Reset `isShutdown` during initialization.</violation>
<violation number="2" location="p2p/src/main/java/org/tron/p2p/connection/ChannelManager.java:94">
P1: When an old connection closes after a replacement to the same remote address is admitted, this removal deletes the replacement from `channels`. Remove the entry only when its mapped value is this `channel`.</violation>
</file>
<file name="p2p/src/main/java/org/tron/p2p/dns/sync/ClientTree.java">
<violation number="1" location="p2p/src/main/java/org/tron/p2p/dns/sync/ClientTree.java:113">
P1: When a referenced TXT record is temporarily unavailable, this line permanently drops its hash from `linkSync.missing` and treats the signed tree as complete. Keep unresolved hashes queued or fail the sync, and apply the same handling to the ENR removal in `syncNextRandomNode` so transient DNS propagation does not produce an incomplete node set.</violation>
</file>
<file name="p2p/src/main/java/org/tron/p2p/discover/protocol/kad/table/NodeEntry.java">
<violation number="1" location="p2p/src/main/java/org/tron/p2p/discover/protocol/kad/table/NodeEntry.java:14">
P2: When an existing handler learns a different node ID, this cached distance remains based on the old ID, so `NodeTable` keeps the peer in the wrong Kademlia bucket and may evict or fail-find the wrong peers. Recompute the distance from the current node ID, or update/rebucket the entry whenever the node ID changes.</violation>
</file>
<file name="p2p/src/main/java/org/tron/p2p/dns/update/PublishService.java">
<violation number="1" location="p2p/src/main/java/org/tron/p2p/dns/update/PublishService.java:138">
P1: When DNS publishing is enabled without `dnsPrivate`, `checkConfig` still accepts the configuration and `Tree.makeTree` skips signing, so the publisher emits a `tree://null@...`/unsigned tree. Reject missing private keys before starting the publisher.</violation>
</file>
<file name="p2p/src/main/java/org/tron/p2p/dns/tree/RootEntry.java">
<violation number="1" location="p2p/src/main/java/org/tron/p2p/dns/tree/RootEntry.java:67">
P1: When a root TXT value is shorter than `rootPrefix`, this line throws `StringIndexOutOfBoundsException`; prefixless values are also parsed as roots. `AwsClient.computeChanges` catches only `DnsException`, so malformed existing root data aborts publish; validate the prefix and length first.</violation>
<violation number="2" location="p2p/src/main/java/org/tron/p2p/dns/tree/RootEntry.java:70">
P2: When the outer root payload or embedded signature contains malformed Base64, `Algorithm.decode64` throws unchecked `IllegalArgumentException` instead of the declared `DnsException`. Catch decoder failures and convert them to the appropriate root or signature parse error so corrupt DNS data cannot escape root resolution and publishing paths.</violation>
</file>
<file name="p2p/src/main/java/org/tron/p2p/connection/business/keepalive/KeepAliveService.java">
<violation number="1" location="p2p/src/main/java/org/tron/p2p/connection/business/keepalive/KeepAliveService.java:40">
P1: When a peer answers before the following assignments run, `processMessage` clears `waitForPong`, then this code sets it back to true. Record `pingSent` and mark `waitForPong` before sending the ping, otherwise a healthy peer can be disconnected after 20 seconds.</violation>
</file>
<file name="p2p/src/main/java/org/tron/p2p/discover/protocol/kad/table/NodeTable.java">
<violation number="1" location="p2p/src/main/java/org/tron/p2p/discover/protocol/kad/table/NodeTable.java:115">
P2: When more than 16 peers share a distance bucket, `DistanceComparator` returns zero for all of them, so this sort preserves arbitrary `HashMap` order. The following truncation can omit XOR-closer peers from `getClosestNodes`; sort by the full XOR distance before limiting the response.</violation>
</file>
<file name="p2p/src/main/java/org/tron/p2p/discover/Node.java">
<violation number="1" location="p2p/src/main/java/org/tron/p2p/discover/Node.java:155">
P1: When a peer keeps the same ID but advertises a different endpoint, equal `Node` objects produce different hashes, so `HashSet` lookups and deduplication fail. Hash the same byte-array identity used by `equals`.</violation>
<violation number="2" location="p2p/src/main/java/org/tron/p2p/discover/Node.java:169">
P1: When two peer IDs contain different invalid UTF-8 byte sequences, `getIdString()` can make them equal and the node table can treat distinct peers as the same node. Compare the ID byte arrays directly with `Arrays.equals` instead of decoding them to `String`.</violation>
</file>
<file name="p2p/src/main/java/org/tron/p2p/dns/update/AwsClient.java">
<violation number="1" location="p2p/src/main/java/org/tron/p2p/dns/update/AwsClient.java:99">
P1: When the zone ID is omitted and nested Route53 zones exist, `findZoneID` can select the parent zone because it returns the first suffix match. Keep the longest matching hosted-zone name, or require an explicit zone ID.</violation>
</file>
<file name="p2p/src/main/java/org/web3j/utils/Numeric.java">
<violation number="1" location="p2p/src/main/java/org/web3j/utils/Numeric.java:43">
P2: When `decodeQuantity` receives a negative or unprefixed value, it returns it instead of rejecting it. Validate the quantity digits and require the `0x` form before parsing.</violation>
<violation number="2" location="p2p/src/main/java/org/web3j/utils/Numeric.java:213">
P2: When `hexStringToByteArray` receives a non-hex character, it silently transforms the input into bytes, so `Hash.sha3(String)` hashes the wrong data. Reject either nibble before constructing each byte.</violation>
</file>
<file name="p2p/src/main/java/org/tron/p2p/connection/socket/PeerClient.java">
<violation number="1" location="p2p/src/main/java/org/tron/p2p/connection/socket/PeerClient.java:38">
P2: `connect` blocks until the peer disconnects, so callers cannot continue after the connection is established. Wait only for the connect future and return.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| logger.error("The configuration items related to the AwsRoute53 dns server cannot be empty"); | ||
| return false; | ||
| } | ||
| return true; |
There was a problem hiding this comment.
P1: When DNS publishing is enabled without dnsPrivate, checkConfig still accepts the configuration and Tree.makeTree skips signing, so the publisher emits a tree://null@.../unsigned tree. Reject missing private keys before starting the publisher.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At p2p/src/main/java/org/tron/p2p/dns/update/PublishService.java, line 138:
<comment>When DNS publishing is enabled without `dnsPrivate`, `checkConfig` still accepts the configuration and `Tree.makeTree` skips signing, so the publisher emits a `tree://null@...`/unsigned tree. Reject missing private keys before starting the publisher.</comment>
<file context>
@@ -0,0 +1,146 @@
+ logger.error("The configuration items related to the AwsRoute53 dns server cannot be empty");
+ return false;
+ }
+ return true;
+ }
+
</file context>
| } | ||
|
|
||
| public static RootEntry parseEntry(String e) throws DnsException { | ||
| String value = e.substring(rootPrefix.length()); |
There was a problem hiding this comment.
P1: When a root TXT value is shorter than rootPrefix, this line throws StringIndexOutOfBoundsException; prefixless values are also parsed as roots. AwsClient.computeChanges catches only DnsException, so malformed existing root data aborts publish; validate the prefix and length first.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At p2p/src/main/java/org/tron/p2p/dns/tree/RootEntry.java, line 67:
<comment>When a root TXT value is shorter than `rootPrefix`, this line throws `StringIndexOutOfBoundsException`; prefixless values are also parsed as roots. `AwsClient.computeChanges` catches only `DnsException`, so malformed existing root data aborts publish; validate the prefix and length first.</comment>
<file context>
@@ -0,0 +1,113 @@
+ }
+
+ public static RootEntry parseEntry(String e) throws DnsException {
+ String value = e.substring(rootPrefix.length());
+ DnsRoot dnsRoot1;
+ try {
</file context>
|
|
||
| for (String key : existing.keySet()) { | ||
| if (!records.containsKey(key)) { | ||
| deleteRecord(existing.get(key).getRecordId()); |
There was a problem hiding this comment.
P1: When a stale-record deletion fails after retries, submitChanges still reports publication success and leaves the old DNS record active. Check the return value and propagate a deletion failure before logging success.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At p2p/src/main/java/org/tron/p2p/dns/update/AliClient.java, line 199:
<comment>When a stale-record deletion fails after retries, `submitChanges` still reports publication success and leaves the old DNS record active. Check the return value and propagate a deletion failure before logging success.</comment>
<file context>
@@ -0,0 +1,341 @@
+
+ for (String key : existing.keySet()) {
+ if (!records.containsKey(key)) {
+ deleteRecord(existing.get(key).getRecordId());
+ deleteCount++;
+ }
</file context>
|
|
||
| @Override | ||
| public int hashCode() { | ||
| return this.format().hashCode(); |
There was a problem hiding this comment.
P1: When a peer keeps the same ID but advertises a different endpoint, equal Node objects produce different hashes, so HashSet lookups and deduplication fail. Hash the same byte-array identity used by equals.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At p2p/src/main/java/org/tron/p2p/discover/Node.java, line 155:
<comment>When a peer keeps the same ID but advertises a different endpoint, equal `Node` objects produce different hashes, so `HashSet` lookups and deduplication fail. Hash the same byte-array identity used by `equals`.</comment>
<file context>
@@ -0,0 +1,197 @@
+
+ @Override
+ public int hashCode() {
+ return this.format().hashCode();
+ }
+
</file context>
| public static String compressPubKey(BigInteger pubKey) { | ||
| String pubKeyYPrefix = pubKey.testBit(0) ? "03" : "02"; | ||
| String pubKeyHex = pubKey.toString(16); | ||
| String pubKeyX = pubKeyHex.substring(0, 64); |
There was a problem hiding this comment.
P1: When the public key’s X coordinate starts with a zero nibble, this slice drops that nibble and includes part of Y, so DNS tree publication produces an unusable public key. Left-pad the full public-key value to 128 hex characters before extracting X.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At p2p/src/main/java/org/tron/p2p/dns/tree/Algorithm.java, line 31:
<comment>When the public key’s X coordinate starts with a zero nibble, this slice drops that nibble and includes part of Y, so DNS tree publication produces an unusable public key. Left-pad the full public-key value to 128 hex characters before extracting X.</comment>
<file context>
@@ -0,0 +1,150 @@
+ public static String compressPubKey(BigInteger pubKey) {
+ String pubKeyYPrefix = pubKey.testBit(0) ? "03" : "02";
+ String pubKeyHex = pubKey.toString(16);
+ String pubKeyX = pubKeyHex.substring(0, 64);
+ String hexPub = pubKeyYPrefix + pubKeyX;
+ return hexPub;
</file context>
| } | ||
|
|
||
| public static BigInteger decodeQuantity(String value) { | ||
| if (isLongValue(value)) { |
There was a problem hiding this comment.
P2: When decodeQuantity receives a negative or unprefixed value, it returns it instead of rejecting it. Validate the quantity digits and require the 0x form before parsing.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At p2p/src/main/java/org/web3j/utils/Numeric.java, line 43:
<comment>When `decodeQuantity` receives a negative or unprefixed value, it returns it instead of rejecting it. Validate the quantity digits and require the `0x` form before parsing.</comment>
<file context>
@@ -0,0 +1,252 @@
+ }
+
+ public static BigInteger decodeQuantity(String value) {
+ if (isLongValue(value)) {
+ return BigInteger.valueOf(Long.parseLong(value));
+ }
</file context>
| int startIdx; | ||
| if (len % 2 != 0) { | ||
| data = new byte[(len / 2) + 1]; | ||
| data[0] = (byte) Character.digit(cleanInput.charAt(0), 16); |
There was a problem hiding this comment.
P2: When hexStringToByteArray receives a non-hex character, it silently transforms the input into bytes, so Hash.sha3(String) hashes the wrong data. Reject either nibble before constructing each byte.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At p2p/src/main/java/org/web3j/utils/Numeric.java, line 213:
<comment>When `hexStringToByteArray` receives a non-hex character, it silently transforms the input into bytes, so `Hash.sha3(String)` hashes the wrong data. Reject either nibble before constructing each byte.</comment>
<file context>
@@ -0,0 +1,252 @@
+ int startIdx;
+ if (len % 2 != 0) {
+ data = new byte[(len / 2) + 1];
+ data[0] = (byte) Character.digit(cleanInput.charAt(0), 16);
+ startIdx = 1;
+ } else {
</file context>
| }); | ||
|
|
||
| } catch (Exception e) { | ||
| logger.error("Unexpected initChannel error", e); |
There was a problem hiding this comment.
P2: When channel initialization throws, this catch logs and returns while leaving the Netty channel open with a partial pipeline. Close ch or rethrow the failure so Netty cannot retain an unusable peer connection.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At p2p/src/main/java/org/tron/p2p/connection/socket/P2pChannelInitializer.java, line 58:
<comment>When channel initialization throws, this catch logs and returns while leaving the Netty channel open with a partial pipeline. Close `ch` or rethrow the failure so Netty cannot retain an unusable peer connection.</comment>
<file context>
@@ -0,0 +1,62 @@
+ });
+
+ } catch (Exception e) {
+ logger.error("Unexpected initChannel error", e);
+ }
+ }
</file context>
| public NodeEntry(byte[] ownerId, Node n) { | ||
| this.node = n; | ||
| entryId = n.getHostKey(); | ||
| distance = distance(ownerId, n.getId()); |
There was a problem hiding this comment.
P2: When an existing handler learns a different node ID, this cached distance remains based on the old ID, so NodeTable keeps the peer in the wrong Kademlia bucket and may evict or fail-find the wrong peers. Recompute the distance from the current node ID, or update/rebucket the entry whenever the node ID changes.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At p2p/src/main/java/org/tron/p2p/discover/protocol/kad/table/NodeEntry.java, line 14:
<comment>When an existing handler learns a different node ID, this cached distance remains based on the old ID, so `NodeTable` keeps the peer in the wrong Kademlia bucket and may evict or fail-find the wrong peers. Recompute the distance from the current node ID, or update/rebucket the entry whenever the node ID changes.</comment>
<file context>
@@ -0,0 +1,88 @@
+ public NodeEntry(byte[] ownerId, Node n) {
+ this.node = n;
+ entryId = n.getHostKey();
+ distance = distance(ownerId, n.getId());
+ touch();
+ }
</file context>
There was a problem hiding this comment.
All reported issues were addressed across 3 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Netty 4.2 split io.netty.handler.codec.protobuf out of netty-codec into its own artifact, so both :framework and :p2p have to declare it explicitly -- each puts the varint32 framing codecs on its channel pipelines. Both carried the literal 4.2.15.Final. Netty itself is not declared anywhere; it arrives transitively through grpc-netty, which :p2p tracks as rootProject.grpcVersion. So a grpc bump moves Netty while these two literals stay put -- exactly the mismatch that broke p2p's pipeline when develop moved to Netty 4.2 in the first place. Extract nettyVersion next to grpcVersion so the coupling is visible in one place. Resolution is unchanged: netty-codec-protobuf still resolves to 4.2.15.Final on both :framework:compileClasspath and :p2p:compileClasspath.
c4b005b to
0dd786b
Compare
:framework uses org.tron.p2p in 17 files under src/main/java, but declared no
dependency on it. The types arrive three hops away, through
:common -> :crypto -> :chainbase, because common exposes p2p with
`api project(":p2p")`.
That export is not a mistake and is not removable here: CommonParameter
publishes `P2pConfig p2pConfig` and `PublishConfig dnsPublishConfig` as public
@Getter fields, so p2p types are part of :common's own API surface. Narrowing
it to `implementation` would break every caller of getP2pConfig(). Actually
de-coupling the graph means moving those fields out of CommonParameter, which
is a functional refactor and out of scope for this PR.
What is fixable now is the undeclared direct use. Declare it, so framework does
not depend on an unrelated module's export choice for code it uses itself.
api rather than implementation, because framework re-exports p2p types itself:
P2pEventHandlerImpl extends org.tron.p2p.P2pEventHandler, HelloMessage.getFrom()
returns org.tron.p2p.discover.Node, PeerManager.add/remove take
org.tron.p2p.connection.Channel, and Args.loadDnsPublishConfig returns
PublishConfig. implementation would compile today only because the transitive
api chain still supplies those types to consumers -- the moment that chain is
narrowed, it breaks.
No resolution change -- p2p was already on framework's compile and runtime
classpaths via the transitive api.
0dd786b to
5071aec
Compare
getClosestNodes_nodesMoreThanBucketCapacity built both nodes from the same
byte[]:
byte[] bytes = new byte[64];
bytes[0] = 15; Node nearNode = new Node(bytes, ...);
bytes[0] = 70; Node farNode = new Node(bytes, ...);
Node keeps the reference it is handed (this.id = id), so the second mutation
rewrote nearNode's id too and both nodes ended up identical. The test still
passed, but only because Node.equals compares getIdString(): the surviving
farNode satisfies closest.contains(nearNode). Nothing the method claims to
check was actually checked -- the comment "nearnode's distance is 252, far's
is 255, others' are 253" was never exercised.
Give each node its own array. Those three distances now hold: with the home id
all zeros, distance is 256 minus the leading zero bits of the id, so 0x0F ->
252, 0x11 -> 253, 0x46 -> 255.
Also assert what the trailing comment already promised but never verified --
that the farthest node is excluded, and that the result is capped at
BUCKET_SIZE. Confirmed both bite: restoring the shared array makes the test
fail on the new assertion.
Unrelated and pre-existing: this class cannot run on its own, because it reads
Parameter.p2pConfig without setting it and depends on another test class having
initialised it. Verified against the unmodified branch -- running the class
alone fails there too. Not addressed here.
The Coverage Gate on PR #14 fails the overall-delta check: base 79.67%, PR 78.49%, delta -1.17% against a -0.1% threshold. The cause is precise -- this PR wires p2p's classes into :framework:jacocoTestReport, which adds 16,568 instructions at 60.91% to the repo-wide denominator. Excluding p2p the PR sits at 79.70%, a delta of +0.04, so every point of the drop comes from vendored code now being counted. This is the first batch of tests aimed at that gap, covering the parts that are pure logic and need no live connection: - org/web3j/utils: Numeric, Strings, Assertions, and both message exceptions - org/web3j/crypto: Hash digests, Sign sign/recover round trips, ECKeyPair value semantics, ECDSASignature canonicalisation - UpgradeController: the per-peer compression negotiation, both legacy paths - kad discovery messages: ping/pong/find-node/neighbours through their own bytes - StatusMessage, P2pDisconnectMessage - TrafficStats and StatsManager - AwsClient batching: the 32000-byte and 1000-change Route53 limits, UPSERT counting double, makeDeletionChanges, isSubdomain - MessageHandler: every P2pException to DisconnectReason branch - P2pService: the public entry point, which had no test at all Two pre-existing test defects surfaced and are fixed here because they made coverage depend on fork scheduling rather than on what the tests assert: - ConnPoolServiceTest and SocketTest both bound fixed ports (10000, 10001). PeerServer.start only logs on bind failure, so a collision let them pass while exercising nothing. Both now take a free port from PublicMethod.chooseRandomPort(), which is what java-tron's own tests use. - NodeTableTest read Parameter.p2pConfig without ever setting it, so it depended on an earlier class in the same fork having done so. Running the class on its own failed all eleven methods, on this branch and on the unmodified one alike. It now sets up and restores its own config. Local p2p instruction coverage: 59.02% -> 69.43%.
…ssage dispatch Second coverage batch: - P2pPacketDecoder: every drop path a hostile datagram can take (too short, oversized, unknown type, unparseable body) plus the assertion that a bad packet leaves the shared discovery socket open - Channel: pipeline layout, the post-disconnect send guard, running-mean latency, and the exception classification that precedes a close - KeepAliveService.processMessage: ping answered, pong clears the wait flag - discover Message.parse: dispatch for all four kad types and both rejections Local p2p instruction coverage: 69.43% -> 72.03%.
Third coverage batch: - ChannelManager.processPeer: ban list, global cap, per-IP cap and the duplicate-nodeId tie-break, plus the full DisconnectCode to DisconnectReason mapping - Tree: root signing, both toTXT shapes (Aliyun bare root and fully qualified Route53 names), the entry accessors, and merge's network grouping - NetUtil: parseInetSocketAddress including the bracketed-IPv6 requirement and its three rejection paths, Endpoint conversion, local address enumeration Notes what sign() actually does with an empty private key: it returns early rather than refusing, leaving the tree unsigned and the public key null. Pinned as current behaviour since the publisher does not check it either.
… detection Fourth coverage batch: - AwsClient against a mocked Route53Client: record collection with pagination, the subdomain and TXT filters, rejoining split TXT chunks, zone discovery when no zone id is configured, deploy on an empty zone, and deleteDomain - ConnPoolService: the active/passive counters that decide how many outbound slots the pool tries to fill - NodeDetectService: NodeStat's finished/in-flight predicate and the trim pass that bans an address which never answered its probe One test pins a known defect rather than asserting the behaviour we want: malformed base64 inside a nodes entry makes Algorithm.decode64 raise an unchecked IllegalArgumentException, which escapes collectRecords' DnsException catch and aborts the whole publish instead of skipping one record. Reported in the PR description as deferred; the test fails loudly if that ever changes. Local p2p instruction coverage: 73.17% -> 77.27%.
Fifth coverage batch: AliClient.deploy against a mocked Aliyun SDK -- the empty-zone path where every record is an add, the below-threshold path where a tree matching what DNS already holds is skipped entirely, the DnsException wrapping of any SDK failure, and the serverNodes reset afterwards. Seeding the below-threshold case had to go through describeDomainRecords rather than the serverNodes field: deploy() calls collectRecords first, which overwrites that set from the DNS response. Local p2p instruction coverage: 77.27% -> 78.87%. Added 3,159 covered instructions against the 2,851 the delta gate needs.
:framework:checkstyleTest runs with maxWarnings = 0. Five violations across the four files this branch touched: four out-of-order imports and one missing blank line before an appended method.
There was a problem hiding this comment.
1 issue found across 33 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="framework/src/test/java/org/tron/p2p/connection/message/detect/StatusMessageTest.java">
<violation number="1" location="framework/src/test/java/org/tron/p2p/connection/message/detect/StatusMessageTest.java:28">
P3: This test rewrites the process-wide singleton `Parameter.p2pConfig` and clears the shared `ChannelManager.channels` map in setUp/tearDown. `Parameter.p2pConfig` is `volatile` and read by the live p2p stack (getHomeNode, processPeer, StatusMessage), and `channels` is the production connection map; mutating them makes the test order- and concurrency-sensitive and lets it clobber state that other tests or a running Stack may rely on.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| config.setIp("127.0.0.1"); | ||
| config.setNetworkId(11111); | ||
| config.setMaxConnections(30); | ||
| Parameter.p2pConfig = config; |
There was a problem hiding this comment.
P3: This test rewrites the process-wide singleton Parameter.p2pConfig and clears the shared ChannelManager.channels map in setUp/tearDown. Parameter.p2pConfig is volatile and read by the live p2p stack (getHomeNode, processPeer, StatusMessage), and channels is the production connection map; mutating them makes the test order- and concurrency-sensitive and lets it clobber state that other tests or a running Stack may rely on.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At framework/src/test/java/org/tron/p2p/connection/message/detect/StatusMessageTest.java, line 28:
<comment>This test rewrites the process-wide singleton `Parameter.p2pConfig` and clears the shared `ChannelManager.channels` map in setUp/tearDown. `Parameter.p2pConfig` is `volatile` and read by the live p2p stack (getHomeNode, processPeer, StatusMessage), and `channels` is the production connection map; mutating them makes the test order- and concurrency-sensitive and lets it clobber state that other tests or a running Stack may rely on.</comment>
<file context>
@@ -0,0 +1,70 @@
+ config.setIp("127.0.0.1");
+ config.setNetworkId(11111);
+ config.setMaxConnections(30);
+ Parameter.p2pConfig = config;
+ ChannelManager.getChannels().clear();
+ }
</file context>
Five of the six comments were right, and three of them are the same defect this branch already fixed in NodeTableTest: a test that does not check what its name claims. - KadMessagesTest.messageFromAnInvalidEndpointIsRejected built a Node on port 18888 -- a valid one -- then asserted valid() was true. It tested the opposite of its name. Replaced with two tests that build the Endpoint proto directly, one missing its address and one missing its node id, and assert both are rejected. - AliClientDeployTest.deployClearsServerNodesAfterwards could not fail: collectRecords() reassigns serverNodes on the way into deploy(), so the planted value was gone regardless. Rewritten to assert what is actually worth pinning -- a stale cached node does not survive the round trip. - SignTest asserted the public key rendered to at most 128 hex chars, which a truncated key also satisfies. Now asserts the exact 64-byte length, on the zero-padded form since toHexStringNoPrefix drops leading zeros. Two were global-state leaks that would have made later tests in the same fork depend on this one: - P2pServiceTest registered a handler for type 0x7A into Parameter.handlerMap and handlerList and never removed it. Both registries are now snapshotted and restored. - ChannelCoreTest calls close(), which bans the peer address for DEFAULT_BAN_TIME in a process-wide cache. A leftover ban on 127.0.0.1 would make any later test see a recently-disconnected peer. The cache is now cleared around each test. The sixth comment, that StatusMessageTest rewrites Parameter.p2pConfig and clears ChannelManager.channels, is accurate but describes how every p2p test in this tree already works, including the ones that predate this branch. Left alone rather than diverging one file from the convention. Local p2p instruction coverage: 78.92% -> 79.02%.
There was a problem hiding this comment.
7 issues found across 33 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="framework/src/test/java/org/tron/p2p/stats/StatsManagerTest.java">
<violation number="1" location="framework/src/test/java/org/tron/p2p/stats/StatsManagerTest.java:26">
P3: snapshotIsDetachedFromLaterTraffic permanently mutates the shared static TrafficStats.tcp counter and never restores it. Because TrafficStats.tcp is a JVM-global @Sharable singleton also attached to real TCP pipelines, the +1 leaks into any later test or path that relies on an absolute baseline. Restore the counter (or use a fresh TrafficStatHandler) after asserting to keep the test isolated.</violation>
</file>
<file name="framework/src/test/java/org/tron/p2p/discover/message/DiscoverMessageTest.java">
<violation number="1" location="framework/src/test/java/org/tron/p2p/discover/message/DiscoverMessageTest.java:19">
P3: The class comment claims the two Message.parse rejection paths are covered, but only the NO_SUCH_MESSAGE default branch is. unparseableBodyIsRejected throws from Discover.PingMessage.parseFrom (InvalidProtocolBufferException) before valid() runs, so no test hits the valid()==false BAD_MESSAGE rejection. Either fix the comment or add a test that sends a well-formed datagram with an invalid node (e.g., empty host) and asserts P2pException.TypeEnum.BAD_MESSAGE.</violation>
</file>
<file name="framework/src/test/java/org/tron/p2p/connection/socket/MessageHandlerTest.java">
<violation number="1" location="framework/src/test/java/org/tron/p2p/connection/socket/MessageHandlerTest.java:123">
P3: This test asserts NO_SUCH_MESSAGE for type byte 0x01, which depends on the global static Parameter.handlerMap having no registered handler for 0x01. Any other test (or the application wiring) that registers a P2pEventHandler with type 0x01 through addP2pEventHandle makes this assertion fail, and because handlerMap is never reset by setUp/tearDown the test is order- and suite-dependent. Save and restore handlerMap (clear it in setUp, restore in tearDown) or register the handler under test explicitly so the test does not depend on unspecified global state.</violation>
</file>
<file name="framework/src/test/java/org/tron/p2p/connection/business/pool/ConnPoolLifecycleTest.java">
<violation number="1" location="framework/src/test/java/org/tron/p2p/connection/business/pool/ConnPoolLifecycleTest.java:61">
P2: Each test method registers a new ConnPoolService into the global static Parameter.handlerList (added by the ConnPoolService constructor) and never removes it or calls close(). ChannelManager.onConnect/onDisconnect iterate that list, so every leaked instance keeps receiving connection events and accumulating across the suite, polluting other p2p tests in the same JVM. Remove the handler (Parameter.handlerList.remove(service)) and shut down the executors in tearDown, or restore the list to its pre-test contents.</violation>
</file>
<file name="framework/src/test/java/org/tron/p2p/utils/NetUtilAddressTest.java">
<violation number="1" location="framework/src/test/java/org/tron/p2p/utils/NetUtilAddressTest.java:35">
P3: `tolerantOfSurroundingWhitespace` trims its input (`.trim()`) before calling `parseInetSocketAddress`, so it never exercises the method's own whitespace handling and would pass even if `parseInetSocketAddress` dropped its internal `para.trim()`. Pass the un-trimmed value so the test actually verifies the claimed behavior it documents.</violation>
</file>
<file name="framework/src/test/java/org/tron/p2p/connection/ChannelCoreTest.java">
<violation number="1" location="framework/src/test/java/org/tron/p2p/connection/ChannelCoreTest.java:106">
P3: The assertion `channel.getLastSendTime() > 0` cannot fail: `Channel.lastSendTime` is initialized to `System.currentTimeMillis()` at field construction, so it is always non-zero even if `send` never ran. To actually verify that `send` updates the timestamp, reset it (e.g. set lastSendTime to 0) before calling send, then assert it became non-zero after the write.</violation>
</file>
<file name="framework/src/test/java/org/tron/p2p/P2pServiceTest.java">
<violation number="1" location="framework/src/test/java/org/tron/p2p/P2pServiceTest.java:47">
P3: Each test calls service.start(config), and P2pService.start() registers a fresh Runtime shutdown hook (new Thread(this::close)) that is never removed. Running this 5-test class accumulates 5 hook threads that persist until the JVM exits and hold references to otherwise-discarded service instances. Start the service once per class (or refactor so the hook is not registered per instance) to avoid leaking a hook per test.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| config.setMaxConnectionsWithSameIp(5); | ||
| Parameter.p2pConfig = config; | ||
| ChannelManager.getChannels().clear(); | ||
| service = new ConnPoolService(); |
There was a problem hiding this comment.
P2: Each test method registers a new ConnPoolService into the global static Parameter.handlerList (added by the ConnPoolService constructor) and never removes it or calls close(). ChannelManager.onConnect/onDisconnect iterate that list, so every leaked instance keeps receiving connection events and accumulating across the suite, polluting other p2p tests in the same JVM. Remove the handler (Parameter.handlerList.remove(service)) and shut down the executors in tearDown, or restore the list to its pre-test contents.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At framework/src/test/java/org/tron/p2p/connection/business/pool/ConnPoolLifecycleTest.java, line 61:
<comment>Each test method registers a new ConnPoolService into the global static Parameter.handlerList (added by the ConnPoolService constructor) and never removes it or calls close(). ChannelManager.onConnect/onDisconnect iterate that list, so every leaked instance keeps receiving connection events and accumulating across the suite, polluting other p2p tests in the same JVM. Remove the handler (Parameter.handlerList.remove(service)) and shut down the executors in tearDown, or restore the list to its pre-test contents.</comment>
<file context>
@@ -0,0 +1,133 @@
+ config.setMaxConnectionsWithSameIp(5);
+ Parameter.p2pConfig = config;
+ ChannelManager.getChannels().clear();
+ service = new ConnPoolService();
+ }
+
</file context>
| public void snapshotIsDetachedFromLaterTraffic() { | ||
| P2pStats before = new StatsManager().getP2pStats(); | ||
| long recorded = before.getTcpInPackets(); | ||
| TrafficStats.tcp.getInPackets().incrementAndGet(); |
There was a problem hiding this comment.
P3: snapshotIsDetachedFromLaterTraffic permanently mutates the shared static TrafficStats.tcp counter and never restores it. Because TrafficStats.tcp is a JVM-global @sharable singleton also attached to real TCP pipelines, the +1 leaks into any later test or path that relies on an absolute baseline. Restore the counter (or use a fresh TrafficStatHandler) after asserting to keep the test isolated.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At framework/src/test/java/org/tron/p2p/stats/StatsManagerTest.java, line 26:
<comment>snapshotIsDetachedFromLaterTraffic permanently mutates the shared static TrafficStats.tcp counter and never restores it. Because TrafficStats.tcp is a JVM-global @Sharable singleton also attached to real TCP pipelines, the +1 leaks into any later test or path that relies on an absolute baseline. Restore the counter (or use a fresh TrafficStatHandler) after asserting to keep the test isolated.</comment>
<file context>
@@ -0,0 +1,32 @@
+ public void snapshotIsDetachedFromLaterTraffic() {
+ P2pStats before = new StatsManager().getP2pStats();
+ long recorded = before.getTcpInPackets();
+ TrafficStats.tcp.getInPackets().incrementAndGet();
+
+ // The old snapshot must not move with the counter.
</file context>
| import org.tron.p2p.utils.NetUtil; | ||
|
|
||
| /** | ||
| * Message.parse is the entry point for every inbound discovery datagram, so its |
There was a problem hiding this comment.
P3: The class comment claims the two Message.parse rejection paths are covered, but only the NO_SUCH_MESSAGE default branch is. unparseableBodyIsRejected throws from Discover.PingMessage.parseFrom (InvalidProtocolBufferException) before valid() runs, so no test hits the valid()==false BAD_MESSAGE rejection. Either fix the comment or add a test that sends a well-formed datagram with an invalid node (e.g., empty host) and asserts P2pException.TypeEnum.BAD_MESSAGE.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At framework/src/test/java/org/tron/p2p/discover/message/DiscoverMessageTest.java, line 19:
<comment>The class comment claims the two Message.parse rejection paths are covered, but only the NO_SUCH_MESSAGE default branch is. unparseableBodyIsRejected throws from Discover.PingMessage.parseFrom (InvalidProtocolBufferException) before valid() runs, so no test hits the valid()==false BAD_MESSAGE rejection. Either fix the comment or add a test that sends a well-formed datagram with an invalid node (e.g., empty host) and asserts P2pException.TypeEnum.BAD_MESSAGE.</comment>
<file context>
@@ -0,0 +1,128 @@
+import org.tron.p2p.utils.NetUtil;
+
+/**
+ * Message.parse is the entry point for every inbound discovery datagram, so its
+ * dispatch table and its two rejection paths are what a hostile packet meets
+ * first.
</file context>
| // registered handler map rather than going through Message.parse. With no | ||
| // handler registered for 0x01 that path raises NO_SUCH_MESSAGE too, so a | ||
| // peer probing unused type bytes is disconnected the same way. | ||
| RecordingChannel channel = feed(new byte[] {0x01, 1, 2, 3}); |
There was a problem hiding this comment.
P3: This test asserts NO_SUCH_MESSAGE for type byte 0x01, which depends on the global static Parameter.handlerMap having no registered handler for 0x01. Any other test (or the application wiring) that registers a P2pEventHandler with type 0x01 through addP2pEventHandle makes this assertion fail, and because handlerMap is never reset by setUp/tearDown the test is order- and suite-dependent. Save and restore handlerMap (clear it in setUp, restore in tearDown) or register the handler under test explicitly so the test does not depend on unspecified global state.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At framework/src/test/java/org/tron/p2p/connection/socket/MessageHandlerTest.java, line 123:
<comment>This test asserts NO_SUCH_MESSAGE for type byte 0x01, which depends on the global static Parameter.handlerMap having no registered handler for 0x01. Any other test (or the application wiring) that registers a P2pEventHandler with type 0x01 through addP2pEventHandle makes this assertion fail, and because handlerMap is never reset by setUp/tearDown the test is order- and suite-dependent. Save and restore handlerMap (clear it in setUp, restore in tearDown) or register the handler under test explicitly so the test does not depend on unspecified global state.</comment>
<file context>
@@ -0,0 +1,139 @@
+ // registered handler map rather than going through Message.parse. With no
+ // handler registered for 0x01 that path raises NO_SUCH_MESSAGE too, so a
+ // peer probing unused type bytes is disconnected the same way.
+ RecordingChannel channel = feed(new byte[] {0x01, 1, 2, 3});
+ Assert.assertEquals(DisconnectReason.NO_SUCH_MESSAGE, channel.onlyReason());
+ }
</file context>
| @Test | ||
| public void tolerantOfSurroundingWhitespace() { | ||
| Assert.assertEquals(18888, | ||
| NetUtil.parseInetSocketAddress(" 127.0.0.1:18888 ".trim()).getPort()); |
There was a problem hiding this comment.
P3: tolerantOfSurroundingWhitespace trims its input (.trim()) before calling parseInetSocketAddress, so it never exercises the method's own whitespace handling and would pass even if parseInetSocketAddress dropped its internal para.trim(). Pass the un-trimmed value so the test actually verifies the claimed behavior it documents.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At framework/src/test/java/org/tron/p2p/utils/NetUtilAddressTest.java, line 35:
<comment>`tolerantOfSurroundingWhitespace` trims its input (`.trim()`) before calling `parseInetSocketAddress`, so it never exercises the method's own whitespace handling and would pass even if `parseInetSocketAddress` dropped its internal `para.trim()`. Pass the un-trimmed value so the test actually verifies the claimed behavior it documents.</comment>
<file context>
@@ -0,0 +1,103 @@
+ @Test
+ public void tolerantOfSurroundingWhitespace() {
+ Assert.assertEquals(18888,
+ NetUtil.parseInetSocketAddress(" 127.0.0.1:18888 ".trim()).getPort());
+ }
+
</file context>
| ByteBuf written = netty.readOutbound(); | ||
| Assert.assertNotNull("a ping should have been written", written); | ||
| Assert.assertEquals(MessageType.KEEP_ALIVE_PING.getType(), written.getByte(0)); | ||
| Assert.assertTrue(channel.getLastSendTime() > 0); |
There was a problem hiding this comment.
P3: The assertion channel.getLastSendTime() > 0 cannot fail: Channel.lastSendTime is initialized to System.currentTimeMillis() at field construction, so it is always non-zero even if send never ran. To actually verify that send updates the timestamp, reset it (e.g. set lastSendTime to 0) before calling send, then assert it became non-zero after the write.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At framework/src/test/java/org/tron/p2p/connection/ChannelCoreTest.java, line 106:
<comment>The assertion `channel.getLastSendTime() > 0` cannot fail: `Channel.lastSendTime` is initialized to `System.currentTimeMillis()` at field construction, so it is always non-zero even if `send` never ran. To actually verify that `send` updates the timestamp, reset it (e.g. set lastSendTime to 0) before calling send, then assert it became non-zero after the write.</comment>
<file context>
@@ -0,0 +1,173 @@
+ ByteBuf written = netty.readOutbound();
+ Assert.assertNotNull("a ping should have been written", written);
+ Assert.assertEquals(MessageType.KEEP_ALIVE_PING.getType(), written.getByte(0));
+ Assert.assertTrue(channel.getLastSendTime() > 0);
+ netty.finishAndReleaseAll();
+ }
</file context>
| savedHandlerMap = new HashMap<>(Parameter.handlerMap); | ||
|
|
||
| service = new P2pService(); | ||
| service.start(config); |
There was a problem hiding this comment.
P3: Each test calls service.start(config), and P2pService.start() registers a fresh Runtime shutdown hook (new Thread(this::close)) that is never removed. Running this 5-test class accumulates 5 hook threads that persist until the JVM exits and hold references to otherwise-discarded service instances. Start the service once per class (or refactor so the hook is not registered per instance) to avoid leaking a hook per test.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At framework/src/test/java/org/tron/p2p/P2pServiceTest.java, line 47:
<comment>Each test calls service.start(config), and P2pService.start() registers a fresh Runtime shutdown hook (new Thread(this::close)) that is never removed. Running this 5-test class accumulates 5 hook threads that persist until the JVM exits and hold references to otherwise-discarded service instances. Start the service once per class (or refactor so the hook is not registered per instance) to avoid leaking a hook per test.</comment>
<file context>
@@ -0,0 +1,118 @@
+ savedHandlerMap = new HashMap<>(Parameter.handlerMap);
+
+ service = new P2pService();
+ service.start(config);
+ }
+
</file context>
The earlier placement in framework/src/test was justified as "the project-wide convention already used by actuator, chainbase, consensus and common". That reading does not hold up: chainbase and consensus have no tests at all, and actuator has one -- so they are not evidence of a convention. The two modules that do have their own tests, common (13 files) and plugins (19), keep them in the module. Moving them here also deletes two workarounds that only existed because :p2p had no test sourceSet: - framework/build.gradle re-declared route53, alidns and dnsjava as testImplementation, because they are implementation-scope in :p2p and so invisible to another module's test classpath. It also had to mirror the dom4j exclusion tail onto every test configuration to keep dependency verification passing. In :p2p, testImplementation extends implementation, so both blocks are unnecessary -- the relocated tests compiled first try without them. - :framework:jacocoTestReport had p2p's class and source dirs bolted on with additionalClassDirs/additionalSourceDirs, because :p2p:jacocoTestReport produced nothing without exec data. :p2p now reports for itself. CI collects **/build/reports/jacoco/test/jacocoTestReport.xml across every module, so it is picked up with no wiring at all; the protos exclusion moves into the module's own report block. Coverage is unchanged by the move: 78.88% instruction, 78.12% line, the same figures the combined report produced. Three tests used framework's PublicMethod.chooseRandomPort. :p2p cannot depend on :framework -- that is a cycle -- so the same few lines live in p2p/src/test/java/org/tron/p2p/utils/TestPort. Separate test tasks also let Gradle run :p2p:test and :framework:test in parallel. 345 tests, 0 failures, 3 skipped. :p2p:checkstyleMain and :p2p:checkstyleTest both clean.
`DnsExample1`, `DnsExample2` and `ImportUsing` documented how an embedder configures and drives this module, but they only ever compiled. Each ended in a `while (true)` loop, bound a fixed port and pointed at live seed nodes, so nothing they demonstrated was checked -- and cubic found real defects sitting in them: `TestMessage` is not serializable so `ByteArray.fromObject` returns null and `Channel.send` closes the channel, and `DnsExample1` carried a signing private key in copyable code. Porting them line by line would produce three slow, network-dependent, port-bound tests. What is worth pinning is the contract they advertised: those configuration shapes are still accepted and still mean what the comments said. External embedders copy them, so a renamed setter or tightened validation is a breaking change even though nothing in this repo calls them. `ExampleUsageTest` covers all three shapes -- the connection-tuning surface, the register/start/query/close lifecycle on a free port with discovery off, the duplicate-message-type rejection, the AwsRoute53 publish config, and the discovery-off + tree-urls sync config. The signing key moves into the test as a fixture; it is upstream's well-known test key, already used by AlgorithmTest, and an embedder has to supply their own. `StartApp` moves to `src/main/java` and stays, as the entry point for debugging the module without starting java-tron. Moving it out of the exempt sourceSet subjects it to the project's checkstyle for the first time: three over-long lines, wrapped. It also carried a real bug. `--trust-ips` is declared as `ip[,ip[...]]` but resolved the whole comma-separated value as a single hostname, so with more than one address none of the listed peers became trusted. It now splits and resolves each, skipping and logging any that do not resolve. The `example` sourceSet and all of its build wiring are gone: the sourceSet block, the two extendsFrom configurations, the checkstyle opt-out, the encoding override, the Lombok wiring, and the `processExampleResources` task edge that only existed because `generatedFilesBaseDir` points into `src/`. 351 tests, 0 failures, 3 skipped. `:p2p:build` clean.
`p2p/` had no README. Upstream's lived at `src/example/resources/README.md`,
which no reader would find and which the deleted example sourceSet took with it.
Promoted to `p2p/README.md`.
Three things in it were stale after internalizing:
- `java -jar libp2p.jar` — the artifact is `p2p-1.0.0.jar` now
- the `StartApp` link pointed at github.com/tronprotocol/libp2p
- it referred to `ImportUsing.java`, `DnsExample1.java`, `DnsExample2.java`,
which no longer exist; now points at `ExampleUsageTest`
Added a header stating what the module is — vendored libp2p v2.2.9, consumed as
a project dependency rather than a published artifact.
`java -jar` did not work: the jar had no `Main-Class`, so the entry point Zeus
asked to keep was not reachable. The jar now declares
`org.tron.p2p.example.StartApp`. It is still a thin jar, so the README documents
the classpath form and a `printRuntimeClasspath` helper task supplies the rest:
./gradlew :p2p:jar
java -cp "p2p/build/libs/p2p-1.0.0.jar:$(./gradlew -q :p2p:printRuntimeClasspath)" \
org.tron.p2p.example.StartApp -h
Verified by running it: the module starts on its own and prints its help.
`src/main/resources/logback.xml.example` came from libp2p as a standalone project, where an embedder had no logging config of their own. Inside java-tron `framework/src/main/resources/logback.xml` is the config that applies, and the sample was being packaged into `p2p-1.0.0.jar` for no reason. Nothing references it — not the build, not the README, not any source file. `src/main/resources/` is now empty.
There was a problem hiding this comment.
5 issues found across 71 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="p2p/src/test/java/org/tron/p2p/example/ExampleUsageTest.java">
<violation number="1" location="p2p/src/test/java/org/tron/p2p/example/ExampleUsageTest.java:43">
P2: These two tests register a handler for hardcoded byte 0x01 into the process-wide static Parameter.handlerList/handlerMap and clean up only by restoring a snapshot taken in @Before. If any prior test in the shared fork leaves a 0x01 handler in that static map, Parameter.addP2pEventHandle throws TYPE_ALREADY_REGISTERED and importUsingLifecycleRunsEndToEnd fails spuriously. Reset the handlers to a clean state in @Before (or use a type unique to this class) instead of relying on every class's @After restoring the global registry exactly.</violation>
</file>
<file name="p2p/build.gradle">
<violation number="1" location="p2p/build.gradle:165">
P2: The documented standalone launch fails because this task's command-substitution output is contaminated by the root build's configuration-time `println`. Emit a machine-readable classpath without other Gradle stdout, or otherwise remove the configuration log from this command's output.</violation>
</file>
<file name="p2p/README.md">
<violation number="1" location="p2p/README.md:437">
P3: The unchanged "For details please" line now dangles in front of the added text, reading "For details please The former `ImportUsing`...". Drop the dangling lead-in so the sentence reads cleanly.</violation>
</file>
<file name="p2p/src/test/java/org/tron/p2p/utils/TestPort.java">
<violation number="1" location="p2p/src/test/java/org/tron/p2p/utils/TestPort.java:31">
P3: The catch (IOException e) in choose() is unreachable: available() swallows every IOException internally and returns false, so it never propagates. The try/catch wrapper adds no behavior; remove it and the now-unused IOException import.</violation>
<violation number="2" location="p2p/src/test/java/org/tron/p2p/utils/TestPort.java:42">
P2: available() frees the port the moment the check returns, so between that check and the test's actual bind another Gradle fork or process can take the port — the exact false-pass the comment warns about. This is the classic check-then-use TOCTOU race; checking a port and handing out its number cannot guarantee it stays free. Prefer returning a held ServerSocket that the test binds against (keeping it open), or at least acknowledge that the availability check does not remove the collision window.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| } | ||
|
|
||
| private static boolean available(int port) throws IOException { | ||
| try (ServerSocket socket = new ServerSocket(port)) { |
There was a problem hiding this comment.
P2: available() frees the port the moment the check returns, so between that check and the test's actual bind another Gradle fork or process can take the port — the exact false-pass the comment warns about. This is the classic check-then-use TOCTOU race; checking a port and handing out its number cannot guarantee it stays free. Prefer returning a held ServerSocket that the test binds against (keeping it open), or at least acknowledge that the availability check does not remove the collision window.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At p2p/src/test/java/org/tron/p2p/utils/TestPort.java, line 42:
<comment>available() frees the port the moment the check returns, so between that check and the test's actual bind another Gradle fork or process can take the port — the exact false-pass the comment warns about. This is the classic check-then-use TOCTOU race; checking a port and handing out its number cannot guarantee it stays free. Prefer returning a held ServerSocket that the test binds against (keeping it open), or at least acknowledge that the availability check does not remove the collision window.</comment>
<file context>
@@ -0,0 +1,49 @@
+ }
+
+ private static boolean available(int port) throws IOException {
+ try (ServerSocket socket = new ServerSocket(port)) {
+ socket.setReuseAddress(true);
+ return true;
</file context>
| private List<P2pEventHandler> savedHandlerList; | ||
| private java.util.Map<Byte, P2pEventHandler> savedHandlerMap; | ||
|
|
||
| private static final byte TEST_MESSAGE_TYPE = (byte) 0x01; |
There was a problem hiding this comment.
P2: These two tests register a handler for hardcoded byte 0x01 into the process-wide static Parameter.handlerList/handlerMap and clean up only by restoring a snapshot taken in @before. If any prior test in the shared fork leaves a 0x01 handler in that static map, Parameter.addP2pEventHandle throws TYPE_ALREADY_REGISTERED and importUsingLifecycleRunsEndToEnd fails spuriously. Reset the handlers to a clean state in @before (or use a type unique to this class) instead of relying on every class's @after restoring the global registry exactly.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At p2p/src/test/java/org/tron/p2p/example/ExampleUsageTest.java, line 43:
<comment>These two tests register a handler for hardcoded byte 0x01 into the process-wide static Parameter.handlerList/handlerMap and clean up only by restoring a snapshot taken in @Before. If any prior test in the shared fork leaves a 0x01 handler in that static map, Parameter.addP2pEventHandle throws TYPE_ALREADY_REGISTERED and importUsingLifecycleRunsEndToEnd fails spuriously. Reset the handlers to a clean state in @Before (or use a type unique to this class) instead of relying on every class's @After restoring the global registry exactly.</comment>
<file context>
@@ -0,0 +1,224 @@
+ private List<P2pEventHandler> savedHandlerList;
+ private java.util.Map<Byte, P2pEventHandler> savedHandlerMap;
+
+ private static final byte TEST_MESSAGE_TYPE = (byte) 0x01;
+
+ @Before
</file context>
| ``` | ||
|
|
||
| For details please | ||
| The former `ImportUsing`, `DnsExample1` and `DnsExample2` reference classes have |
There was a problem hiding this comment.
P3: The unchanged "For details please" line now dangles in front of the added text, reading "For details please The former ImportUsing...". Drop the dangling lead-in so the sentence reads cleanly.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At p2p/README.md, line 437:
<comment>The unchanged "For details please" line now dangles in front of the added text, reading "For details please The former `ImportUsing`...". Drop the dangling lead-in so the sentence reads cleanly.</comment>
<file context>
@@ -416,6 +434,9 @@ p2pService.start(config);
For details please
-check [ImportUsing](ImportUsing.java), [DnsExample1](DnsExample1.java), [DnsExample2](DnsExample2.java)
+The former `ImportUsing`, `DnsExample1` and `DnsExample2` reference classes have
+been replaced by
+[ExampleUsageTest](src/test/java/org/tron/p2p/example/ExampleUsageTest.java),
</file context>
| while (!available(port)) { | ||
| port = next(); | ||
| } | ||
| } catch (IOException e) { |
There was a problem hiding this comment.
P3: The catch (IOException e) in choose() is unreachable: available() swallows every IOException internally and returns false, so it never propagates. The try/catch wrapper adds no behavior; remove it and the now-unused IOException import.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At p2p/src/test/java/org/tron/p2p/utils/TestPort.java, line 31:
<comment>The catch (IOException e) in choose() is unreachable: available() swallows every IOException internally and returns false, so it never propagates. The try/catch wrapper adds no behavior; remove it and the now-unused IOException import.</comment>
<file context>
@@ -0,0 +1,49 @@
+ while (!available(port)) {
+ port = next();
+ }
+ } catch (IOException e) {
+ return next();
+ }
</file context>
The coverage gate failed after the previous four commits: delta went from -0.05% to -0.34%. Two separate causes, neither of them the code getting worse. **StartApp, 981 instructions at 0%.** Moving it from the `example` sourceSet into `src/main/java` put it in the coverage denominator for the first time -- that sourceSet was exempt from both checkstyle and coverage. It is argument parsing, option declarations and a main() that starts services and blocks: not module logic, and not code the node runs. `org/tron/p2p/example/**` is now excluded from this module's report alongside `**/protos/**`, which keeps the measured surface the same as before the move rather than hiding newly counted logic. The two parsing helpers it does own are real logic, and one of them shipped the `--trust-ips` bug fixed in the previous commit, so they are package-private now and `StartAppArgsTest` covers them: comma splitting, whitespace, unresolvable entries, and the bracketed-IPv6 form of `parseInetSocketAddressList`. The exclusion does not take regression protection with it. **Coverage that stopped being attributed.** :framework's own tests execute p2p code, and while p2p's classes hung off :framework:jacocoTestReport that was counted. :p2p:jacocoTestReport now reads framework's exec data too, so it keeps being counted. Worth noting it recovers only 70 instructions, not the ~700 the earlier measurements suggested -- the tests added in this PR already cover most of what framework's tests were reaching. The fileTree is empty when :framework:test has not run, so :p2p:build alone still works. p2p instruction coverage: 79.32% (12,625/15,917), against 78.88% on the last run that passed the gate.
There was a problem hiding this comment.
1 issue found across 3 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="p2p/src/test/java/org/tron/p2p/example/StartAppArgsTest.java">
<violation number="1" location="p2p/src/test/java/org/tron/p2p/example/StartAppArgsTest.java:48">
P3: trustIpsSkipsWhatItCannotResolve forces a real DNS lookup for "no-such-host.invalid" on every run. The .invalid TLD is RFC 2606-reserved so it is deterministic in normal environments, but this is still network I/O inside a unit test; in a network-less or slow-DNS CI it can hang on the resolver timeout or behave unexpectedly. Consider stubbing/mocking the resolver or removing the hostname case and testing the unresolvable path with a value that needs no network.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| public void trustIpsSkipsWhatItCannotResolve() { | ||
| // An unresolvable entry is logged and dropped rather than aborting the rest. | ||
| List<InetAddress> parsed = | ||
| app.parseInetAddressList("127.0.0.2,no-such-host.invalid,127.0.0.3"); |
There was a problem hiding this comment.
P3: trustIpsSkipsWhatItCannotResolve forces a real DNS lookup for "no-such-host.invalid" on every run. The .invalid TLD is RFC 2606-reserved so it is deterministic in normal environments, but this is still network I/O inside a unit test; in a network-less or slow-DNS CI it can hang on the resolver timeout or behave unexpectedly. Consider stubbing/mocking the resolver or removing the hostname case and testing the unresolvable path with a value that needs no network.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At p2p/src/test/java/org/tron/p2p/example/StartAppArgsTest.java, line 48:
<comment>trustIpsSkipsWhatItCannotResolve forces a real DNS lookup for "no-such-host.invalid" on every run. The .invalid TLD is RFC 2606-reserved so it is deterministic in normal environments, but this is still network I/O inside a unit test; in a network-less or slow-DNS CI it can hang on the resolver timeout or behave unexpectedly. Consider stubbing/mocking the resolver or removing the hostname case and testing the unresolvable path with a value that needs no network.</comment>
<file context>
@@ -0,0 +1,71 @@
+ public void trustIpsSkipsWhatItCannotResolve() {
+ // An unresolvable entry is logged and dropped rather than aborting the rest.
+ List<InetAddress> parsed =
+ app.parseInetAddressList("127.0.0.2,no-such-host.invalid,127.0.0.3");
+ Assert.assertEquals(2, parsed.size());
+ Assert.assertEquals("127.0.0.2", parsed.get(0).getHostAddress());
</file context>
Three follow-ups from review.
**`java -jar` failed.** The previous commit put `Main-Class` on the plain jar,
which is thin: the entry point resolved and then died on the first dependency it
touched, `NoClassDefFoundError: org/apache/commons/cli/ParseException`. That is
worse than declaring nothing -- it advertises support that cannot work. I had
only verified the `-cp` form documented in the README, not `java -jar` itself.
The plain jar drops `Main-Class` again and now fails honestly with "no main
manifest attribute". `buildStandaloneJar` produces `p2p-standalone.jar` with the
runtime classpath bundled, following :framework's FullNode.jar and :plugins'
Toolkit.jar -- same `artifacts { archives(...) }` wiring, the same
`-PbinaryRelease=false` opt-out, and the same exclusions for Bouncy Castle's
signatures and dnsjava's resolver SPI. Verified by running it: `java -jar
p2p/build/libs/p2p-standalone.jar --help` prints the help.
The `printRuntimeClasspath` helper is gone; it existed only to work around the
thin jar.
**The README still read as upstream's.** Four source links pointed at
github.com/tronprotocol/libp2p and the prose described libp2p as a standalone
project. Links are module-relative now, the prose talks about this module, and
the header states the provenance once and explains which of the two jars to use.
The one remaining upstream link is that attribution.
**Duplicated versions.** `protobufVersion` 3.25.8 was declared in both
:protocol and :p2p; checkstyle 8.7 sat in a local `versions` map in :framework
and :plugins and as a literal in :p2p. Both move to the root `ext` beside
`grpcVersion` and `nettyVersion`, and all four modules reference them, so the
duplication is removed rather than relocated. Resolution is unchanged:
`protobuf-java:3.25.8` on :p2p's compile classpath.
There was a problem hiding this comment.
1 issue found across 6 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="p2p/README.md">
<violation number="1" location="p2p/README.md:28">
P2: The documented `-PbinaryRelease=false` opt-out does not take effect. The Gradle property is `binaryRelease` (lowercase 'b') in p2p/build.gradle:29, and `-P` property names are case-sensitive, so passing the capital-B flag leaves the property unset and the default 'true' builds `p2p-standalone.jar` anyway. Use `-PbinaryRelease=false` to match the modules it claims to mirror (`:framework`, `:plugins`).</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| java -jar p2p/build/libs/p2p-standalone.jar --help | ||
| ``` | ||
|
|
||
| `-PbinaryRelease=false` skips building it, matching `:framework` and `:plugins`. |
There was a problem hiding this comment.
P2: The documented -PbinaryRelease=false opt-out does not take effect. The Gradle property is binaryRelease (lowercase 'b') in p2p/build.gradle:29, and -P property names are case-sensitive, so passing the capital-B flag leaves the property unset and the default 'true' builds p2p-standalone.jar anyway. Use -PbinaryRelease=false to match the modules it claims to mirror (:framework, :plugins).
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At p2p/README.md, line 28:
<comment>The documented `-PbinaryRelease=false` opt-out does not take effect. The Gradle property is `binaryRelease` (lowercase 'b') in p2p/build.gradle:29, and `-P` property names are case-sensitive, so passing the capital-B flag leaves the property unset and the default 'true' builds `p2p-standalone.jar` anyway. Use `-PbinaryRelease=false` to match the modules it claims to mirror (`:framework`, `:plugins`).</comment>
<file context>
@@ -1,29 +1,40 @@
+java -jar p2p/build/libs/p2p-standalone.jar --help
+```
+
+`-PbinaryRelease=false` skips building it, matching `:framework` and `:plugins`.
+
+This module can run on its own, or be used as a library.
</file context>
`src/main/protos` with an explicit `srcDir` override builds fine, but IDEA's protobuf plugin does not read that override -- it resolves imports against the plugin's default path -- so `import "Discover.proto"` in Connect.proto and every type it brings in showed as unresolved in the editor. Renamed to `src/main/proto`, the default, and dropped the sourceSet override. Generated sources still land in `src/main/java/org/tron/p2p/protos` via `generatedFilesBaseDir` and are still gitignored; `clean` still removes them. Worth flagging for whoever picks this up: `:protocol` has the identical setup -- `src/main/protos` plus the same explicit `srcDir`, with imports relative to that root -- so it presumably shows the same red in IDEA. This commit leaves it alone, which means the two modules now differ. Aligning `:protocol` is a one-directory rename too, but it is core java-tron with a `src/main/gen` interplay and does not belong in this PR. 357 tests, 0 failures, 3 skipped. `:p2p:build` clean.
chore(p2p): internalize libp2p v2.2.9 as a local
p2pmoduleReplaces the external
io.github.tronprotocol:libp2p:2.2.9Maven dependency with a localp2p/Gradle module built from the same source.Supersedes tronprotocol#6673, which was approved on the merits but closed because it touched too many files while 4.8.2 was already full. This is a fresh branch — no history is reused.
Reproducing the diff
tronprotocol/java-trondevelop@4a21592f95e37908b21bc3f611c6e7a1a67f09f3tronprotocol/libp2ptagv2.2.9@c564f263d310d7a964035d3b597634aba6bda86dCommit 1 is byte-comparable to the upstream tag. It contains only
git archive v2.2.9 src/mainplus the new build files, so a reviewer can diff it directly againstc564f263and see that no vendored line was altered. Everything we changed about that source lands in commit 2, separately, for exactly this reason.File count
p2p/src/main/java(vendored)p2p/src/main/protop2p/src/test/javaProtobuf-generated sources under
src/main/java/org/tron/p2p/protos/are gitignored and regenerated at build time, so they are not in the diff.Tests live in
p2p/src/test/java/, next to the code they cover. An earlier revision of this branch put them inframework/src/test/and justified it as a project convention; that was wrong —chainbaseandconsensushave no tests at all andactuatorhas one, so they are not evidence of anything, while the two modules that do have tests (common,plugins) keep them in the module. Moving them back also deleted two workarounds; see Module layout below.StartAppstays insrc/main/javaas the entry point for driving the module without starting java-tron, and the jar declares it asMain-Class. The other three reference classes became a test — see Module layout below.Problems encountered
Three of these did not exist when tronprotocol#6673 was written; all were confirmed by compiling.
H1 — Netty 4.2 split out
netty-codec-protobufdevelopnow resolves Netty 4.2.15.Final (via the gRPC 1.83 bump in tronprotocol#6891).io.netty.handler.codec.protobufno longer arrives transitively, soProtobufVarint32FrameDecoder/ProtobufVarint32LengthFieldPrepender— on every p2p channel pipeline — failed to resolve. Declared explicitly inp2p/build.gradle, mirroring whatframework/build.gradlealready does for the same reason, excludes included. Both now referencerootProject.nettyVersionrather than repeating the literal — see Build wiring cleanups below.p2ptracksrootProject.grpcVersionrather than pinning libp2p's own gRPC version, so it cannot drift from the Netty the rest of the build resolves.H2 — errorprone
StringCaseLocaleUsage(the one behavioural deviation)The root build enables exactly two errorprone rules as ERROR on every subproject except
protocol/errorprone:StringCaseLocaleUsageandStringCaseLocaleUsageMethodRef. libp2p has 4 baretoLowerCase()/toUpperCase()calls, which are nowtoLowerCase(Locale.ROOT)/toUpperCase(Locale.ROOT), matching the project's own idiom (Args.java:1273).This is the only change in this PR that is not purely mechanical. It is compile-forced and behaviour-identical for ASCII input, but it is a real semantic change under a Turkish locale, so it is called out here rather than buried in a style commit.
H3 —
BasicThreadFactory.builder()needs commons-lang3 3.12+The project pins commons-lang3 3.4 globally; v2.2.9 uses
builder()in 13 places. Rewritten tonew BasicThreadFactory.Builder()(the 3.0 API) rather than bumping commons-lang3, which would have been a silent global upgrade.dom4j exclusion tail
The
jaxen/stax-api/msv/xsdlib/relaxngDatatype/pull-parser/xpp3exclusions used to sit on thelibp2pdependency incommon/build.gradle. That transitive tail comes from the Aliyun / Route53 SDKs, so internalizing moves it intop2p/build.gradleas aconfigurations.configureEachblock.An earlier revision also had to mirror this tail onto
:framework's test configurations, because the DNS tests lived there and the SDKs areimplementation-scope in:p2p. With the tests back in:p2p,testImplementation extends implementationcovers it and that mirror is gone.Task-dependency edges an external jar did not need
Adding a project to the dependency graph needs three explicit task edges that a Maven artifact did not. Gradle reported each as an
implicit_dependencyand responded by disabling execution optimizations "to ensure correctness":framework'sbuildFullNodeJarandplugins'binaryReleaseboth zip upruntimeClasspathand maintain a hand-writtendependsOnlist of project jars — the plugins one carries a comment explaining it exists so "partial / parallel builds cannot run binaryRelease before the dependency jars exist".:commonnow exposes p2p viaapi, sop2p-1.0.0.jaris on both classpaths, and neither list had been updated. Without the edge a parallel build could assemble the shipped fat jar before:p2p:jaris written.:p2p:processExampleResourcesreadsrc/example/resources, which the protobuf plugin claimed as an output ofgenerateExampleProtobecausegeneratedFilesBaseDirpoints at$projectDir/src. That edge went away with the example sourceSet itself.A full build now reports zero
implicit_dependencywarnings.Coverage attribution
An earlier revision bolted p2p's class and source dirs onto
:framework:jacocoTestReportwithadditionalClassDirs/additionalSourceDirs, because:p2phad no test sourceSet and so produced no exec data of its own — leaving the module invisible to the coverage gate.With the tests in
:p2p, the module reports for itself. CI collects**/build/reports/jacoco/test/jacocoTestReport.xmlacross every module, so it is picked up with no wiring at all, and the**/protos/**exclusion moves into the module's own report block.Dependency verification
Three components needed adding to
gradle/verification-metadata.xml:org.bouncycastle:bcutil-jdk18on:1.84,com.google.code.gson:gson:2.9.0andcom.google.code.gson:gson-parent:2.9.0. Checksums were taken from Maven Central and cross-checked against the published.sha1.gson 2.9.0 is older than the 2.14.0 used elsewhere. This does not change the assembled node:
:p2p's isolated compile classpath sees 2.9.0, while:framework'sruntimeClasspathstill resolvesgson:2.9.0 -> 2.14.0.Version preservation across the switch
Removing a dependency also removes it as a version requester, so every version the libp2p POM declared was checked against what
:p2pnow declares. All twelve match except two deliberate differences::p2pdeclarescommons-lang3grpc-nettyrootProject.grpcVersion(1.83.0)commons-lang3is the one that nearly went wrong. libp2p declared 3.18.0 at runtime scope, which won conflict resolution against the root build's 3.4 and put 3.18.0 on:framework:runtimeClasspath.:p2pinitially pinned 3.4 — the version the module needs to compile, since the source uses the 3.0-compatiblenew BasicThreadFactory.Builder()— which left no requester for anything newer and would have shipped a 2015 release, reintroducing CVE-2025-48924 (ClassUtils.getAbbreviatedNameuncontrolled recursion, fixed in 3.18.0).:p2pnow declares 3.18.0.Verified with
./gradlew :framework:dependencyInsight --configuration runtimeClasspath:gsonat 2.14.0 andcommons-lang3at 3.18.0 — the same versions the node shipped before.Module layout — review feedback
Four changes came out of review and are separate commits.
Tests moved to
p2p/src/test. Covered above. Beyond matching whatcommonandpluginsdo, it deleted two workarounds that existed only because:p2phad no test sourceSet — the:frameworktest-classpath SDK re-declaration plus dom4j mirror, and the jacocoadditionalClassDirsbolt-on. The relocated tests compiled first try with both removed, which is the evidence that neither was needed. It also lets Gradle run:p2p:testand:framework:testin parallel.The
examplesourceSet is gone.DnsExample1,DnsExample2andImportUsingdocumented how an embedder configures this module, but they only compiled — each ended in awhile (true)loop, bound a fixed port and pointed at live seed nodes, so nothing they showed was checked. Two of them carried real defects (TestMessageis not serializable, soByteArray.fromObjectreturns null andChannel.sendcloses the channel;DnsExample1held a signing private key in copyable code).Porting them line by line would give three slow, network-dependent tests.
ExampleUsageTestinstead pins the contract they advertised — those configuration shapes are still accepted and still mean what the comments said. External embedders copy them, so a renamed setter is a breaking change even though nothing here calls them.StartAppmoved tosrc/main/javaand stays as the standalone entry point. Three things surfaced:org/tron/p2p/example/**is excluded from the module's jacoco report alongside**/protos/**; the two parsing helpers it owns are package-private and covered byStartAppArgsTest, so the exclusion does not take regression protection with it.java -jarneeds a fat jar. An intermediate commit putMain-Classon the plain jar, which is thin — the entry point resolved and then died onNoClassDefFoundError: org/apache/commons/cli/ParseException. Worse than declaring nothing, since it advertises support that cannot work. The plain jar declares none again and fails honestly with "no main manifest attribute";buildStandaloneJarproducesp2p-standalone.jarwith the runtime classpath bundled, wired exactly like:framework'sFullNode.jarand:plugins'Toolkit.jar— sameartifacts { archives(...) }, same-PbinaryRelease=falseopt-out, same Bouncy Castle signature and dnsjava SPI exclusions. Verified by running both jars.It also carried a real bug:
--trust-ipsis declaredip[,ip[...]]but resolved the whole comma-separated value as one hostname, so with more than one address none of the listed peers became trusted. Fixed, and pinned byStartAppArgsTest.p2p/README.mdadded, promoted fromsrc/example/resources/README.mdwhere no reader would find it. It still read as upstream's document: four source links pointed at github.com/tronprotocol/libp2p, the invocations namedlibp2p.jar, and the prose described libp2p as a standalone project. Links are module-relative now, the prose is about this module, and the header states the provenance once — the single remaining upstream link — and says which of the two jars to use for what.Duplicated versions removed.
protobufVersion3.25.8 was declared in both:protocoland:p2p; checkstyle 8.7 sat in a localversionsmap in:frameworkand:pluginsand as a literal in:p2p. Both move to the rootextbesidegrpcVersionandnettyVersion, and all four modules reference them — removed rather than relocated. Resolution unchanged:protobuf-java:3.25.8on:p2p's compile classpath..protofiles moved tosrc/main/proto.src/main/protoswith an explicitsrcDiroverride builds fine, but IDEA's protobuf plugin does not read that override — it resolves against the plugin's default path — soimport "Discover.proto"and every type it brings in showed as unresolved in the editor. The override is gone; generated sources still land insrc/main/java/org/tron/p2p/protosand are still gitignored.Flagging for a follow-up:
:protocolhas the identical setup and presumably the identical symptom, so the two modules now differ. Aligning it is the same one-directory rename, but it is core java-tron with asrc/main/geninterplay and does not belong here.logback.xml.exampledeleted. It came from libp2p as a standalone project; inside java-tronframework/src/main/resources/logback.xmlis what applies, and the sample was being packaged into the jar for no reason.Not done, by request: moving
Connect.proto/Discover.protointo:protocol. It would make:p2pdepend on:protocoland cost the module its leaf position in the dependency graph — currently it has zero project dependencies, which is what keeps vendored code from reaching back into java-tron internals and keeps upstream re-contribution possible.Tests
All 23 of v2.2.9's own test files are ported, plus 4 new ones. Two upstream tests were unreliable by construction and were fixed rather than carried over as-is:
NetUtilTest.testGetIPcalled three public IP-echo services and asserted all three returned the same string — a network dependency, and a coin flip on any host with more than one egress address. It now runs against a loopbackHttpServer, which exercises the same fetch/parse/validate path deterministically and covers the rejection branches too. (libp2p's own CI never ran its tests, so this had not shown up.)ConnPoolServiceTest.getNodes_orderByUpdateTimeDescasserted thatgetNodes()returns nodes ordered byupdateTimedescending.getNodes()sorts, truncates tomax(limit * 10, 50)candidates, and then callsCollections.shuffle()— so with two nodes that assertion passes about half the time. It now asserts membership, and a new test (getNodes_prefersNewestAboveCandidateSize) covers the descending sort where it is actually observable: above the candidate bound.One import was dropped:
NodeHandlerTesthad an unusedorg.checkerframework.checker.units.qual.Nimport — an IDE auto-import artifact that does not resolve on this classpath.Coverage: p2p is at 78.62% line coverage (2,990/3,803), 79.32% instruction coverage (12,625/15,917), now reported by
:p2p:jacocoTestReportitself. That report also reads:framework's exec data, because framework's own tests execute p2p code and that coverage was counted while p2p's classes hung off:framework:jacocoTestReport. v2.2.9's own tests reach about 35%. Getting from there to here took two rounds — the second was forced by the CI coverage gate and is written up in its own section below.Two pre-existing test defects were fixed along the way, because they made coverage depend on fork scheduling rather than on what the tests assert:
ConnPoolServiceTestandSocketTestbound fixed ports (10000 / 10001).PeerServer.startonly logs on bind failure, so a collision let them pass while exercising nothing. Both now take a free port fromPublicMethod.chooseRandomPort(), which is what java-tron's own tests use.NodeTableTestreadParameter.p2pConfigwithout ever setting it, so it depended on an earlier class in the same fork having done so. Running the class on its own failed all eleven methods — on this branch and on the unmodified one alike. It now sets up and restores its own config.Where a collaborator is genuinely external — the Aliyun SDK, process-wide
ChannelManagerstate — it is mocked, so the logic under test is real and only the transport is faked. What remains uncovered is code that needs a live connection:ConnPoolService.onConnect/onDisconnect/onMessage,NodeDetectService,PeerClient,Channel.init/send. Upstream's ownSocketTestfor exactly that is entirely commented out, so it would need integration tests with real channels rather than more unit tests.Defects found while testing (not fixed here)
Two instances of the same shape: a DNS parse helper throws an unchecked exception past a
catch (DnsException)that was clearly written to tolerate unparseable input, so one malformed TXT record aborts the whole operation instead of being skipped.1. Short root entry.
RootEntry.parseEntrydoese.substring(rootPrefix.length())with no length guard:p2p/src/main/java/org/tron/p2p/dns/tree/RootEntry.java:67Any value shorter than the 13-character
tree-root-v1:prefix throwsStringIndexOutOfBoundsException. The caller atp2p/src/main/java/org/tron/p2p/dns/update/AwsClient.java:334catches onlyDnsException, so it escapes and abortscomputeChanges, failing the entire publish.2. Malformed base64 in a nodes entry.
Algorithm.decode64callsBase64.getUrlDecoder().decode()directly:p2p/src/main/java/org/tron/p2p/dns/tree/Algorithm.java:121which throws
IllegalArgumentExceptionon malformed input.NodesEntry.parseEntryconverts onlyInvalidProtocolBufferExceptionandUnknownHostExceptionintoDnsException, so theIllegalArgumentExceptionescapes both it and theDnsException-only catch atp2p/src/main/java/org/tron/p2p/dns/update/AliClient.java:138, aborting the wholecollectRecords— and with it thedeploy()that called it.A third, latent instance:
BranchEntry.parseEntry(p2p/src/main/java/org/tron/p2p/dns/tree/BranchEntry.java:19) does the same unguardedsubstring(branchPrefix.length()), and unlike its siblings does not even declarethrows DnsException. Its only caller checkstxt.startsWith(branchPrefix)first, so it is not currently reachable with a short string — a hazard for the next caller, not a live bug.LinkEntry.parseEntryshows the correct shape: prefix check, length check, andcatch (RuntimeException)around the base32 decode.Reachability — these are operator-side, not peer-reachable
Traced every path that reaches these parsers:
Message.parsecatch (Exception)P2pPacketDecodercatch (Exception)RandomIterator.next()catch (Exception)aroundsyncRandom()(ClientTreehas no catch of its own)catch (DnsException)So no remote peer can trigger these. Both live instances are on the operator's own publish path: they abort a DNS publish for whoever runs it, they do not give an attacker anything. That is worth stating plainly, because "unchecked exception on a parse path" in a networking module reads much worse than it is here.
All three are pre-existing in libp2p and out of scope for a no-functional-changes PR, so they are reported rather than fixed.
Instance 2 is now pinned by a test.
AwsClientRecordsTest.malformedBase64InANodesEntryAbortsTheWholeCollectionfeeds a corrupt nodes entry throughcollectRecordsand asserts the uncheckedIllegalArgumentExceptionescapes — that is, it asserts the defect, not the behaviour we want. It fails loudly if the escape is ever closed, which is the point. The tests covering instances 1 and 3 use inputs that reach the intendedDnsExceptionpath and document the unchecked one in a comment.Known security issues — pre-existing, deferred
These exist in libp2p today and are unchanged by this PR. Internalizing the source is what makes them fixable in-tree; each gets its own follow-up:
compressPubKeydrops leading zerosAwsClientswallowsInterruptedExceptionwithout restoring the interrupt flag (dns/update/AwsClient.java, now commented rather than silently empty)Mixing any of them in would break the "no functional changes" claim, which is the only thing that makes a diff this size reviewable.
Build wiring cleanups
Build-only, no vendored code touched.
nettyVersionextracted to the rootext.:frameworkand:p2peach declarenetty-codec-protobufexplicitly (see H1) and each carried the literal4.2.15.Final. Netty itself is declared nowhere — it arrives transitively viagrpc-netty, which:p2ptracks asrootProject.grpcVersion. So a grpc bump moves Netty while those two literals stay put, which is exactly the mismatch H1 describes. The version now sits next togrpcVersionso the coupling is visible in one place. Resolution is unchanged:4.2.15.Finalon both compile classpaths.:frameworknow declares its direct:p2pdependency. framework usesorg.tron.p2pin 17 files undersrc/main/javabut declared nothing, relying on a three-hop transitive through:common -> :crypto -> :chainbase.That export is not removable here, and it is worth being precise about why:
CommonParameterpublishesP2pConfig p2pConfigandPublishConfig dnsPublishConfigas public@Getterfields, so p2p types are part of:common's own API surface andapi project(":p2p")is forced. Narrowing it toimplementationwould break every caller ofgetP2pConfig(). Genuinely decoupling the graph means moving those two fields out ofCommonParameter, which is a functional refactor and deliberately out of scope. Until then,:p2pstays on the compile classpath of every module in the graph — as the externallibp2partifact did before this PR, for the same reason.What is fixable now is the undeclared direct use, so it is declared.
api, notimplementation— framework does re-export p2p types:P2pEventHandlerImpl extends org.tron.p2p.P2pEventHandler,HelloMessage.getFrom()returnsorg.tron.p2p.discover.Node,PeerManager.add/removetakeorg.tron.p2p.connection.Channel, andArgs.loadDnsPublishConfigreturnsPublishConfig. Declaring itimplementationwould compile today only because the transitiveapichain still supplies those types to consumers — the moment that chain is narrowed, it breaks. No resolution change either way.The coverage gate, and what it took to pass it
Worth reading before the verification table, because it is the one place where this PR's numbers moved a lot.
Wiring p2p into
:framework:jacocoTestReport(see Coverage attribution above) is what makes the module visible to coverage at all. It also puts 16,568 instructions at 60.91% into the repo-wide denominator, and CI's overall-delta gate failed on exactly that:The cause was fully attributable. Downloading both jacoco reports from CI and recomputing:
Every point of the drop came from vendored code now being counted — java-tron's own coverage went slightly up. So the choice was between hiding vendored code from the metric and actually testing it. This PR does the latter: p2p went from 60.91% to 78.88% instruction coverage, +3,168 covered instructions, and the delta gate now passes with the vendored code fully counted.
The added tests target the parts that need no live connection: the web3j copy (
Numeric,Strings,Hash,Sign,ECKeyPair,ECDSASignature), every kad discovery message through its own wire bytes,P2pPacketDecoder's drop paths for hostile datagrams,Channel's send guard and exception classification,ChannelManager.processPeer's admission branches (ban list, global cap, per-IP cap, duplicate node id),Tree's signing and TXT output,AwsClient's Route53 batching limits,AliClient.deploy's threshold decision, andP2pService, which had no test at all.What remains uncovered is still code that needs a real socket:
PeerClient,P2pChannelInitializer,Channel.init/sendagainst a live peer.Scope — what this PR does not change
.protofiles move verbatim)org.tron.p2p.**), so imports in dependent code are untouchedp2premains usable standalone —java -cp … org.tron.p2p.example.StartAppruns the module on its own; seep2p/README.mdVerification
:p2p:build(compile, test, checkstyle, jar)java -jar p2p-standalone.jar --help:framework:compileJava --rerun-tasksproject :p2pon framework runtimeClasspathlibp2partifactsgson:2.9.0 -> 2.14.0:p2p:checkstyleMain:framework:checkstyleTest:p2p:checkstyleTest:p2p:testcommons-lang3unchanged at 3.18.0implicit_dependencywarnings:framework:testThree tests failed on their first attempt in the full local run and passed on retry, all pre-existing and none of them a p2p test:
ValidateMultiSignContractTest.testTip854RejectsMalformedCalldata,AllowTvmLondonTest.testBaseFee,AllowTvmLondonTest.testStartWithEF. They also fail on the pre-change tree.:framework:testitself did not fail.Separately, a
BindExceptioninMetrics.initcan cascade through four framework test classes (SRMetricsTest,PrometheusApiServiceTest,JsonrpcServiceTest,RpcApiServicesTest) that each bind the same hard-coded Prometheus port 9527 while the test task runs up to 4 parallel forks. Verified pre-existing by running those four classes together with no p2p tests in the run: it reproduces, and hits a different class each time. The test classes this PR adds change how classes distribute across forks, which can make two of them land concurrently — the race is java-tron's, not p2p's, but this PR makes it more likely to surface.Known remaining CI-reliability risk
Two ported upstream tests still need live DNS against a third-party zone and are left as-is rather than disabled, since they exercise real discovery behaviour:
RandomTest.testRandomIteratorandSyncTestboth synctree://…@nile.trondisco.netthrough hard-coded public resolvers.RandomTestalready failed once in a local batch run and only went green via thetest-retryplugin (maxRetries = 5). On a runner with restricted egress, or if that DNS tree is re-published or retired, they fail permanently.LookUpTxtTestin the same package already@Ignores its network tests, so that is the precedent if reviewers would rather these be skipped than retried. Disabling them costs roughly 1.5 points of coverage, which still clears both gates.The fixed-port problem that used to sit here —
ConnPoolServiceTestandSocketTestbinding 10000 / 10001 — is fixed in this PR; see Tests above.