Conversation
+ sniff into file
📝 WalkthroughWalkthroughThis pull request adds file-based transaction sourcing and dumping capabilities to the chain stresser tool. The sniffer can now read transactions from a file instead of querying via RPC in real time, or write transactions to a file instead of replaying them. Configuration flags Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@cmd/chain-stresser/main.go`:
- Around line 572-575: The dump-only branch currently waits for <-sniffer.Done()
and returns nil, which masks sniffer failures; after waiting on sniffer.Done()
check sniffer.Errors() and if it returns a non-nil error return that error
instead of nil so failures (e.g., exhausted RPC retries) propagate; update the
branch that checks replayCfg.ToFile and uses sniffer.Done() to inspect
sniffer.Errors() and return it when present.
In `@replay/sniffer.go`:
- Around line 125-140: The code that builds a virtual block from s.cfg.FromFile
uses a nil block sentinel when no txs are read, but because EndHeight==0 the
outer loop never exits; instead of setting block = nil when len(block.Txs) == 0,
stop the producer loop immediately: when readTxFromFile yields no txs (len==0),
break out of the surrounding production loop or return from the producing method
so Done() can close cleanly; adjust the logic around
BlockFromFileTxNum/readTxFromFile to return/terminate rather than relying on a
nil block sentinel.
- Around line 207-229: In readTxFromFile (method on Sniffer) replace the two raw
s.txsFile.Read calls with io.ReadFull to ensure buffers are completely filled
and check the returned error; treat a zero-byte length read as io.EOF and
propagate other read errors; validate the decoded uint64 length against a
reasonable maximum (e.g., a configured MaxTxSize or a constant) before
allocating txnBuf to avoid unbounded allocations, then use io.ReadFull to read
the txnBuf and return any read errors instead of ignoring them.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 80f96cc8-f627-4421-8e5c-3433dd5d9602
📒 Files selected for processing (4)
cmd/chain-stresser/main.goreplay/replay.goreplay/sniffer.gostresser.go
💤 Files with no reviewable changes (1)
- stresser.go
| if replayCfg.ToFile != "" { // we just dumping txs, no replay needed | ||
| <-sniffer.Done() | ||
| return nil | ||
| } |
There was a problem hiding this comment.
Return sniffer failures in dump-only mode.
This branch only waits for Done() and then returns nil. If the sniffer exits after exhausting RPC retries, the command still succeeds and can leave a partial dump behind. Check sniffer.Errors() after Done() and return it when present.
Suggested fix
if replayCfg.ToFile != "" { // we just dumping txs, no replay needed
<-sniffer.Done()
+ select {
+ case err, ok := <-sniffer.Errors():
+ if ok && err != nil {
+ return err
+ }
+ default:
+ }
return nil
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@cmd/chain-stresser/main.go` around lines 572 - 575, The dump-only branch
currently waits for <-sniffer.Done() and returns nil, which masks sniffer
failures; after waiting on sniffer.Done() check sniffer.Errors() and if it
returns a non-nil error return that error instead of nil so failures (e.g.,
exhausted RPC retries) propagate; update the branch that checks replayCfg.ToFile
and uses sniffer.Done() to inspect sniffer.Errors() and return it when present.
| if s.cfg.FromFile != "" { // read BlockFromFileTxNum txs from file and construct virtual block | ||
| block = &comettypes.Block{ | ||
| Data: comettypes.Data{ | ||
| Txs: make([]comettypes.Tx, 0, BlockFromFileTxNum), | ||
| }, | ||
| } | ||
| for i := 0; i < BlockFromFileTxNum; i++ { | ||
| tx, err := s.readTxFromFile() | ||
| if err != nil { // end of file or err | ||
| break | ||
| } | ||
| block.Txs = append(block.Txs, tx) | ||
| } | ||
|
|
||
| if len(block.Txs) == 0 { | ||
| block = nil // signal the end |
There was a problem hiding this comment.
Stop the producer after the last file batch.
With --from-file, Line 140 only swaps in a nil block and still returns success. Because this mode is also EndHeight == 0, the outer loop keeps running and Done() never closes cleanly. Break after the final non-empty batch instead of using nil as a sentinel.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@replay/sniffer.go` around lines 125 - 140, The code that builds a virtual
block from s.cfg.FromFile uses a nil block sentinel when no txs are read, but
because EndHeight==0 the outer loop never exits; instead of setting block = nil
when len(block.Txs) == 0, stop the producer loop immediately: when
readTxFromFile yields no txs (len==0), break out of the surrounding production
loop or return from the producing method so Done() can close cleanly; adjust the
logic around BlockFromFileTxNum/readTxFromFile to return/terminate rather than
relying on a nil block sentinel.
| func (s *Sniffer) readTxFromFile() ([]byte, error) { | ||
| var ( | ||
| lenN int | ||
| txnN int | ||
| len uint64 | ||
| txnBuf []byte | ||
| ) | ||
|
|
||
| lenBuf := make([]byte, 8) | ||
| lenN, _ = s.txsFile.Read(lenBuf) | ||
|
|
||
| if lenN > 0 { | ||
| len = binary.LittleEndian.Uint64(lenBuf) | ||
|
|
||
| txnBuf = make([]byte, len) | ||
| txnN, _ = s.txsFile.Read(txnBuf) | ||
| } | ||
|
|
||
| if lenN < 8 || uint64(txnN) < len { | ||
| return nil, fmt.Errorf("sniffer error: can't read tx from file") | ||
| } | ||
|
|
||
| return txnBuf, nil |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "sniffer.go" -path "*/replay/*" -type fRepository: InjectiveLabs/chain-stresser
Length of output: 91
🏁 Script executed:
# Read the specific file and lines mentioned
sed -n '207,229p' ./replay/sniffer.go | cat -nRepository: InjectiveLabs/chain-stresser
Length of output: 671
🏁 Script executed:
# Get more context around the function
sed -n '200,240p' ./replay/sniffer.go | cat -nRepository: InjectiveLabs/chain-stresser
Length of output: 1102
🏁 Script executed:
# Check for other usages of this function and related error handling
rg "readTxFromFile" --context=3Repository: InjectiveLabs/chain-stresser
Length of output: 591
🏁 Script executed:
# Look at the entire file structure to understand context
wc -l ./replay/sniffer.goRepository: InjectiveLabs/chain-stresser
Length of output: 95
Use io.ReadFull and validate lengths before allocating slices.
A single Read on an io.Reader is not required to fill the buffer. Ignoring the returned errors here can cause short reads to be mishandled as success, silently accept truncated records as EOF, and line 221 can allocate arbitrarily large slices from an unvalidated length prefix. Replace the two Read calls with io.ReadFull, return io.EOF only when the length read returns zero bytes, and validate the transaction length against a reasonable maximum before allocating.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@replay/sniffer.go` around lines 207 - 229, In readTxFromFile (method on
Sniffer) replace the two raw s.txsFile.Read calls with io.ReadFull to ensure
buffers are completely filled and check the returned error; treat a zero-byte
length read as io.EOF and propagate other read errors; validate the decoded
uint64 length against a reasonable maximum (e.g., a configured MaxTxSize or a
constant) before allocating txnBuf to avoid unbounded allocations, then use
io.ReadFull to read the txnBuf and return any read errors instead of ignoring
them.
Adds two more flags to
tx-replaycommand:--from-filefilepath to read sniffed txs from. If omitted, will sniff txs from RPC in realtime.--to-filefilepath to store sniffed txs into. If omitted, will replay sniffed txs in realtime.When replaying from file, we batch txs into blocks by 100 tx each.
Summary by CodeRabbit
Release Notes
New Features
Bug Fixes