-
Notifications
You must be signed in to change notification settings - Fork 250
feat: adding spamoor test #3091
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
aeaf7fa
feat: introduce EVM contract benchmarking with new tests and a GitHub…
alpe c322838
Capture otel
alpe bda012c
wip: basic spamoor test running and reporting metrics
chatton 4f3cd9f
wiring up to docker reth
chatton 9c35f6d
chore: adding trance benchmark e2e
chatton 3dfd7de
wip: experimenting with gas burner tx
chatton 269be56
wip
chatton edba202
chore: adding basic spamoor test
chatton b930226
chore: merge main
chatton a784783
chore: remove local pin
chatton c5306c8
chore: adding basic assertion
chatton 6b7a328
fix linter
chatton fb54505
chore: fix indentation
chatton fe145e6
Merge branch 'main' into cian/spamoor
chatton a992fae
Merge branch 'main' into cian/spamoor
chatton File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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()) | ||
| 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 | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
|
|
@@ -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) { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
|
@@ -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. | ||
|
|
@@ -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...) | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For better test lifecycle management and cancellation, consider using the test-specific context
t.Context()instead ofcontext.Background(). This ensures that the context is cancelled when the test completes.