Skip to content

perf(evmonlyapp): hash a block's transactions before taking the sender lock - #4364

Merged
bdchatham merged 4 commits into
mainfrom
devin/1790609776-plt-1303-hash-before-sender-lock
Sep 29, 2026
Merged

bdchatham merged 4 commits into
mainfrom
devin/1790609776-plt-1303-hash-before-sender-lock

Conversation

@bdchatham

Copy link
Copy Markdown
Contributor

Backports #4235 from giga-1 to main (PLT-1303, part of PLT-1297). takeSenders keccaks every raw transaction while holding the checkedSenders lock on the execute loop, and CheckTx writes that same cache constantly, so admission stalls for the length of a block's worth of hashes (~790 µs for 1,848 txs on the original measurement).

takeSenders now computes all hashes first with hashRawTxs, which splits the block across up to GOMAXPROCS workers (at least minTxsPerHashWorker = 64 txs each, so small blocks stay on one goroutine), and the lock covers only the senderCache.take lookups. Main has since replaced the plain map with the two-generation senderCache, so the port keeps that structure and only moves the hashing out of the lock. Unlike the giga-1 version, the fan-out uses scope.Parallel instead of a bare sync.WaitGroup + go func, per the sei-tendermint conventions.

No consensus or state impact: lookups still run in block index order, so a duplicate tx in a block still resolves only the first occurrence. TestHashRawTxsMatchesKeccak256Hash pins hashRawTxs to crypto.Keccak256Hash across the single-worker and multi-worker paths; the evmonlyapp package tests pass under -race, and make lint / make fmtcheck are clean.

Link to Devin session: https://app.devin.ai/sessions/d808db90106341278344623de10b92c0
Open in Devin Desktop: https://app.devin.ai/desktop/session/d808db90106341278344623de10b92c0?variant=devin
Requested by: @bdchatham

…r lock (#4235)

One of four independent PRs from the serial-budget survey (#4234, #4235,

`takeSenders` keccaked every raw transaction **inside** the
`checkedSenders` lock, on the execute loop. The hashing is the whole
cost of the pass, and `CheckTx` writes that same map about 64,500 times
a second — so admission stalled for the length of a block's worth of
hashes.

The hashes are now taken across the pool first, and the lock covers only
the map lookups.

Measured locally with a throwaway `BenchmarkHashRawTxs` (not included in
this PR). 1,848 transactions, 10-core machine:

| | time |
|---|---|
| keccak on the loop (what it did) | 791 µs — 428 ns/tx |
| across the pool (what it does) | 231 µs |

**560 µs off a 28.83 ms loop, ~1.9%.** A validator at `GOMAXPROCS` 32
has more of the pool to spread it over, so this is the floor. It costs
one 59 KB slice of hashes per block.

Two things the survey did not have: the keccak is 428 ns/tx, slightly
above its 300–420 ns estimate; and the lock was held across the whole
pass, which it did not mention and which is the part that touches
admission latency rather than throughput.

- Lookups still run in index order, so a block carrying the same
transaction twice resolves the first and leaves the second unknown —
unchanged.
- `TestHashRawTxsMatchesKeccak256Hash` pins `hashRawTxs` to
`crypto.Keccak256Hash` over 0, 1, 2, 17 and 1,848 inputs. This matters
more than it looks: the map is keyed by `tx.Hash()`, so a hash that
differs by one byte would silently lose every sender and make the
executor recover all 1,848 signatures instead.
- One hasher is reset per worker rather than allocated per transaction.
- Ranges are contiguous, not strided, so workers do not share cache
lines while writing.
- `-race` clean on the package.

Link to Devin session:
https://app.devin.ai/sessions/0952787e9df84a7994fda23fea5db400
Open in Devin Desktop:
https://app.devin.ai/desktop/session/0952787e9df84a7994fda23fea5db400?variant=devin
Requested by: @bdchatham

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
(cherry picked from commit abeab64)
@devin-ai-integration

Copy link
Copy Markdown
Contributor

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

@cursor

cursor Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

PR Summary

Low Risk
Performance-only refactor of sender-cache lookups with a regression test pinning hash semantics; no consensus or state changes.

Overview
Moves transaction Keccak hashing out of the checkedSenders lock during FinalizeBlock's takeSenders phase so CheckTx admission is not blocked for an entire block's worth of hashes.

takeSenders now precomputes hashes via hashRawTxs, which uses a reused Keccak state and fans out across up to GOMAXPROCS workers through scope.Parallel when the block has more than 64 transactions (otherwise it stays single-threaded). The mutex only covers cache take lookups, preserving block order and the same hash keys as before.

Adds TestHashRawTxsMatchesKeccak256Hash to lock hashRawTxs output to crypto.Keccak256Hash on both single- and multi-worker paths.

Reviewed by Cursor Bugbot for commit 48978dd. Bugbot is set up for automated code reviews on this repo. Configure here.

@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

The latest Buf updates on your PR. Results from workflow Buf / buf (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedSep 29, 2026, 10:40 AM

@codecov

codecov Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 66.56%. Comparing base (b7ab9ac) to head (48978dd).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #4364      +/-   ##
==========================================
- Coverage   67.73%   66.56%   -1.18%     
==========================================
  Files        2172     2046     -126     
  Lines      168523   156057   -12466     
==========================================
- Hits       114144   103874   -10270     
+ Misses      54370    52174    -2196     
  Partials        9        9              
Flag Coverage Δ
sei-chain-pr 88.01% <100.00%> (?)
sei-db 74.81% <ø> (ø)
sei-db-state-db ?

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
sei-tendermint/internal/evmonlyapp/app.go 87.63% <100.00%> (+2.04%) ⬆️

... and 201 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I found nothing that needs fixing. The PR moves transaction hashing out of the checkedSenders lock and runs it in parallel with scope.Parallel. Lookups still happen in block order under the lock, so duplicate handling stays the same, and a new test checks that the hashes match crypto.Keccak256Hash for block sizes both below and above the parallel threshold.

Findings: 0 blocking | 0 non-blocking | 0 posted inline

Blockers

  • None at the file/PR level.

Non-blocking

  • None at the file/PR level.

@bdchatham
bdchatham added this pull request to the merge queue Sep 28, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Sep 28, 2026
…1303-hash-before-sender-lock

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

# Conflicts:
#	sei-tendermint/internal/evmonlyapp/app_test.go
…1303-hash-before-sender-lock

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

# Conflicts:
#	sei-tendermint/internal/evmonlyapp/app.go
@bdchatham
bdchatham added this pull request to the merge queue Sep 29, 2026
Merged via the queue into main with commit b34bf7f Sep 29, 2026
60 checks passed
@bdchatham
bdchatham deleted the devin/1790609776-plt-1303-hash-before-sender-lock branch September 29, 2026 17:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants