Skip to content
Open
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
9 changes: 7 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -500,11 +500,16 @@ giga-integration-test:
# Run Autobahn integration tests with an Autobahn-enabled cluster.
autobahn-integration-test:
@# The test drives cluster start/stop itself via TestMain — see
@# integration_test/autobahn/autobahn_test.go. GOWORK=off: ignore ambient
@# go.work; this target only needs stdlib + sei-tendermint.
@# integration_test/autobahn/autobahn_test.go. GOWORK=off ignores an
@# ambient go.work so dependency resolution matches the module.
@GOWORK=off go test -tags autobahn_integration -v -count=1 -timeout 30m ./integration_test/autobahn/...
.PHONY: autobahn-integration-test

# Run the minimal in-memory EVM-only executor behind a four-validator Autobahn cluster.
autobahn-evmonly-integration-test:
@AUTOBAHN_EVMONLY_IN_MEMORY=true GOWORK=off go test -tags autobahn_integration -v -count=1 -timeout 30m ./integration_test/autobahn/...
.PHONY: autobahn-evmonly-integration-test

# Run a mixed-mode cluster: node 0 uses GIGA_EXECUTOR with OCC, nodes 1-3 use standard V2.
# (node-level GIGA_EXECUTOR/GIGA_OCC values are pinned in docker-compose.giga-mixed.yml)
# Any determinism divergence between giga and V2 will cause the giga node to halt.
Expand Down
4 changes: 4 additions & 0 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ services:
- GIGA_OCC
- RECEIPT_BACKEND
- AUTOBAHN
- AUTOBAHN_EVMONLY_IN_MEMORY
- GIGA_STORAGE
- GIGA_MIGRATE_FROM_MEMIAVL
- GIGA_FLATKV_ONLY
Expand Down Expand Up @@ -55,6 +56,7 @@ services:
- GIGA_OCC
- RECEIPT_BACKEND
- AUTOBAHN
- AUTOBAHN_EVMONLY_IN_MEMORY
- GIGA_STORAGE
- GIGA_MIGRATE_FROM_MEMIAVL
- GIGA_FLATKV_ONLY
Expand Down Expand Up @@ -86,6 +88,7 @@ services:
- GIGA_OCC
- RECEIPT_BACKEND
- AUTOBAHN
- AUTOBAHN_EVMONLY_IN_MEMORY
- GIGA_STORAGE
- GIGA_MIGRATE_FROM_MEMIAVL
- GIGA_FLATKV_ONLY
Expand Down Expand Up @@ -121,6 +124,7 @@ services:
- GIGA_OCC
- RECEIPT_BACKEND
- AUTOBAHN
- AUTOBAHN_EVMONLY_IN_MEMORY
- GIGA_STORAGE
- GIGA_MIGRATE_FROM_MEMIAVL
- GIGA_FLATKV_ONLY
Expand Down
7 changes: 7 additions & 0 deletions docker/localnode/scripts/step4_config_override.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ NODE_ID=${ID:-0}
GIGA_EXECUTOR=${GIGA_EXECUTOR:-true}
GIGA_OCC=${GIGA_OCC:-true}
AUTOBAHN=${AUTOBAHN:-false}
AUTOBAHN_EVMONLY_IN_MEMORY=${AUTOBAHN_EVMONLY_IN_MEMORY:-false}
GIGA_STORAGE=${GIGA_STORAGE:-false}
# GIGA_FLATKV_ONLY=true boots the cluster directly in the terminal v3
# steady state: all SC writes route to FlatKV and memiavl is not allocated.
Expand Down Expand Up @@ -165,6 +166,12 @@ if [ "$AUTOBAHN" = "true" ]; then

# Generate autobahn config using seid
seid tendermint gen-autobahn-config $NODE_DIRS --output "$AUTOBAHN_CONFIG"
if [ "$AUTOBAHN_EVMONLY_IN_MEMORY" = "true" ]; then
AUTOBAHN_CONFIG_TMP="$AUTOBAHN_CONFIG.tmp"
jq '.evm_only_in_memory = true | del(.persistent_state_dir)' "$AUTOBAHN_CONFIG" > "$AUTOBAHN_CONFIG_TMP"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[suggestion] This drops the && guard the repo's other jq rewrites use (override_genesis in step2_genesis.sh:13 is jq ... > tmp && mv tmp target). The script has no set -e, so if jq fails the mv on the next line still runs and replaces autobahn.json with a truncated file, and the node fails later with an unrelated-looking config error. Chaining > "$AUTOBAHN_CONFIG_TMP" && mv ... restores the existing idiom.

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.

You can pass empty string to persistent_state_dir, which I think will disable persistence.

mv "$AUTOBAHN_CONFIG_TMP" "$AUTOBAHN_CONFIG"
echo "Enabled Autobahn EVM-only execution with in-memory consensus state for node $NODE_ID"
fi
# Inject autobahn config file path into config.toml
# Must be placed before any [section] header so TOML parser reads it as a top-level key.
if grep -q "autobahn-config-file" ~/.sei/config/config.toml; then
Expand Down
152 changes: 148 additions & 4 deletions integration_test/autobahn/autobahn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// Requires a running autobahn Docker cluster. Run via:
//
// make autobahn-integration-test
// make autobahn-evmonly-integration-test
//
// Or directly (cluster must already be up):
//
Expand All @@ -13,9 +14,11 @@ package autobahn

import (
"bufio"
"context"
"encoding/json"
"fmt"
"io"
"math/big"
"net/http"
"os"
"os/exec"
Expand All @@ -24,8 +27,15 @@ import (
"testing"
"time"

"github.com/ethereum/go-ethereum/common"
"golang.org/x/sync/errgroup"

"github.com/sei-protocol/sei-chain/giga/evmonly/cmd/evmonly-loadtest/scenarios"
tmconfig "github.com/sei-protocol/sei-chain/sei-tendermint/config"
tmjson "github.com/sei-protocol/sei-chain/sei-tendermint/libs/json"
tmhttp "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/client/http"
"github.com/sei-protocol/sei-chain/sei-tendermint/rpc/coretypes"
tmtypes "github.com/sei-protocol/sei-chain/sei-tendermint/types"
"github.com/sei-protocol/sei-chain/testutil/evmtest"
)

Expand Down Expand Up @@ -75,6 +85,10 @@ const (
// CI runners are slower than local; 1m was tight enough to flake.
haltStableTimeout = 2 * time.Minute
testRecipientEVM = "0x1000000000000000000000000000000000000001"

evmOnlyInMemoryEnv = "AUTOBAHN_EVMONLY_IN_MEMORY"
evmOnlyLoadTxs = 4_000
evmOnlyLoadTimeout = 3 * time.Minute
)

var (
Expand Down Expand Up @@ -200,6 +214,21 @@ func assertAutobahnEnabled(t *testing.T) {
}
}

func evmOnlyInMemoryEnabled() bool {

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.

I acknowledge that this is for integration testing, but are there facilities to auto-map env variables to the json config? I'm only seeing JSON for AutobahnFileConfig.

return os.Getenv(evmOnlyInMemoryEnv) == "true"
}

func assertEVMOnlyInMemoryEnabled(t *testing.T) {
t.Helper()
for _, name := range listRunningNodes(t) {
cmd := exec.Command("docker", "exec", name, "sh", "-c",
"grep -q 'Autobahn EVM-only in-memory execution enabled' build/generated/logs/seid-*.log")
if out, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("EVM-only in-memory execution not enabled on %s: %v\n%s", name, err, out)
}
}
}

// dockerExec runs `docker exec <container> sh -c <script>` and returns stdout.
func dockerExec(t *testing.T, container, script string) string {
t.Helper()
Expand Down Expand Up @@ -337,10 +366,12 @@ func TestMain(m *testing.M) {
teardownCluster() // best-effort
os.Exit(1)
}
if err := setupFullnodeNode(); err != nil {
fmt.Fprintf(os.Stderr, "fullnode sidecar setup failed: %v\n", err)
teardownCluster()
os.Exit(1)
if !evmOnlyInMemoryEnabled() {
if err := setupFullnodeNode(); err != nil {
fmt.Fprintf(os.Stderr, "fullnode sidecar setup failed: %v\n", err)
teardownCluster()
os.Exit(1)
}
}
code := m.Run()
teardownCluster()
Expand Down Expand Up @@ -564,6 +595,10 @@ func TestAutobahn(t *testing.T) {
// validator sets.
maxFaults = (clusterSize - 1) / 3
t.Logf("cluster size = %d, max tolerated faults = %d (assuming equal weights)", clusterSize, maxFaults)
if evmOnlyInMemoryEnabled() {
t.Run("EVMOnlyLoad", testEVMOnlyLoad)
return
}

t.Run("BlockProduction", testBlockProduction)
t.Run("EVMTransfer", testEVMTransfer)
Expand All @@ -572,6 +607,115 @@ func TestAutobahn(t *testing.T) {
t.Run("Recovery", testRecovery)
}

type evmOnlyLoadState struct{}

func (evmOnlyLoadState) SetBalance(common.Address, *big.Int) {}
func (evmOnlyLoadState) SetCode(common.Address, []byte) {}
func (evmOnlyLoadState) SetState(common.Address, common.Hash, common.Hash) {}

func testEVMOnlyLoad(t *testing.T) {
assertAutobahnEnabled(t)
assertEVMOnlyInMemoryEnabled(t)
if clusterSize != 4 {
t.Fatalf("EVM-only Docker load test requires four validators, got %d", clusterSize)
}

workload, err := scenarios.NewTransferWorkload(scenarios.Config{
TxsPerBlock: evmOnlyLoadTxs,
ChainID: new(big.Int).SetUint64(tmconfig.AutobahnEVMOnlyInMemoryChainID),
GasPrice: big.NewInt(1_000_000_000),
SenderBalance: new(big.Int).Lsh(big.NewInt(1), 200),
TransferValue: big.NewInt(1),
TxGasLimit: 21_000,
}, evmOnlyLoadState{})
if err != nil {
t.Fatalf("create EVM-only transfer workload: %v", err)
}
ctx, cancel := context.WithTimeout(t.Context(), evmOnlyLoadTimeout)
defer cancel()
block, err := workload.BuildBlock(ctx, 1)
if err != nil {
t.Fatalf("build EVM-only transfer workload: %v", err)
}

clients := make([]*tmhttp.HTTP, clusterSize)
for i := range clients {
client, err := tmhttp.New(fmt.Sprintf("http://localhost:%d", 26657+3*i))
if err != nil {
t.Fatalf("create node %d RPC client: %v", i, err)
}
clients[i] = client
}
started := time.Now()
group, groupCtx := errgroup.WithContext(ctx)
for nodeIndex, client := range clients {
nodeIndex, client := nodeIndex, client
group.Go(func() error {
for txIndex := nodeIndex; txIndex < len(block.Txs); txIndex += len(clients) {
response, err := client.BroadcastTxSync(groupCtx, tmtypes.Tx(block.Txs[txIndex]))
if err != nil {
return fmt.Errorf("broadcast tx %d through node %d: %w", txIndex, nodeIndex, err)
}
if response.Code != 0 {
return fmt.Errorf("broadcast tx %d through node %d: code=%d log=%s", txIndex, nodeIndex, response.Code, response.Log)
}
}
return nil
})
}
if err := group.Wait(); err != nil {
t.Fatal(err)
}

lastHeight, included := waitForEVMOnlyTxs(t, ctx, clients[0], len(block.Txs))
waitForEVMOnlyHeight(t, ctx, clients, lastHeight)
elapsed := time.Since(started)
t.Logf("Autobahn finalized %d raw EVM transfers through %d validators in %s (%.0f tx/s)",
included, clusterSize, elapsed.Round(time.Millisecond), float64(included)/elapsed.Seconds())
}

func waitForEVMOnlyTxs(t *testing.T, ctx context.Context, client *tmhttp.HTTP, target int) (int64, int) {
t.Helper()
nextHeight := int64(1)
included := 0
for included < target {
if err := ctx.Err(); err != nil {
t.Fatalf("waiting for %d EVM-only transactions: finalized %d: %v", target, included, err)
}
latest, err := client.Block(ctx, nil)
if err != nil || latest.Block == nil || latest.Block.Height < nextHeight {
time.Sleep(100 * time.Millisecond)
continue
}
for nextHeight <= latest.Block.Height {
height := nextHeight
block, err := client.Block(ctx, &height)
if err != nil || block.Block == nil {
break
}
included += len(block.Block.Data.Txs)
nextHeight++
}
}
return nextHeight - 1, included
}

func waitForEVMOnlyHeight(t *testing.T, ctx context.Context, clients []*tmhttp.HTTP, target int64) {
t.Helper()
for i, client := range clients {
for {
if err := ctx.Err(); err != nil {
t.Fatalf("waiting for node %d to execute EVM-only height %d: %v", i, target, err)
}
block, err := client.Block(ctx, nil)
if err == nil && block.Block != nil && block.Block.Height >= target {
break
}
time.Sleep(100 * time.Millisecond)
}
}
}

// restartNode re-invokes the container's seid-start script inside sei-node-<i>.
// The script backgrounds seid and exits, so `docker exec -d` is the right mode:
// it returns immediately while seid keeps running.
Expand Down
8 changes: 8 additions & 0 deletions sei-tendermint/config/autobahn.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,16 @@ type AutobahnFileConfig struct {
// (see AutobahnBlockDBConfig for field semantics). Ignored when
// PersistentStateDir is absent (memblock). Omitted from JSON when empty.
BlockDB AutobahnBlockDBConfig `json:"block_db,omitzero"`
// EVMOnlyInMemory replaces the Cosmos application used by Autobahn with an
// ephemeral EVM-only executor. It exists only for Docker load testing and
// must not be enabled on a persistent network.
EVMOnlyInMemory bool `json:"evm_only_in_memory,omitzero"`

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[suggestion] The doc comment says this "must not be enabled on a persistent network", but nothing enforces it: AutobahnFileConfig.Validate accepts evm_only_in_memory: true together with persistent_state_dir, and only the docker script happens to del(.persistent_state_dir). A hand-written config with both set gets persisted consensus blocks over an EVM state that resets to the funded base on every restart. Validate() is the choke point every config load passes through — rejecting the combination there makes it an invariant instead of a convention the next config author has to remember.

}

// AutobahnEVMOnlyInMemoryChainID is the EVM chain ID used by Autobahn's
// ephemeral EVM-only load-test runtime.
const AutobahnEVMOnlyInMemoryChainID uint64 = 1337

func (c *AutobahnFileConfig) GetEnableEvmProxy() bool {
return c.EnableEvmProxy.Or(true)
}
Expand Down
Loading
Loading