Skip to content

chore(deps): security #3296

Merged
tac0turtle merged 4 commits intomainfrom
marko/sec_deps
Apr 29, 2026
Merged

chore(deps): security #3296
tac0turtle merged 4 commits intomainfrom
marko/sec_deps

Conversation

@tac0turtle
Copy link
Copy Markdown
Contributor

@tac0turtle tac0turtle commented Apr 28, 2026

Overview

Summary by CodeRabbit

Release Notes

  • Tests

    • Enhanced EVM readiness validation with improved timeout handling and authentication
    • Added new Docker-based benchmark node implementations for performance testing
    • Updated benchmark suite infrastructure for better metrics collection
  • Chores

    • Upgraded core testing dependencies to v0.19.0
    • Migrated Docker client dependencies to newer versions for improved compatibility

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 28, 2026

📝 Walkthrough

Walkthrough

The PR updates EVM test infrastructure by changing genesis hash/state root constants, upgrading tastora dependency to v0.19.0 across modules, refactoring readiness checks to use eth_getBlockByNumber and engine_forkchoiceUpdatedV3, migrating Docker client APIs from docker/docker to moby/moby, and adding new Spamoor and VictoriaTraces benchmark nodes for e2e benchmarking.

Changes

Cohort / File(s) Summary
Genesis Configuration
execution/evm/test/execution_test.go
Updated hardcoded GENESIS_HASH and GENESIS_STATEROOT constants used in chain initialization.
Dependency Updates
execution/evm/test/go.mod, test/docker-e2e/go.mod, test/e2e/go.mod
Bumped github.com/celestiaorg/tastora to v0.19.0 across modules; updated Cosmos SDK ecosystem, Docker/Moby client dependencies, and other indirect dependencies.
Readiness Check Refactoring
execution/evm/test/test_helpers.go
Replaced net_version polling with eth_getBlockByNumber genesis hash validation; added JWT bearer token pre-decoding; increased HTTP timeout from 100ms to 500ms; validates forkchoice response for payloadStatus.status == "VALID".
Docker API Migration
test/docker-e2e/resiliency_test.go
Migrated Docker client from github.com/docker/docker to github.com/moby/moby/client for exec and log operations; updated exec lifecycle calls to ExecCreate, ExecAttach, ExecStart.
New Benchmark Node Implementations
test/e2e/benchmark/spamoor_node.go, test/e2e/benchmark/victoriatraces_node.go
Added Spamoor and VictoriaTraces container-backed benchmark nodes with Docker lifecycle management, port mapping detection, HTTP readiness polling, and network configuration.
Benchmark Suite Integration
test/e2e/benchmark/suite_test.go
Integrated new Spamoor and VictoriaTraces nodes via builder functions; replaced direct package instantiation with test-local constructs in local and external environment setup.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • chatton
  • julienrbrt

Poem

🐰 A genesis hash reborn, with traces dancing high,
Spamoor nodes spin containers as benchmarks fly,
Docker APIs migrate, Docker midnight to morn,
Readiness checks now validate the forkchoice sworn,
Fresh benchmarks bloom—e2e tests can breathe anew!

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is entirely empty with only the template comments present and no substantive content explaining the changes, objectives, or rationale. Add a complete description explaining which dependencies were upgraded, why the upgrades are necessary (security fixes), what changes were made across the affected files, and any testing performed.
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The PR title 'chore(deps): security' is vague and lacks specificity about which dependencies were updated or why the security changes matter. Provide a more descriptive title that specifies the key dependencies updated (e.g., 'chore(deps): upgrade tastora and moby dependencies for security') or the nature of the security improvements.
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch marko/sec_deps

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Apr 28, 2026

Claude finished @tac0turtle's task in 2m 35s —— View job


Code Review

  • Gather context and read changed files
  • Review dependency updates (go.mod/go.sum)
  • Review new files (spamoor_node.go, victoriatraces_node.go)
  • Review modified test files
  • Post comprehensive review

Summary

This PR makes two distinct changes: (1) security/maintenance dependency bumps, and (2) new benchmark test infrastructure. The dep migration from github.com/docker/docker (+incompatible) to the modular github.com/moby/moby/api + github.com/moby/moby/client is the right direction. Most changes are clean; a few issues worth addressing below.


Dependency Changes

Positive changes:

  • github.com/docker/docker v28.x+incompatible and github.com/moby/moby v27/v28+incompatible correctly replaced with github.com/moby/moby/api v1.54.1 + github.com/moby/moby/client v0.4.0 — the +incompatible suffix was a maintenance smell.
  • getsentry/sentry-go v0.27.0 → v0.31.1 (meaningful security/reliability improvement).
  • go.etcd.io/bbolt alpha → v1.4.0 stable.
  • celestiaorg/tastora unified to v0.19.0 across all modules.
  • CosmosSDK ecosystem (errors, log, math, store, x/tx) minor version bumps.

Observation: github.com/bcp-innovations/hyperlane-cosmos v1.0.1, github.com/bytedance/sonic, and github.com/cloudwego/base64x appear as new transitive deps pulled in by the tastora v0.19.0 upgrade. These are indirect dependencies of test modules only, so the risk is contained — but worth confirming they were expected transitive deps rather than phantom additions.


test/e2e/benchmark/spamoor_node.go (new file)

Issue: waitHTTP silently drops timeout errorsspamoor_node.go:354-368

func waitHTTP(url string, timeout time.Duration) {  // no return value
    ...
    // on deadline: just returns
}

Called at spamoor_node.go:202 without any signal to the caller:

n.started = true
waitHTTP("http://127.0.0.1:"+n.external.HTTP+"/metrics", 20*time.Second)
return nil  // success even if spamoor never became ready

If spamoor fails to start within 20 seconds, Start returns nil and subsequent API calls will fail with confusing errors. Fix this →

Issue: ExecStart connection leak — test/docker-e2e/resiliency_test.go:912

// Attach to exec to get output
resp, err := s.dockerClient.ExecAttach(ctx, exec.ID, mobyclient.ExecAttachOptions{})
...
defer resp.Close()

// Start the exec
_, err = s.dockerClient.ExecStart(ctx, exec.ID, mobyclient.ExecStartOptions{})

The new mobyclient.ExecStart returns a net.Conn (the hijacked connection) which is silently discarded with _. Depending on the moby client implementation this may be a no-op after ExecAttach, or it may open a second connection that leaks. It should be explicitly closed:

conn, err := s.dockerClient.ExecStart(ctx, exec.ID, mobyclient.ExecStartOptions{})
if err != nil {
    return nil, fmt.Errorf("failed to start exec: %w", err)
}
if conn != nil {
    defer conn.Close()
}

Fix this →

Minor: DNS: []netip.Addr{}spamoor_node.go:263

Passing an explicit empty DNS list to Docker may disable DNS resolution inside the container rather than inheriting host defaults. If this is intentional (test isolation), add a comment; otherwise nil should be used:

// Set to nil to inherit Docker default DNS, or keep empty to disable
DNS: nil,

Minor: helper functions scoped to wrong file

extractPort, condenseHostName, and sanitizeDockerResourceName are defined in victoriatraces_node.go but used by spamoor_node.go. Since these are both in package benchmark this compiles fine, but they are shared utilities and would be better placed in a dedicated helpers.go file in the package.


execution/evm/test/test_helpers.go (modified)

The refactor to waitForRethContainer is a clear improvement:

  • JWT auth token is created once (1-hour validity comfortably covers the 30-second poll loop — confirmed by reading getAuthToken).
  • Fetches genesis hash dynamically via eth_getBlockByNumber rather than comparing against a hardcoded constant, which is why GENESIS_HASH in execution_test.go needed to change.
  • lastErr wrapping in the timeout path gives much better diagnostic messages.
  • Proper defer resp.Body.Close() on all HTTP responses.

One small nit: getAuthToken is now called before the loop and reused across all iterations. The comment says "valid for 1 hour" but this function is also called from other callsites where loop duration may differ. Keeping the expiry configurable or at least asserting it in the comment ("sufficient for the 30s poll loop") would help future readers.


test/e2e/benchmark/suite_test.go (modified)

Code is clean and well-structured. Two minor observations:

  • Cleanup errors are silently swallowed:

    t.Cleanup(func() { _ = vt.Remove(t.Context()) })
    t.Cleanup(func() { _ = spNode.Remove(t.Context()) })

    In test infrastructure, leaking containers/volumes when cleanup fails can cause hard-to-debug failures in subsequent test runs. Consider logging the error: t.Cleanup(func() { if err := vt.Remove(t.Context()); err != nil { t.Logf("cleanup vt: %v", err) } }).

  • The TODO: temporary hardcoded tag comment on defaultRethTag = "latest" (line 50) should track an issue — "latest" is non-deterministic and can cause flaky tests when the image is updated.


victoriatraces_node.go (new file)

Clean implementation. Uses container.Lifecycle correctly (unlike spamoor_node.go which rolls its own container lifecycle). One observation: Start does not implement the same idempotency guard as spamoor_node.go (n.started check) — calling Start twice on a victoriatraces_node will attempt to re-create the container. This asymmetry may cause subtle issues if the caller pattern ever calls Start after a test restart.


Overall Assessment

The dependency migration is solid and the new benchmark infrastructure is well-thought-out. The two actionable issues are the waitHTTP silent failure and the ExecStart connection leak in the resiliency_test.go migration. The rest are minor polish items.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 28, 2026

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

BuildFormatLintBreakingUpdated (UTC)
✅ passed⏩ skipped✅ passed✅ passedApr 29, 2026, 7:53 AM

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 28, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 62.51%. Comparing base (81c1c25) to head (3c99304).
⚠️ Report is 3 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3296      +/-   ##
==========================================
- Coverage   62.52%   62.51%   -0.02%     
==========================================
  Files         122      122              
  Lines       13047    13047              
==========================================
- Hits         8158     8156       -2     
- Misses       4004     4008       +4     
+ Partials      885      883       -2     
Flag Coverage Δ
combined 62.51% <ø> (-0.02%) ⬇️

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

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@tac0turtle tac0turtle marked this pull request as ready for review April 29, 2026 07:53
@tac0turtle tac0turtle requested a review from chatton April 29, 2026 07:53
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
test/docker-e2e/resiliency_test.go (1)

890-947: ⚠️ Potential issue | 🟠 Major

Demultiplex Docker streams and verify exec exit codes; silent command failures invalidate corruption test.

When ExecCreateOptions omits Tty (defaults to false), both ExecAttach and ContainerLogs return multiplexed stdout/stderr using Docker's 8-byte header protocol (STREAM_TYPE + size). Reading with io.ReadAll or io.Copy includes these framing bytes in the output. More critically, the corruption commands (lines 729, 738, 747) are wrapped in sh -c with 2>/dev/null || true, masking failures. The code checks for errors from ExecCreate and ExecStart but never calls ExecInspect to retrieve the actual exit code. This means a failed corruption command appears successful, causing TestDataCorruptionRecovery to pass without actually corrupting data—a false positive. Use stdcopy.StdCopy from github.com/moby/moby/api/pkg/stdcopy to demultiplex, and call ExecInspect to check the exit code before returning.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/docker-e2e/resiliency_test.go` around lines 890 - 947, The exec and log
readers are returning Docker's multiplexed stream frames and the exec path never
checks the command exit code, so failures get masked; in execCommandInContainer
use github.com/moby/moby/api/pkg/stdcopy.StdCopy to demultiplex resp.Reader into
separate stdout/stderr buffers (instead of io.ReadAll) and after ExecStart call
s.dockerClient.ExecInspect(ctx, exec.ID) to obtain the exit code and return an
error if it is non-zero; likewise in getContainerLogs demultiplex the returned
stream from s.dockerClient.ContainerLogs using stdcopy.StdCopy into a
strings.Builder (or separate buffers) rather than io.Copy so framing bytes are
removed. Ensure you reference execCommandInContainer, getContainerLogs,
ExecAttach, ContainerLogs, ExecStart, ExecInspect and stdcopy.StdCopy when
making the changes.
execution/evm/test/execution_test.go (2)

59-60: 🛠️ Refactor suggestion | 🟠 Major

Test determinism: remove time.Now() from genesis + block timestamps.

genesisTime := time.Now().UTC().Truncate(time.Second) and baseTimestamp := time.Now() make the chain setup dependent on wall-clock time. Per repo guidance for *_test.go, tests should be deterministic. While build/sync comparisons happen within the same run, this still risks cross-run variability and harder-to-reproduce flakes.

Use fixed timestamps (e.g., a constant time.Date(...)) and derive all block timestamps from it.

Proposed fix
- genesisTime := time.Now().UTC().Truncate(time.Second)
+ genesisTime := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)

- baseTimestamp := time.Now()
+ baseTimestamp := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)

  blockTimestamp := baseTimestamp.Add(time.Duration(blockHeight-initialHeight) * time.Second)

Also applies to: 97-98, 128-129

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@execution/evm/test/execution_test.go` around lines 59 - 60, Replace
non-deterministic calls to time.Now() used for test timestamps (e.g.,
genesisTime and baseTimestamp in execution_test.go) with a fixed, hard-coded
time (for example using time.Date) and derive any subsequent block timestamps by
adding durations to that fixed base; update all occurrences (including the
variables genesisTime, baseTimestamp and any places that compute block
timestamps near lines referenced) so the test uses the single deterministic seed
timestamp instead of time.Now().

108-114: ⚠️ Potential issue | 🟠 Major

Resource + timeout hygiene in tx submission loop.

In the per-block loop:

  • defer ethClient.Close() is inside the loop, so connections are not closed until the test ends (potentially accumulating resources).
  • ethClient.SendTransaction(context.Background(), ...) ignores the ctx with the 300s timeout you already created, so calls may hang beyond the test deadline.

Use ctx (from context.WithTimeout) and close the client immediately at the end of each iteration (no defer).

Proposed fix
for blockHeight := initialHeight; blockHeight <= 10; blockHeight++ {
  ...
  ethClient := createEthClient(t, ethURL)
- defer ethClient.Close()
+ // Close immediately after using it for this blockHeight.
  for i := range txs {
-   txs[i] = evm.GetRandomTransaction(TEST_PRIVATE_KEY, ... , &lastNonce)
-   require.NoError(tt, ethClient.SendTransaction(context.Background(), txs[i]))
+   txs[i] = evm.GetRandomTransaction(TEST_PRIVATE_KEY, ... , &lastNonce)
+   require.NoError(tt, ethClient.SendTransaction(ctx, txs[i]))
  }
+ require.NoError(tt, ethClient.Close())
  ...
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@execution/evm/test/execution_test.go` around lines 108 - 114, The loop
creates ethClient via createEthClient(t, ethURL) but defers ethClient.Close()
inside the loop and calls ethClient.SendTransaction with context.Background(),
causing resource leakage and ignoring the test timeout; change to call
ethClient.Close() explicitly at the end of each iteration (no defer) and replace
context.Background() with the existing ctx (from context.WithTimeout) when
calling ethClient.SendTransaction so the send honors the test timeout and the
client is closed immediately after each transaction attempt (ensure Close() runs
even on send error).
🧹 Nitpick comments (4)
test/e2e/benchmark/suite_test.go (1)

77-78: Pin the Spamoor and VictoriaTraces images in the suite.

This setup relies on helper defaults, and both helpers currently fall back to latest. That makes the benchmark suite non-reproducible across runs on the same commit and can skew perf/trace baselines. Please pass explicit tags or digests from the suite instead of inheriting floating defaults.

Based on learnings "Applies to **/*_test.go : Ensure tests are deterministic".

Also applies to: 131-188

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/e2e/benchmark/suite_test.go` around lines 77 - 78, Pin the container
images used by the benchmark suite instead of relying on helper defaults: update
the victoriaTracesConfig passed to newVictoriaTracesNode (and the equivalent
spamoorConfig/newSpamoorNode used later) to include explicit image references
(tag or digest) coming from the suite-level variables/constants, and use those
pinned values when constructing the nodes so neither VictoriaTraces nor Spamoor
falls back to "latest" floating tags.
test/e2e/benchmark/victoriatraces_node.go (1)

73-95: Wait for VictoriaTraces to accept HTTP before returning from Start.

This marks the node as started as soon as Docker has mapped a port, but suite_test.go wires the sequencer to the OTLP endpoint immediately afterwards. Without a readiness probe here, the first trace exports can race the VictoriaTraces process startup and disappear intermittently.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/e2e/benchmark/victoriatraces_node.go` around lines 73 - 95, Start
currently marks the node as started once Docker maps a port but returns before
VictoriaTraces is accepting HTTP; change Start (in victoriatraces_node.Start) to
perform an HTTP readiness probe against the mapped external HTTP port (use the
value assigned to n.externalHTTPPort after extractPort(hostPorts[0])) after
calling n.ContainerLifecycle.StartContainer and before setting n.started = true,
retrying with a short sleep until a successful 200 response or a configurable
timeout, and return an error if the probe never succeeds; keep all existing
container creation and port-extraction logic (createContainer,
ContainerLifecycle.GetHostPorts, extractPort) but only mark started after the
probe succeeds.
execution/evm/test/execution_test.go (2)

61-61: Minor: rename GenesisStateRoot to avoid exported-like naming and clarify type.

GenesisStateRoot := genesisStateRoot[:] creates a slice from a common.Hash, but the GenesisStateRoot name is inconsistent with the local lowercase pattern and looks exported. Also it’s not obvious whether downstream code expects []byte vs common.Hash.

Rename to something like genesisStateRootBytes and/or derive explicitly (e.g., genesisStateRootBytes := genesisStateRoot.Bytes() if applicable) to make intent clear.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@execution/evm/test/execution_test.go` at line 61, Rename the local variable
`GenesisStateRoot` to a lowercase, clearer name like `genesisStateRootBytes` and
ensure it is the correct type (byte slice) by deriving it explicitly from
`genesisStateRoot` (e.g., use `genesisStateRoot.Bytes()` if `genesisStateRoot`
is a `common.Hash`, or `[:]` only if you need a slice of the underlying array)
so downstream uses in this test (the assignment currently written as
`GenesisStateRoot := genesisStateRoot[:]`) clearly reflect a `[]byte` and follow
local naming conventions.

252-268: Reliability: fail fast instead of returning sentinel values on RPC errors.

checkLatestBlock logs warnings and returns (0, common.Hash{}, 0) when HeaderByNumber or BlockByNumber fails. That can lead to confusing downstream failures (or, worse, misleading comparisons) and hides the real root cause.

Prefer:

  • return (…, …, …, err) and require.NoError at call sites, or
  • call t.Fatalf(...) / require.NoError(t, err) inside the helper.

Given this is an assertion helper used for state correctness, silent fallback is risky.

Proposed direction
header, err := ethClient.HeaderByNumber(ctx, nil)
if err != nil {
- t.Logf("Warning: Failed to get latest block header: %v", err)
- return 0, common.Hash{}, 0
+ t.Fatalf("failed to get latest block header: %v", err)
}

...

block, err := ethClient.BlockByNumber(ctx, header.Number)
if err != nil {
- t.Logf("Warning: Failed to get full block: %v", err)
- return blockNumber, blockHash, 0
+ t.Fatalf("failed to get full block: %v", err)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@execution/evm/test/execution_test.go` around lines 252 - 268, The helper
checkLatestBlock currently swallows RPC errors and returns sentinel values;
change it to fail fast by either (A) updating checkLatestBlock to return
(uint64, common.Hash, int, error) and propagate the error to callers so tests
call require.NoError(t, err) / t.Fatal on failure, or (B) have checkLatestBlock
itself assert the RPC success using require.NoError(t, err) or t.Fatalf when
HeaderByNumber or BlockByNumber returns an error; locate the helper function
named checkLatestBlock and its call sites in tests and apply one of these two
fixes so RPC errors are not converted to (0, common.Hash{}, 0).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@execution/evm/test/execution_test.go`:
- Around line 22-28: The test currently embeds a raw private key constant
TEST_PRIVATE_KEY; remove that constant and instead obtain the test key at
runtime—either read EVM_TEST_PRIVATE_KEY from the environment (fail the test
with require.NotEmpty(t, privKey, "missing EVM_TEST_PRIVATE_KEY") if absent) or
derive a deterministic key from a fixed seed at test startup so CI is
reproducible; then pass the resulting privKey into the existing
evm.GetRandomTransaction(...) call (and any other places referencing
TEST_PRIVATE_KEY) so no raw private key is committed in execution_test.go.

In `@test/e2e/benchmark/spamoor_node.go`:
- Around line 241-245: The benchmark is passing the raw private key string into
the container command args (see the Cmd construction using n.cfg.PrivateKey and
the WithPrivateKey flow), which leaks secrets; instead write or mount the
private key as a file and pass the file path (or use a container environment
variable mapped from a mounted secret) rather than embedding the key in Cmd.
Update the code that builds cmd (the slice containing "--privkey",
n.cfg.PrivateKey) to reference a path (e.g., n.HomeDir()/spamoor.key) and ensure
the key is written with restrictive permissions or mounted into the container as
a secret, and adjust any consumer logic that expects "--privkey" to read the key
from the file or from a secure env var so the secret no longer appears in the
container command line; keep references to WithPrivateKey and
defaultSpamoorHTTPPort intact.

---

Outside diff comments:
In `@execution/evm/test/execution_test.go`:
- Around line 59-60: Replace non-deterministic calls to time.Now() used for test
timestamps (e.g., genesisTime and baseTimestamp in execution_test.go) with a
fixed, hard-coded time (for example using time.Date) and derive any subsequent
block timestamps by adding durations to that fixed base; update all occurrences
(including the variables genesisTime, baseTimestamp and any places that compute
block timestamps near lines referenced) so the test uses the single
deterministic seed timestamp instead of time.Now().
- Around line 108-114: The loop creates ethClient via createEthClient(t, ethURL)
but defers ethClient.Close() inside the loop and calls ethClient.SendTransaction
with context.Background(), causing resource leakage and ignoring the test
timeout; change to call ethClient.Close() explicitly at the end of each
iteration (no defer) and replace context.Background() with the existing ctx
(from context.WithTimeout) when calling ethClient.SendTransaction so the send
honors the test timeout and the client is closed immediately after each
transaction attempt (ensure Close() runs even on send error).

In `@test/docker-e2e/resiliency_test.go`:
- Around line 890-947: The exec and log readers are returning Docker's
multiplexed stream frames and the exec path never checks the command exit code,
so failures get masked; in execCommandInContainer use
github.com/moby/moby/api/pkg/stdcopy.StdCopy to demultiplex resp.Reader into
separate stdout/stderr buffers (instead of io.ReadAll) and after ExecStart call
s.dockerClient.ExecInspect(ctx, exec.ID) to obtain the exit code and return an
error if it is non-zero; likewise in getContainerLogs demultiplex the returned
stream from s.dockerClient.ContainerLogs using stdcopy.StdCopy into a
strings.Builder (or separate buffers) rather than io.Copy so framing bytes are
removed. Ensure you reference execCommandInContainer, getContainerLogs,
ExecAttach, ContainerLogs, ExecStart, ExecInspect and stdcopy.StdCopy when
making the changes.

---

Nitpick comments:
In `@execution/evm/test/execution_test.go`:
- Line 61: Rename the local variable `GenesisStateRoot` to a lowercase, clearer
name like `genesisStateRootBytes` and ensure it is the correct type (byte slice)
by deriving it explicitly from `genesisStateRoot` (e.g., use
`genesisStateRoot.Bytes()` if `genesisStateRoot` is a `common.Hash`, or `[:]`
only if you need a slice of the underlying array) so downstream uses in this
test (the assignment currently written as `GenesisStateRoot :=
genesisStateRoot[:]`) clearly reflect a `[]byte` and follow local naming
conventions.
- Around line 252-268: The helper checkLatestBlock currently swallows RPC errors
and returns sentinel values; change it to fail fast by either (A) updating
checkLatestBlock to return (uint64, common.Hash, int, error) and propagate the
error to callers so tests call require.NoError(t, err) / t.Fatal on failure, or
(B) have checkLatestBlock itself assert the RPC success using require.NoError(t,
err) or t.Fatalf when HeaderByNumber or BlockByNumber returns an error; locate
the helper function named checkLatestBlock and its call sites in tests and apply
one of these two fixes so RPC errors are not converted to (0, common.Hash{}, 0).

In `@test/e2e/benchmark/suite_test.go`:
- Around line 77-78: Pin the container images used by the benchmark suite
instead of relying on helper defaults: update the victoriaTracesConfig passed to
newVictoriaTracesNode (and the equivalent spamoorConfig/newSpamoorNode used
later) to include explicit image references (tag or digest) coming from the
suite-level variables/constants, and use those pinned values when constructing
the nodes so neither VictoriaTraces nor Spamoor falls back to "latest" floating
tags.

In `@test/e2e/benchmark/victoriatraces_node.go`:
- Around line 73-95: Start currently marks the node as started once Docker maps
a port but returns before VictoriaTraces is accepting HTTP; change Start (in
victoriatraces_node.Start) to perform an HTTP readiness probe against the mapped
external HTTP port (use the value assigned to n.externalHTTPPort after
extractPort(hostPorts[0])) after calling n.ContainerLifecycle.StartContainer and
before setting n.started = true, retrying with a short sleep until a successful
200 response or a configurable timeout, and return an error if the probe never
succeeds; keep all existing container creation and port-extraction logic
(createContainer, ContainerLifecycle.GetHostPorts, extractPort) but only mark
started after the probe succeeds.
🪄 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: c1a88668-40b3-48ae-ba79-078e653fcc29

📥 Commits

Reviewing files that changed from the base of the PR and between 81c1c25 and 3c99304.

⛔ Files ignored due to path filters (3)
  • execution/evm/test/go.sum is excluded by !**/*.sum
  • test/docker-e2e/go.sum is excluded by !**/*.sum
  • test/e2e/go.sum is excluded by !**/*.sum
📒 Files selected for processing (9)
  • execution/evm/test/execution_test.go
  • execution/evm/test/go.mod
  • execution/evm/test/test_helpers.go
  • test/docker-e2e/go.mod
  • test/docker-e2e/resiliency_test.go
  • test/e2e/benchmark/spamoor_node.go
  • test/e2e/benchmark/suite_test.go
  • test/e2e/benchmark/victoriatraces_node.go
  • test/e2e/go.mod

Comment on lines 22 to 28
const (
CHAIN_ID = "1234"
GENESIS_HASH = "0x2b8bbb1ea1e04f9c9809b4b278a8687806edc061a356c7dbc491930d8e922503"
GENESIS_STATEROOT = "0x05e9954443da80d86f2104e56ffdfd98fe21988730684360104865b3dc8191b4"
GENESIS_HASH = "0x6699a4ef6e7b76499c6cd68443455a3584e505b1da43cf82e6efaef41f5e1d8a"
GENESIS_STATEROOT = "0x0373244015ce5fa583ed6e575b7a22c39a542233a62d2bd47c37fd1a2b035366"
TEST_PRIVATE_KEY = "cece4f25ac74deb1468965160c7185e07dff413f23fcadb611b05ca37ab0a52e"
TEST_TO_ADDRESS = "0x944fDcD1c868E3cC566C78023CcB38A32cDA836E"
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Security: avoid committing a raw private key constant.

TEST_PRIVATE_KEY is hardcoded in the test file. Even if intended for local/E2E usage, this is still a sensitive artifact and can be reused accidentally or leaked via logs/exports.

Consider:

  • Read the key from an env var (and fail if missing), or
  • Generate a deterministic test key at runtime from a fixed seed (so CI stays reproducible) without storing the raw private key.
Proposed direction
const (
  CHAIN_ID          = "1234"
  GENESIS_HASH      = "0x6699a4ef..."
  GENESIS_STATEROOT = "0x037324..."
- TEST_PRIVATE_KEY  = "cece4f25ac74deb1468965160c7185e07dff413f23fcadb611b05ca37ab0a52e"
+ // Load from env to avoid committing raw private key material.
+ // Fallback: generate deterministically from a fixed seed if you prefer
+ // (but avoid storing the raw key in the repo).
  TEST_TO_ADDRESS   = "0x944fDcD1c868E3cC566C78023CcB38A32cDA836E"
)

Then:

  • privKey := os.Getenv("EVM_TEST_PRIVATE_KEY")
  • require.NotEmpty(t, privKey, "missing EVM_TEST_PRIVATE_KEY")
  • use privKey in evm.GetRandomTransaction(...).
🧰 Tools
🪛 Betterleaks (1.1.2)

[high] 26-26: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@execution/evm/test/execution_test.go` around lines 22 - 28, The test
currently embeds a raw private key constant TEST_PRIVATE_KEY; remove that
constant and instead obtain the test key at runtime—either read
EVM_TEST_PRIVATE_KEY from the environment (fail the test with
require.NotEmpty(t, privKey, "missing EVM_TEST_PRIVATE_KEY") if absent) or
derive a deterministic key from a fixed seed at test startup so CI is
reproducible; then pass the resulting privKey into the existing
evm.GetRandomTransaction(...) call (and any other places referencing
TEST_PRIVATE_KEY) so no raw private key is committed in execution_test.go.

Comment on lines +241 to +245
cmd := []string{
"--privkey", n.cfg.PrivateKey,
"--port", defaultSpamoorHTTPPort,
"--db", fmt.Sprintf("%s/%s", n.HomeDir(), "spamoor.db"),
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't pass the benchmark private key via Cmd.

WithPrivateKey feeds the raw key into --privkey here, so the secret becomes part of the container command line. In external mode that value comes from BENCH_PRIVATE_KEY, which makes routine debugging/inspection of the benchmark container an easy leak path. Please switch this to a mounted secret/file or another channel that does not serialize the key into container args.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/e2e/benchmark/spamoor_node.go` around lines 241 - 245, The benchmark is
passing the raw private key string into the container command args (see the Cmd
construction using n.cfg.PrivateKey and the WithPrivateKey flow), which leaks
secrets; instead write or mount the private key as a file and pass the file path
(or use a container environment variable mapped from a mounted secret) rather
than embedding the key in Cmd. Update the code that builds cmd (the slice
containing "--privkey", n.cfg.PrivateKey) to reference a path (e.g.,
n.HomeDir()/spamoor.key) and ensure the key is written with restrictive
permissions or mounted into the container as a secret, and adjust any consumer
logic that expects "--privkey" to read the key from the file or from a secure
env var so the secret no longer appears in the container command line; keep
references to WithPrivateKey and defaultSpamoorHTTPPort intact.

Copy link
Copy Markdown
Contributor

@chatton chatton left a comment

Choose a reason for hiding this comment

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

it looks like this is pulling in a bunch of stuff that was in tastora? The spamoor / traces stuff. I think it's because we were on a commit and not a tag for benchmarking, I think we can merge this for now but I will look into making sure all the stuff we use for benchmarking is in a proper tag in tastora.

@tac0turtle tac0turtle merged commit 1f1f48e into main Apr 29, 2026
42 checks passed
@tac0turtle tac0turtle deleted the marko/sec_deps branch April 29, 2026 12:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants