Skip to content

fix(net): hash inventory items with a per-process key - #7107

Merged
kuny0707 merged 3 commits into
tronprotocol:release_v4.8.3from
Little-Peony:fix/inventory-item-keyed-hash
Nov 13, 2026
Merged

kuny0707 merged 3 commits into
tronprotocol:release_v4.8.3from
Little-Peony:fix/inventory-item-keyed-hash

Conversation

@SeriousCoding789

Copy link
Copy Markdown
Contributor

What does this PR do?

Changes Item.hashCode() to a SipHash-2-4 hash of the full 32-byte id, keyed by two SecureRandom values drawn once per process. The hash is computed once in the constructor and cached in a final field. Item.equals(), Sha256Hash.hashCode() and all callers are unchanged.

Why are these changes required?

Item is the key of the inventory caches and maps in AdvService and PeerConnection. A keyed hash over the full id gives these collections a uniform key distribution. Computing it once keeps lookups in the periodic fetch and spread loops as cheap as before.

This PR has been tested by:

  • Unit Tests
    • New ItemTest:
      • Equal items share a hash code.
      • An Item built from a BlockId equals one built from a Sha256Hash with the same bytes, and the two share a hash code.
      • 10,000 distinct ids produce distinct hash codes.
    • ./gradlew :framework:test --tests "org.tron.core.net.*": 361 tests in 41 classes, all passed (JDK 17 / aarch64).
    • ./gradlew :framework:checkstyleMain :framework:checkstyleTest: passed.
  • Manual Testing
    • Microbenchmark of the inventory caches with random ids shows no measurable overhead: 0.19–0.26 s before the change, 0.20–0.27 s after.

Follow up

None.

Extra details

  • No wire-format, database, configuration, or consensus change. No fork is required, and nodes on different versions interoperate.
  • Item is only an in-memory key in the network layer. It is never persisted, serialized, or compared across processes, so a per-process key does not affect behavior between nodes.
  • Sha256Hash.equals() and BlockId.equals() both compare id bytes, and the new hash is computed from the same bytes, so the equals/hashCode contract holds.
  • No code depends on the numeric hash value or on the iteration order of the Item-keyed maps.
  • No new dependency; Guava's Hashing is already used in the project.

Inventory ids come from peers, so Item no longer derives its hash
code from the id bytes directly. It computes SipHash-2-4 over the 32
id bytes once in the constructor, keyed by two SecureRandom values
drawn when the class loads. equals() and Sha256Hash.hashCode() are
unchanged.
Inventory ids come from peers, so Item no longer derives its hash
code from the id bytes directly. It computes SipHash-2-4 over the 32
id bytes once in the constructor, keyed by two SecureRandom values
drawn when the class loads. equals() and Sha256Hash.hashCode() are
unchanged.

private static HashFunction newIdHash() {
SecureRandom random = new SecureRandom();
return Hashing.sipHash24(random.nextLong(), random.nextLong());

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Could you share the benchmark code and clarify whether the timings include Item construction? SipHash is computed in the constructor, so cache operations on prebuilt items would miss that added cost.

Please also compare construction plus cache lookup/insertion, including allocation and GC overhead, using the same workload and JVM warmup before and after the change. Results on both JDK 8/x86_64 and JDK 17/aarch64 would help assess the performance impact.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The timings in the description include construction: new Item(...) is inside the timed loop, and only id generation is outside. They were single cold runs, though, so I reran a warmed before/after comparison (code below).

Base 37ea60a8bc vs this PR, each in its own JVM; 5 × 1 s warmup and 5 × 1 s measurement; JDK 17.0.18 / aarch64, -XX:+UseZGC -Xms2g -Xmx2g. Allocation is the bytes allocated by the measuring thread per item.

Per item Before This PR
new Item(...) 22.6–23.1 ns, 40 B 46.4–48.2 ns, 136 B
Construction + cache lookups/insertions (INVENTORY path) 671–1494 ns, 447 B 774–1272 ns, 543 B¹

¹ 695 B in one of the five JVMs.

Construction costs about 24 ns and 96 B more per item, because Guava's hashBytes allocates a hasher, a buffer and the HashCode. With the cache operations included, allocation is 21% higher and the time difference is within run-to-run variation. On that path ZGC ran 8 times per 5 s measurement before and 4–8 times with this PR.

I don't have a JDK 8 / x86_64 machine at hand, so there are no numbers for it yet; the harness runs there unchanged.

hash, type and time are assigned only in the constructor.
@kuny0707
kuny0707 merged commit 0a1c444 into tronprotocol:release_v4.8.3 Nov 13, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

7 participants