build(p2p): internalize libp2p as local module

Closed
BA
5mo ago
+18295-28

What

Internalize io.github.tronprotocol:libp2p:2.2.7 source code as a new p2p/ gradle submodule, replacing the external Maven dependency. No functional changes to P2P behavior, protocol, or consensus.

Why

Upstream tronprotocol/libp2p activity has been intermittent: the repo saw sparse maintenance from 2023-04 to 2024-10 (0–3 commits/month, several months with no commits), with renewed activity in 2025-03 (v2.2.6) and 2025-08 to 2025-11 (v2.2.7). Cross-repo maintenance creates friction:

  • Version coordination overhead: Modifying P2P behavior requires publishing libp2p first, then updating java-tron's dependency version
  • Debugging difficulty: IDE cannot jump into external artifact source; requires downloading source JARs
  • CI dependency: Build depends on external Maven/JitPack artifact availability
  • Industry alignment: geth (go-ethereum) maintains its p2p/ package within the monorepo; this follows the same pattern

Internalization makes future P2P changes a single PR instead of a cross-repo dance, and does not preclude back-porting individual commits to upstream libp2p when appropriate.

Changes

Commit 1: chore(p2p): add libp2p v2.2.7 source as p2p module

  • Copy 90 main Java files (including embedded org.web3j crypto utils) + 2 proto files + 26 test files from tronprotocol/libp2p tag v2.2.7
  • Create p2p/build.gradle with protobuf plugin configuration
  • Add p2p/lombok.config to temporarily override root logger field name (log vs logger)
  • Move example code (StartApp) to src/test/java (not packaged in JAR, but available for reference)
  • Register module in settings.gradle, update gradle/verification-metadata.xml

Commit 2: style(p2p): replace log field with logger

  • Replace all @Slf4j-generated log references with logger (35 files) to match root lombok.config (lombok.log.fieldName=logger)
  • Remove temporary p2p/lombok.config override

Commit 3: build(common): switch from external libp2p to local p2p module

  • common/build.gradle: replace 17 lines (Maven dependency + 11 exclude rules) with single api project(':p2p')
  • Remove io.github.tronprotocol:libp2p:2.2.7 entry from gradle/verification-metadata.xml

Commit 4: style(p2p): fix checkstyle violations and enable checkstyle

  • Apply google-java-format for bulk formatting (indentation, imports, whitespace)
  • Manual fixes: lambda indentation, line length wrapping, empty catch blocks, star imports
  • Enable checkstyle plugin with proto-generated code excluded (exclude '**/protos/**')
  • ~1374 of 1454 violations were from proto-generated code; after exclusion only 80 remained in handwritten code

Commit 5: fix(p2p): address review findings

  • Replace BasicThreadFactory.builder() (requires commons-lang3 3.12+) with new BasicThreadFactory.Builder() (available since 3.0) to prevent implicit global version upgrade from 3.4 to 3.18.0
  • Remove unused grpc-core dependency; retain grpc-netty as Netty transport provider (p2p uses 30+ Netty imports for TCP/UDP but zero gRPC imports)
  • Remove gRPC protoc plugin from protobuf config (p2p protos define only messages, no services)
  • Add p2p/.gitignore to exclude proto-generated code for all developers

Commit 6: fix(p2p): replace Math with StrictMath and fix flaky network tests

  • Replace Math.min/max with StrictMath.min/max in 5 files to satisfy CI check-math rule (integer operations, results identical)
  • Rewrite NetUtilTest.testGetIP with mocked URLConnection — original test called 3 external IP services which failed in CI (libp2p's own CI never ran tests with -xtest)
  • New mock tests cover: valid IP response, connection failure, invalid IP, empty response
  • Remove testExternalIp (covered by mock tests), fix testGetLanIP to not depend on external connectivity

Commit 7: test(p2p): improve test coverage from 35% to 76%

Add comprehensive unit tests to meet the CI coverage gate (changed files > 60%, overall delta < -0.1%).

  • 27 new test files across connection/, dns/, discover/, utils/web3j, stats, exception, plus P2pService and P2pConfig
  • Exclude proto-generated code from jacoco coverage (consistent with the checkstyle exclusion)

Commit 8: chore(p2p): move tests to framework, introduce example sourceSet

Addresses review feedback from @xxo1shine: align with java-tron's project-wide convention of consolidating module unit tests under framework/src/test/.

  • Move 53 unit test files from p2p/src/test/java/ to framework/src/test/java/ (+4 from org.web3j package); 100% git renames, package names preserved
  • Add testImplementation for AWS Route53 + Aliyun DNS SDKs in framework/build.gradle (required by 3 DNS-publish tests moved here; these SDKs are implementation-scope in :p2p so not transitively visible). Minimal excludes only.
  • Fix pre-existing flaky DnsManagerTest NPE by adding @Before initP2pConfig() (test result depended on whether prior tests had left Parameter.p2pConfig non-null)
  • Drop p2p's jacocoTestReport block (no local tests now; coverage is captured by :framework:jacocoTestReport)
  • Move reference examples (StartApp, DnsExample1/2, ImportUsing, README.md) to a dedicated p2p/src/example/ sourceSet — compiles for API validation but not packaged in p2p.jar and not executed as tests

Commit 9: fix(p2p): drop duplicate srcDir calls in example sourceSet

Removes the explicit srcDir 'src/example/{java,resources}' calls inside the example sourceSet — Gradle already defaults custom-sourceSet srcDirs to those paths, so the explicit calls duplicate them and broke :p2p:processExampleResources with "Entry README.md is a duplicate" on CI.

Key Design Decisions

DecisionChoiceRationale
Module namep2pMatches package org.tron.p2p, geth p2p/ convention, project short-name style (common, crypto, etc.)
Proto filesStay in p2p moduleP2P protocol is separate from chain protocol in protocol/; avoids coupling
Unit test locationframework/src/test/Project convention — actuator, chainbase, consensus, common etc. all keep their unit tests under framework
Example codeDedicated p2p/src/example/ sourceSetCompiled for API validation, not packaged in jar, not run as tests; preserves passive library usability
commons-lang33.4 (global consistent)Rewrite builder() → new Builder() (3.0 API) instead of upgrading globally
grpc-netty retainedYesProvides Netty transitively; same as original libp2p; version auto-aligns with protocol module
grpc-core + grpc plugin removedYesp2p protos have no service definitions, zero io.grpc imports in source
Checkstyle proto exclude**/protos/**Proto-generated code has ~1374 violations; consistent with protocol module approach
Math → StrictMathAll 5 call sitesCI check-math rule; integer min/max results are identical
testGetIP mock rewriteURLConnection mockOriginal depended on external services; libp2p CI never ran tests

Scope

  • Does NOT change any P2P protocol behavior or message formats
  • Does NOT change consensus rules or transaction validation logic
  • Does NOT modify protobuf definitions
  • Does NOT require hard fork
  • All files importing org.tron.p2p are unchanged (package name preserved)
  • Existing nodes upgrading need only git pull and rebuild
  • p2p module remains passively usable as a standalone library: self-contained code, default p2p.jar artifact, examples kept as compilable reference

Known Issues (pre-existing in libp2p)

The following issues exist in the original libp2p v2.2.7 codebase and are NOT introduced by this PR. They will be addressed in follow-up PRs:

Critical:

  • Node ID generation uses weak PRNG (java.util.Random instead of SecureRandom) — eclipse attack vector
  • Handshake lacks cryptographic authentication (self-reported node ID, no signature verification)
  • Neighbor injection has no count limit per response

High:

  • TOCTOU race in ChannelManager.handMessage (double onConnect)
  • DNS sync uses predictable java.util.Random
  • Algorithm.compressPubKey drops leading zeros from public key X-coordinate

Medium (from Cubic AI review):

  • Node.equals compares byte[] via new String(id) instead of Arrays.equals
  • Channel.hashCode() NPE when inetSocketAddress is null
  • InterruptedException swallowed in DiscoverServer and PeerClient
  • Several minor NPE risks and unclosed resources

Test

  • p2p module compile + example sourceSet compile: PASS
  • Full build: BUILD SUCCESSFUL (~14m, 2780 tests, 0 real failures; 1 pre-existing flaky testPbft passed on retry)
  • checkstyleMain + checkstyleTest: PASS for all modules including p2p
  • check-math: PASS
  • Node integration test: FullNode started with mainnet config, P2P handshake protocol verified by message exchange (HelloMessage / TOO_MANY_PEERS / channel close)
  • Flaky net tests (PbftMsgHandler, TronStatsManager): confirmed pre-existing, pass on retry