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 Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,7 @@ CLUSTER_ENV_VARS = DOCKER_PLATFORM=$(DOCKER_PLATFORM) USERID=$(shell id -u) GROU
GIGA_OCC=$(GIGA_OCC) \
RECEIPT_BACKEND=$(RECEIPT_BACKEND) \
AUTOBAHN=$(AUTOBAHN) \
AUTOBAHN_EVMONLY_IN_MEMORY=$(AUTOBAHN_EVMONLY_IN_MEMORY) \
AUTOBAHN_EVMONLY=$(AUTOBAHN_EVMONLY) \
GIGA_STORAGE=$(GIGA_STORAGE) \
GIGA_MIGRATE_FROM_MEMIAVL=$(GIGA_MIGRATE_FROM_MEMIAVL) \
GIGA_FLATKV_ONLY=$(GIGA_FLATKV_ONLY)
Expand Down Expand Up @@ -559,9 +559,9 @@ autobahn-integration-test:
@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.
# Run the disk-backed 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/...
@AUTOBAHN_EVMONLY=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.
Expand Down
2 changes: 1 addition & 1 deletion cmd/autobahn-e2e/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ func (a *application) startRemoteCluster(ctx context.Context, state clusterState
"git clone --filter=blob:none " + shellQuote(aws.RepoURL) + " " + shellQuote(aws.RemoteDir),
"cd " + shellQuote(aws.RemoteDir),
"git checkout --detach " + shellQuote(aws.Ref),
"AUTOBAHN=true AUTOBAHN_EVMONLY_IN_MEMORY=true DOCKER_DETACH=true make docker-cluster-start",
"AUTOBAHN=true AUTOBAHN_EVMONLY=true DOCKER_DETACH=true make docker-cluster-start",
}, " && ")
if err := a.runner.stream(ctx, sshCommand(state, command)); err != nil {
return fmt.Errorf("start remote cluster: %w", err)
Expand Down
2 changes: 1 addition & 1 deletion cmd/autobahn-e2e/command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ func TestAWSDeployCreatesManagedResourcesAndReadyState(t *testing.T) {
commands := joinedCommands(runner.commands)
require.Contains(t, commands, "authorize-security-group-ingress")
require.Contains(t, commands, "--cidr 198.51.100.4/32")
require.Contains(t, commands, "AUTOBAHN_EVMONLY_IN_MEMORY=true")
require.Contains(t, commands, "AUTOBAHN_EVMONLY=true")
require.Contains(t, commands, "-o StrictHostKeyChecking=accept-new")
}

Expand Down
2 changes: 1 addition & 1 deletion cmd/autobahn-e2e/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ func (a *application) deployLocal(ctx context.Context, options deployOptions) er
dir: repoRoot,
env: []string{
"AUTOBAHN=true",
"AUTOBAHN_EVMONLY_IN_MEMORY=true",
"AUTOBAHN_EVMONLY=true",
"DOCKER_DETACH=true",
},
name: "make",
Expand Down
8 changes: 4 additions & 4 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ services:
- GIGA_OCC
- RECEIPT_BACKEND
- AUTOBAHN
- AUTOBAHN_EVMONLY_IN_MEMORY
- AUTOBAHN_EVMONLY
- GIGA_STORAGE
- GIGA_MIGRATE_FROM_MEMIAVL
- GIGA_FLATKV_ONLY
Expand Down Expand Up @@ -56,7 +56,7 @@ services:
- GIGA_OCC
- RECEIPT_BACKEND
- AUTOBAHN
- AUTOBAHN_EVMONLY_IN_MEMORY
- AUTOBAHN_EVMONLY
- GIGA_STORAGE
- GIGA_MIGRATE_FROM_MEMIAVL
- GIGA_FLATKV_ONLY
Expand Down Expand Up @@ -88,7 +88,7 @@ services:
- GIGA_OCC
- RECEIPT_BACKEND
- AUTOBAHN
- AUTOBAHN_EVMONLY_IN_MEMORY
- AUTOBAHN_EVMONLY
- GIGA_STORAGE
- GIGA_MIGRATE_FROM_MEMIAVL
- GIGA_FLATKV_ONLY
Expand Down Expand Up @@ -124,7 +124,7 @@ services:
- GIGA_OCC
- RECEIPT_BACKEND
- AUTOBAHN
- AUTOBAHN_EVMONLY_IN_MEMORY
- AUTOBAHN_EVMONLY
- GIGA_STORAGE
- GIGA_MIGRATE_FROM_MEMIAVL
- GIGA_FLATKV_ONLY
Expand Down
2 changes: 1 addition & 1 deletion docker/localnode/config/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ moniker = "sei-node-0"
mode = "validator"

# Test-only application replacement for Autobahn EVM load tests
evm-only-in-memory = false
evm-only = false

# Database backend: goleveldb | cleveldb | boltdb | rocksdb | badgerdb
# * goleveldb (github.com/syndtr/goleveldb - most popular implementation)
Expand Down
10 changes: 5 additions & 5 deletions docker/localnode/scripts/step4_config_override.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ VALIDATOR=${VALIDATOR:-true}
GIGA_EXECUTOR=${GIGA_EXECUTOR:-true}
GIGA_OCC=${GIGA_OCC:-true}
AUTOBAHN=${AUTOBAHN:-false}
AUTOBAHN_EVMONLY_IN_MEMORY=${AUTOBAHN_EVMONLY_IN_MEMORY:-false}
AUTOBAHN_EVMONLY=${AUTOBAHN_EVMONLY:-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 @@ -169,14 +169,14 @@ if [ "$AUTOBAHN" = "true" ]; then
NODE_DIRS="$NODE_DIRS build/generated/node_${i}"
done

if [ "$AUTOBAHN_EVMONLY_IN_MEMORY" = "true" ]; then
seid tendermint gen-autobahn-config $NODE_DIRS --output "$AUTOBAHN_CONFIG" --persistent-state-dir=
sed -i 's/^evm-only-in-memory = .*/evm-only-in-memory = true/' ~/.sei/config/config.toml
if [ "$AUTOBAHN_EVMONLY" = "true" ]; then
seid tendermint gen-autobahn-config $NODE_DIRS --output "$AUTOBAHN_CONFIG"
sed -i 's/^evm-only = .*/evm-only = true/' ~/.sei/config/config.toml
sed -i '/^\[rpc\]/,/^\[/ s|^laddr = .*|laddr = ""|' ~/.sei/config/config.toml
sed -i '/^\[api\]/,/^\[/ s/^enable = .*/enable = false/' ~/.sei/config/app.toml
sed -i '/^\[grpc\]/,/^\[/ s/^enable = .*/enable = false/' ~/.sei/config/app.toml
sed -i '/^\[grpc-web\]/,/^\[/ s/^enable = .*/enable = false/' ~/.sei/config/app.toml
echo "Enabled Autobahn EVM-only execution with only eth_sendRawTransaction RPC for node $NODE_ID"
echo "Enabled Autobahn EVM-only execution with transaction submission and receipt RPC for node $NODE_ID"
else
seid tendermint gen-autobahn-config $NODE_DIRS --output "$AUTOBAHN_CONFIG"
fi
Expand Down
2 changes: 1 addition & 1 deletion docker/localnode/scripts/step5_start_sei.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ echo "Node $NODE_ID seid is started now"
# launch.complete means the node's query surface is available, not merely that
# the process has started.
node_query_ready() {
if [ "${AUTOBAHN_EVMONLY_IN_MEMORY:-false}" = "true" ]; then
if [ "${AUTOBAHN_EVMONLY:-false}" = "true" ]; then
curl -fsS -X POST \
-H 'content-type: application/json' \
--data '{"jsonrpc":"2.0","id":1,"method":"eth_sendRawTransaction","params":["0x01"]}' \
Expand Down
63 changes: 39 additions & 24 deletions giga/evmonly/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ The `evmonly` package currently provides:
transaction execution with granular validation and reruns
- Ethereum receipt construction with logs, bloom, gas, tx hash, block metadata,
contract address, and effective gas price
- receipt persistence through the real Giga `ReceiptStore` backend in load-test
runtimes, with a concurrency-safe in-memory implementation for unit tests
- a versioned `MemoryStore` giga implementation over an immutable `StateReader`
for tests and load generation
- fail-closed custom precompile placeholders
Expand Down Expand Up @@ -70,34 +72,47 @@ prepare then execute in one call. `PreparedBlock` is trusted executor-produced
data: callers should pass the result of `PrepareBlock` unchanged, because
`ExecutePreparedBlock` does not recover senders again.

The executor is always store-backed. `WithStore(...)` selects the `giga.StateDB`
implementation and its `NamedChangeSetEncoder`; execution fails closed if
either is missing. For each block the executor opens a current
`giga.StateView`, executes against its EVM-native read methods, converts the
resulting `StateChangeSet`, and calls `CommitStateChanges`. Execution and commit
on an executor are serialized so blocks cannot share a stale snapshot or
overlap commits; callers must still submit block heights in order. The snapshot
stays open through the commit and is always closed afterward. An empty block
still commits an encoded empty changeset so the store can advance its height.
Stateless preparation can continue concurrently with store-backed execution.
The executor is always store-backed. `WithStorageManager(...)` selects the
`bootstrap.GigaStorageManager` that provides both `giga.StateDB` and the ledger
receipt store, plus the `NamedChangeSetEncoder` for its state implementation.
Unit tests can supply those dependencies independently. Execution fails closed
if either store or the encoder is missing.
For each block the executor opens a current `giga.StateView`, executes against
its EVM-native read methods, converts the resulting `StateChangeSet`, and calls
`CommitStateChanges`. Execution and commit on an executor are serialized so
blocks cannot share a stale snapshot or overlap commits; callers must still
submit block heights in order. The snapshot stays open through the commit and
is always closed afterward. An empty block still commits an encoded empty
changeset so the store can advance its height. Stateless preparation can
continue concurrently with store-backed execution.

The encoder is explicit because `giga.StateDB` defines the protobuf commit
transport but does not define an on-disk key layout. In particular, an encoder
must preserve `StorageClears` as prefix clears rather than silently dropping
persisted slots that were not read during execution. Encoding or commit failures
release the block result and return an error without invoking `ResultSink`.
`ResultSink` runs after the state commit succeeds; a sink error does not roll
back that commit.

`MemoryStore` is the non-persistent implementation used by tests and the load
harness. It wraps an immutable `StateReader`, encodes changes directly into
typed `NamedChangeSet` key/value pairs, and retains committed values in
versioned overlays so current and historical snapshots stay stable without
copying the complete base state per block. It is not the production SC/SS
implementation. Every base
`StateReader` method must be safe for concurrent calls, and returned balances
and code must remain immutable while read. Call `Close()` to disable future OCC
execution on an executor.
persisted slots that were not read during execution. Encoding, state commit, or
receipt-store failures release the block result and return an error without
invoking `ResultSink`. Ethereum receipts are converted into
`receipt.ReceiptRecord` values and persisted through the shared
`receipt.ReceiptStore` interface before the height-advancing state commit,
including for empty blocks. A receipt failure leaves state unchanged so the
block can be retried. A state failure can leave receipts behind, but retrying
the block overwrites them. `ResultSink` runs only after both stores succeed.

The FlatKV encoder persists balance, nonce, code, and storage changes, and the
executor reads them through the current Giga state view. EVM-only Autobahn load
tests use `WithMissingAccountState(...)` to supply the initial funded state for
synthetic accounts that have not appeared in FlatKV yet; after their first
change, subsequent reads come from the persisted account row.

`MemoryStore` and `MemoryReceiptStore` are non-persistent unit-test doubles.
`MemoryStore` wraps an immutable `StateReader`, retains committed values in
versioned overlays, and keeps current and historical snapshots stable without
copying the complete base state per block. `MemoryReceiptStore` implements the
shared receipt interface and indexes cloned Sei receipt records by block number
and transaction hash. Load-test runtimes use the real Giga storage manager
instead. Every base `StateReader` method must be safe for concurrent calls, and
returned balances and code must remain immutable while read. Call `Close()` to
disable future OCC execution on an executor.

A non-nil `error` means block validation failed and the caller must not commit a
partial output. EVM call failures inside an otherwise valid transaction are
Expand Down
26 changes: 16 additions & 10 deletions giga/evmonly/cmd/evmonly-loadtest/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# evmonly-loadtest

`evmonly-loadtest` is a standalone executable for feeding synthetic blocks to
the EVM-only executor through an in-memory `giga.StateDB`, without Cosmos SDK
state, mempool, RPC, or production SC/SS persistence.
the EVM-only executor through the disk-backed Giga state and receipt stores,
without Cosmos SDK state, mempool, or RPC. It opens the validator-mode Giga
storage manager with FlatKV state, littidx receipts, and the block store in a
temporary directory that is removed when the load test exits. GigaSS remains
disabled because EVM-only execution does not use it.

The synthetic workload defaults to local EVM chain ID `1337`; override it with
`--chain-id` when testing another signing domain.
Expand All @@ -11,9 +14,10 @@ It currently generates pure EVM legacy transfer transactions, ERC20 transfer
transactions using `sei-load`'s compiled contract runtime, and a contract-call
workload that exercises nested StateDB
snapshot/revert behavior. By default, each generated sender account has one
nonce-0 transaction and is funded in the command's in-memory genesis state
before its block is queued. Recipients are unique by default so the transfer
workloads exercise the optimistic no-overlap case. Pass
nonce-0 transaction and is funded in generated genesis state. Non-balance
genesis state is committed to FlatKV before the measured blocks run. Recipients
are unique by default so the transfer workloads exercise the optimistic
no-overlap case. Pass
`--recipient-conflict-rate=<0..1>` to pair that fraction of each block's
transactions onto shared recipients, or pass `--recipient=0x...` to force all
transactions to a single recipient. Pass `--same-sender` to use one sender per
Expand Down Expand Up @@ -178,14 +182,16 @@ The command reports these saturation signals on stdout and at `/metrics`:

Every run uses the Giga executor lifecycle:

- `generatedState` implements `evmonly.StateReader` and supplies immutable
generated genesis balances, nonces, code, and storage.
- `evmonly.MemoryStore` opens versioned snapshots over that genesis state and
applies the executor's encoded output through `CommitStateChanges`.
- `generatedState` builds deterministic genesis balances, nonces, code, and
storage. The harness commits that state to FlatKV at height 1.
- The measured workload begins at height 2 and commits state and receipts
through the real Giga storage manager. The manager also opens the
production block store; the standalone harness has no consensus layer to
populate it.
- `discardResultSink` discards the already-committed block result and receipts;
it is not responsible for state persistence.

With `--result-sink=file`, after the in-memory Giga commit succeeds the loadtest
With `--result-sink=file`, after the Giga commit succeeds the loadtest
harness hands pooled `evmonly.BlockResult` values to an async writer through the
executor's `evmonly.ResultSink` interface. The writer appends changesets to
`changesets.rlp` and receipts to `receipts.rlp` under `--persist-dir`; each
Expand Down
10 changes: 8 additions & 2 deletions giga/evmonly/cmd/evmonly-loadtest/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ import (

func withGeneratedState(state evmonly.StateReader) evmonly.Option {
store := evmonly.NewMemoryStore(state)
return evmonly.WithStore(store, store.EncodeChangeSet)
return func(executor *evmonly.Executor) {
evmonly.WithStore(store, store.EncodeChangeSet)(executor)
evmonly.WithReceiptStore(evmonly.NewMemoryReceiptStore())(executor)
}
}

type readOnlyGeneratedStore struct {
Expand All @@ -41,7 +44,10 @@ func (*readOnlyGeneratedStore) CommitStateChanges(int64, []*proto.NamedChangeSet

func withReadOnlyGeneratedState(state evmonly.StateReader) evmonly.Option {
store := &readOnlyGeneratedStore{MemoryStore: evmonly.NewMemoryStore(state)}
return evmonly.WithStore(store, store.EncodeChangeSet)
return func(executor *evmonly.Executor) {
evmonly.WithStore(store, store.EncodeChangeSet)(executor)
evmonly.WithReceiptStore(evmonly.NewMemoryReceiptStore())(executor)
}
}

func TestTransferWorkloadExecutesAgainstEVMOnlyExecutor(t *testing.T) {
Expand Down
33 changes: 30 additions & 3 deletions giga/evmonly/cmd/evmonly-loadtest/pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/prometheus/client_golang/prometheus"
"github.com/sei-protocol/sei-chain/giga/evmonly"
"github.com/sei-protocol/sei-chain/giga/evmonly/cmd/evmonly-loadtest/scenarios"
"github.com/sei-protocol/sei-chain/sei-db/bootstrap"
"golang.org/x/sync/errgroup"
)

Expand Down Expand Up @@ -105,6 +106,33 @@ func runPrebuilt(ctx context.Context, cfg config, state *generatedState, workloa
prebuildElapsed := time.Since(prebuildStartedAt)
printPrebuildReport(prebuildElapsed, prebuilt, cfg.txsPerBlock)

storageDirectory, err := os.MkdirTemp("", "evmonly-loadtest-storage-")
if err != nil {
return fmt.Errorf("create storage directory: %w", err)
}
defer func() {
err = errors.Join(err, os.RemoveAll(storageDirectory))
}()
storageConfig, err := evmonly.NewValidatorStorageConfig(storageDirectory)
if err != nil {
return fmt.Errorf("configure storage manager: %w", err)
}
storage, err := bootstrap.NewGigaStorageManager(ctx, storageConfig)
if err != nil {
return fmt.Errorf("open storage manager: %w", err)
}
defer func() {
err = errors.Join(err, storage.Close())
}()
changeSetEncoder := evmonly.NewFlatKVChangeSetEncoder(storage.SC())
genesisChanges, err := changeSetEncoder(state.changeSet())
if err != nil {
return fmt.Errorf("encode generated genesis state: %w", err)
}
if err := storage.StateDB().CommitStateChanges(1, genesisChanges); err != nil {
return fmt.Errorf("commit generated genesis state: %w", err)
}

profiles, err := startProfiles(cfg)
if err != nil {
return err
Expand All @@ -121,10 +149,9 @@ func runPrebuilt(ctx context.Context, cfg config, state *generatedState, workloa

startedAt := time.Now()
group, groupCtx := errgroup.WithContext(ctx)
store := evmonly.NewMemoryStore(state)
executor := evmonly.NewExecutor(
executorConfig(cfg),
evmonly.WithStore(store, store.EncodeChangeSet),
evmonly.WithStorageManager(storage, changeSetEncoder),
evmonly.WithResultSink(sinks),
)
defer executor.Close()
Expand Down Expand Up @@ -174,7 +201,7 @@ func prebuildBlockRequests(ctx context.Context, cfg config, workload blockWorklo
if number > cfg.blocks {
return nil
}
request, err := workload.BuildBlock(groupCtx, number)
request, err := workload.BuildBlock(groupCtx, number+1)
if err != nil {
if groupCtx.Err() != nil {
return nil
Expand Down
Loading
Loading