Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions test/e2e/evm_force_inclusion_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func setupSequencerWithForceInclusion(t *testing.T, sut *SystemUnderTest, nodeHo
t.Helper()

// Use common setup (no full node needed initially)
jwtSecret, _, genesisHash, endpoints := setupCommonEVMTest(t, sut, false)
jwtSecret, _, genesisHash, endpoints, _ := setupCommonEVMTest(t, sut, false)

// Create passphrase file
passphraseFile := createPassphraseFile(t, nodeHome)
Expand Down Expand Up @@ -192,7 +192,7 @@ func TestEvmFullNodeForceInclusionE2E(t *testing.T) {
// --- Start Sequencer Setup ---
// We manually setup sequencer here because we need the force inclusion flag,
// and we need to capture variables for full node setup.
jwtSecret, fullNodeJwtSecret, genesisHash, endpoints := setupCommonEVMTest(t, sut, true)
jwtSecret, fullNodeJwtSecret, genesisHash, endpoints, _ := setupCommonEVMTest(t, sut, true)

passphraseFile := createPassphraseFile(t, sequencerHome)
jwtSecretFile := createJWTSecretFile(t, sequencerHome, jwtSecret)
Expand Down Expand Up @@ -284,7 +284,7 @@ func setupMaliciousSequencer(t *testing.T, sut *SystemUnderTest, nodeHome string
t.Helper()

// Use common setup with full node support
jwtSecret, fullNodeJwtSecret, genesisHash, endpoints := setupCommonEVMTest(t, sut, true)
jwtSecret, fullNodeJwtSecret, genesisHash, endpoints, _ := setupCommonEVMTest(t, sut, true)

passphraseFile := createPassphraseFile(t, nodeHome)
jwtSecretFile := createJWTSecretFile(t, nodeHome, jwtSecret)
Expand Down
6 changes: 3 additions & 3 deletions test/e2e/evm_full_node_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ func setupSequencerWithFullNode(t *testing.T, sut *SystemUnderTest, sequencerHom
t.Helper()

// Common setup for both sequencer and full node
jwtSecret, fullNodeJwtSecret, genesisHash, endpoints := setupCommonEVMTest(t, sut, true)
jwtSecret, fullNodeJwtSecret, genesisHash, endpoints, _ := setupCommonEVMTest(t, sut, true)

// Setup sequencer
setupSequencerNode(t, sut, sequencerHome, jwtSecret, genesisHash, endpoints)
Expand Down Expand Up @@ -645,7 +645,7 @@ func setupSequencerWithFullNodeLazy(t *testing.T, sut *SystemUnderTest, sequence
t.Helper()

// Common setup for both sequencer and full node
jwtSecret, fullNodeJwtSecret, genesisHash, endpoints := setupCommonEVMTest(t, sut, true)
jwtSecret, fullNodeJwtSecret, genesisHash, endpoints, _ := setupCommonEVMTest(t, sut, true)

t.Logf("Generated test endpoints - Rollkit RPC: %s, P2P: %s, Full Node RPC: %s, P2P: %s, DA Port: %s",
endpoints.RollkitRPCPort, endpoints.RollkitP2PPort, endpoints.FullNodeRPCPort, endpoints.FullNodeP2PPort, endpoints.DAPort)
Expand Down Expand Up @@ -1039,7 +1039,7 @@ func testSequencerFullNodeRestart(t *testing.T, initialLazyMode, restartLazyMode
t.Logf("Test mode: initial_lazy=%t, restart_lazy=%t", initialLazyMode, restartLazyMode)

// Get JWT secrets and setup common components first
jwtSecret, fullNodeJwtSecret, genesisHash, endpoints := setupCommonEVMTest(t, sut, true)
jwtSecret, fullNodeJwtSecret, genesisHash, endpoints, _ := setupCommonEVMTest(t, sut, true)

// Setup sequencer based on initial mode
if initialLazyMode {
Expand Down
177 changes: 177 additions & 0 deletions test/e2e/evm_spamoor_smoke_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
//go:build evm

package e2e

import (
"context"
"fmt"
"net/http"
"path/filepath"
"testing"
"time"

spamoor "github.com/celestiaorg/tastora/framework/docker/evstack/spamoor"
dto "github.com/prometheus/client_model/go"
"github.com/stretchr/testify/require"
)

// TestSpamoorSmoke spins up reth + sequencer and a Spamoor node, starts a few
// basic spammers, waits briefly, then prints a concise metrics summary.
func TestSpamoorSmoke(t *testing.T) {
t.Parallel()

sut := NewSystemUnderTest(t)
// Bring up reth + local DA and start sequencer with default settings.
seqJWT, _, genesisHash, endpoints, rethNode := setupCommonEVMTest(t, sut, false)
sequencerHome := filepath.Join(t.TempDir(), "sequencer")

// In-process OTLP/HTTP collector to capture ev-node spans.
collector := newOTLPCollector(t)
t.Cleanup(func() {
_ = collector.close()
})

// Start sequencer with tracing to our collector.
setupSequencerNode(t, sut, sequencerHome, seqJWT, genesisHash, endpoints,
"--evnode.instrumentation.tracing=true",
"--evnode.instrumentation.tracing_endpoint", collector.endpoint(),
"--evnode.instrumentation.tracing_sample_rate", "1.0",
"--evnode.instrumentation.tracing_service_name", "ev-node-smoke",
)
t.Log("Sequencer node is up")

// Start Spamoor within the same Docker network, targeting reth internal RPC.
ni, err := rethNode.GetNetworkInfo(context.Background())
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

For better test lifecycle management and cancellation, consider using the test-specific context t.Context() instead of context.Background(). This ensures that the context is cancelled when the test completes.

Suggested change
ni, err := rethNode.GetNetworkInfo(context.Background())
ni, err := rethNode.GetNetworkInfo(t.Context())

require.NoError(t, err, "failed to get network info")

internalRPC := "http://" + ni.Internal.RPCAddress()

spBuilder := spamoor.NewNodeBuilder(t.Name()).
WithDockerClient(rethNode.DockerClient).
WithDockerNetworkID(rethNode.NetworkID).
WithLogger(rethNode.Logger).
WithRPCHosts(internalRPC).
WithPrivateKey(TestPrivateKey)

ctx := t.Context()
spNode, err := spBuilder.Build(ctx)
require.NoError(t, err, "failed to build sp node")

t.Cleanup(func() { _ = spNode.Remove(t.Context()) })
require.NoError(t, spNode.Start(ctx), "failed to start spamoor node")

// Wait for daemon readiness.
spInfo, err := spNode.GetNetworkInfo(ctx)
require.NoError(t, err, "failed to get network info")

apiAddr := "http://127.0.0.1:" + spInfo.External.Ports.HTTP
requireHTTP(t, apiAddr+"/api/spammers", 30*time.Second)
api := spNode.API()

// Basic scenarios (structs that YAML-marshal into the daemon config).
eoatx := map[string]any{
"throughput": 100,
"total_count": 3000,
"max_pending": 4000,
"max_wallets": 300,
"amount": 100,
"random_amount": true,
"random_target": true,
"base_fee": 20, // gwei
"tip_fee": 2, // gwei
"refill_amount": "1000000000000000000",
"refill_balance": "500000000000000000",
"refill_interval": 600,
}

gasburner := map[string]any{
"throughput": 25,
"total_count": 2000,
"max_pending": 8000,
"max_wallets": 500,
"gas_units_to_burn": 3000000,
"base_fee": 20,
"tip_fee": 5,
"rebroadcast": 5,
"refill_amount": "5000000000000000000",
"refill_balance": "2000000000000000000",
"refill_interval": 300,
}

var ids []int
id, err := api.CreateSpammer("smoke-eoatx", spamoor.ScenarioEOATX, eoatx, true)
require.NoError(t, err, "failed to create eoatx spammer")
ids = append(ids, id)
id, err = api.CreateSpammer("smoke-gasburner", spamoor.ScenarioGasBurnerTX, gasburner, true)
require.NoError(t, err, "failed to create gasburner spammer")
ids = append(ids, id)

for _, id := range ids {
idToDelete := id
t.Cleanup(func() { _ = api.DeleteSpammer(idToDelete) })
}

// Allow additional time to accumulate activity.
time.Sleep(60 * time.Second)

// Fetch parsed metrics and print a concise summary.
metrics, err := api.GetMetrics()
require.NoError(t, err, "failed to get metrics")
sent := sumCounter(metrics["spamoor_transactions_sent_total"])
fail := sumCounter(metrics["spamoor_transactions_failed_total"])

time.Sleep(2 * time.Second)
printCollectedTraceReport(t, collector)

require.Greater(t, sent, float64(0), "at least one transaction should have been sent")
require.Zero(t, fail, "no transactions should have failed")
}

// --- helpers ---

func requireHTTP(t *testing.T, url string, timeout time.Duration) {
t.Helper()
client := &http.Client{Timeout: 200 * time.Millisecond}
deadline := time.Now().Add(timeout)
var lastErr error
for time.Now().Before(deadline) {
resp, err := client.Get(url)
if err == nil {
_ = resp.Body.Close()
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
return
}
lastErr = fmt.Errorf("status %d", resp.StatusCode)
} else {
lastErr = err
}
time.Sleep(100 * time.Millisecond)
}
t.Fatalf("daemon not ready at %s: %v", url, lastErr)
}

// Metric family helpers.
func sumCounter(f *dto.MetricFamily) float64 {
if f == nil || f.GetType() != dto.MetricType_COUNTER {
return 0
}
var sum float64
for _, m := range f.GetMetric() {
if m.GetCounter() != nil && m.GetCounter().Value != nil {
sum += m.GetCounter().GetValue()
}
}
return sum
}
func sumGauge(f *dto.MetricFamily) float64 {
if f == nil || f.GetType() != dto.MetricType_GAUGE {
return 0
}
var sum float64
for _, m := range f.GetMetric() {
if m.GetGauge() != nil && m.GetGauge().Value != nil {
sum += m.GetGauge().GetValue()
}
}
return sum
}
8 changes: 4 additions & 4 deletions test/e2e/evm_test_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ import (

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
evmtest "github.com/evstack/ev-node/execution/evm/test"
"github.com/stretchr/testify/require"

"github.com/celestiaorg/tastora/framework/docker/evstack/reth"
"github.com/evstack/ev-node/execution/evm"
evmtest "github.com/evstack/ev-node/execution/evm/test"
)

// evmSingleBinaryPath is the path to the evm-single binary used in tests
Expand Down Expand Up @@ -513,7 +513,7 @@ func submitTransactionAndGetBlockNumber(t testing.TB, sequencerClients ...*ethcl
// - daPort: optional DA port to use (if empty, uses default)
//
// Returns: jwtSecret, fullNodeJwtSecret (empty if needsFullNode=false), genesisHash
func setupCommonEVMTest(t testing.TB, sut *SystemUnderTest, needsFullNode bool, _ ...string) (string, string, string, *TestEndpoints) {
func setupCommonEVMTest(t testing.TB, sut *SystemUnderTest, needsFullNode bool) (string, string, string, *TestEndpoints, *reth.Node) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I needed to return the reth node in order to get access to the DockerClient / Network so the spamoor instance can be deployed in the same network.

I think we can refactor this function in a follow up as it is quite bloated at this stage.

I also removed the unused string varargs

t.Helper()

// Reset global nonce for each test to ensure clean state
Expand Down Expand Up @@ -559,7 +559,7 @@ func setupCommonEVMTest(t testing.TB, sut *SystemUnderTest, needsFullNode bool,
dynEndpoints.FullNodeEnginePort = fnInfo.External.Ports.Engine
}

return seqJWT, fnJWT, genesisHash, dynEndpoints
return seqJWT, fnJWT, genesisHash, dynEndpoints, rethNode
}

// checkBlockInfoAt retrieves block information at a specific height including state root.
Expand Down Expand Up @@ -618,7 +618,7 @@ func setupSequencerOnlyTest(t testing.TB, sut *SystemUnderTest, nodeHome string,
t.Helper()

// Use common setup (no full node needed)
jwtSecret, _, genesisHash, endpoints := setupCommonEVMTest(t, sut, false)
jwtSecret, _, genesisHash, endpoints, _ := setupCommonEVMTest(t, sut, false)

// Initialize and start sequencer node
setupSequencerNode(t, sut, nodeHome, jwtSecret, genesisHash, endpoints, extraArgs...)
Expand Down
4 changes: 2 additions & 2 deletions test/e2e/failover_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func TestLeaseFailoverE2E(t *testing.T) {
workDir := t.TempDir()

// Get JWT secrets and setup common components first
jwtSecret, fullNodeJwtSecret, genesisHash, testEndpoints := setupCommonEVMTest(t, sut, true)
jwtSecret, fullNodeJwtSecret, genesisHash, testEndpoints, _ := setupCommonEVMTest(t, sut, true)
rethFn := evmtest.SetupTestRethNode(t)
jwtSecret3 := rethFn.JWTSecretHex()
fnInfo, err := rethFn.GetNetworkInfo(context.Background())
Expand Down Expand Up @@ -253,7 +253,7 @@ func TestHASequencerRollingRestartE2E(t *testing.T) {
workDir := t.TempDir()

// Get JWT secrets and setup common components first
jwtSecret, fullNodeJwtSecret, genesisHash, testEndpoints := setupCommonEVMTest(t, sut, true)
jwtSecret, fullNodeJwtSecret, genesisHash, testEndpoints, _ := setupCommonEVMTest(t, sut, true)
rethFn := evmtest.SetupTestRethNode(t)
jwtSecret3 := rethFn.JWTSecretHex()
fnInfo, err := rethFn.GetNetworkInfo(context.Background())
Expand Down
4 changes: 2 additions & 2 deletions test/e2e/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ go 1.25.6
require (
cosmossdk.io/math v1.5.3
github.com/celestiaorg/go-square/v3 v3.0.2
github.com/celestiaorg/tastora v0.12.0
github.com/celestiaorg/tastora v0.14.0
github.com/cosmos/cosmos-sdk v0.53.6
github.com/cosmos/ibc-go/v8 v8.8.0
github.com/ethereum/go-ethereum v1.17.0
github.com/evstack/ev-node v1.0.0-rc.3
github.com/evstack/ev-node/execution/evm v0.0.0-20250602130019-2a732cf903a5
github.com/evstack/ev-node/execution/evm/test v0.0.0-00010101000000-000000000000
github.com/libp2p/go-libp2p v0.47.0
github.com/prometheus/client_model v0.6.2
github.com/rs/zerolog v1.34.0
github.com/stretchr/testify v1.11.1
go.opentelemetry.io/proto/otlp v1.9.0
Expand Down Expand Up @@ -246,7 +247,6 @@ require (
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/polydawn/refmt v0.89.0 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.17.0 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
Expand Down
4 changes: 2 additions & 2 deletions test/e2e/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,8 @@ github.com/celestiaorg/go-square/v3 v3.0.2 h1:eSQOgNII8inK9IhiBZ+6GADQeWbRq4HYY7
github.com/celestiaorg/go-square/v3 v3.0.2/go.mod h1:oFReMLsSDMRs82ICFEeFQFCqNvwdsbIM1BzCcb0f7dM=
github.com/celestiaorg/nmt v0.24.2 h1:LlpJSPOd6/Lw1Ig6HUhZuqiINHLka/ZSRTBzlNJpchg=
github.com/celestiaorg/nmt v0.24.2/go.mod h1:vgLBpWBi8F5KLxTdXSwb7AU4NhiIQ1AQRGa+PzdcLEA=
github.com/celestiaorg/tastora v0.12.0 h1:xs1a/d+/QFbebShoHZxnWj8q3Kr1i6PY5137oxR+h+4=
github.com/celestiaorg/tastora v0.12.0/go.mod h1:ObeKMraNab/xofYZyylnOEiveHvUAPdKP5HiNjnvQoM=
github.com/celestiaorg/tastora v0.14.0 h1:kvcx1MSKTx4DjOW60g9lcndL/LFpLq3H+CbzglaNlA0=
github.com/celestiaorg/tastora v0.14.0/go.mod h1:ObeKMraNab/xofYZyylnOEiveHvUAPdKP5HiNjnvQoM=
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
Expand Down
Loading