-
Notifications
You must be signed in to change notification settings - Fork 886
Integrate Autobahn with in-memory EVM-only executor #4028
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
base: codex/integrate-evmonly-giga-store
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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. | ||
|
|
@@ -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" | ||
|
Contributor
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. 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 | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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): | ||
| // | ||
|
|
@@ -13,9 +14,11 @@ package autobahn | |
|
|
||
| import ( | ||
| "bufio" | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "io" | ||
| "math/big" | ||
| "net/http" | ||
| "os" | ||
| "os/exec" | ||
|
|
@@ -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" | ||
| ) | ||
|
|
||
|
|
@@ -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 ( | ||
|
|
@@ -200,6 +214,21 @@ func assertAutobahnEnabled(t *testing.T) { | |
| } | ||
| } | ||
|
|
||
| func evmOnlyInMemoryEnabled() bool { | ||
|
Contributor
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 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() | ||
|
|
@@ -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() | ||
|
|
@@ -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) | ||
|
|
@@ -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. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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"` | ||
|
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. [suggestion] The doc comment says this "must not be enabled on a persistent network", but nothing enforces it: |
||
| } | ||
|
|
||
| // 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) | ||
| } | ||
|
|
||
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.
[suggestion] This drops the
&&guard the repo's other jq rewrites use (override_genesisinstep2_genesis.sh:13isjq ... > tmp && mv tmp target). The script has noset -e, so if jq fails themvon the next line still runs and replacesautobahn.jsonwith a truncated file, and the node fails later with an unrelated-looking config error. Chaining> "$AUTOBAHN_CONFIG_TMP" && mv ...restores the existing idiom.