Skip to content
Open
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
8 changes: 4 additions & 4 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,9 +169,9 @@ 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
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
68 changes: 41 additions & 27 deletions giga/evmonly/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@ The `evmonly` package currently provides:
- go-ethereum `core.ApplyMessage` execution against an SDK-free `vm.StateDB`
- key-addressable state reads for balance, nonce, code, and storage
- deterministic post-block `StateChangeSet` construction
- direct snapshot reads and ordered state commits through `giga.StateDB`
- direct snapshot reads and ordered state commits through the Giga `StateDB`
- optional executor-internal Block-STM-style execution for optimistic parallel
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,46 @@ 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 encoder is explicit because `giga.StateDB` defines the protobuf commit
The executor is always store-backed. `WithStorageManager(...)` selects the
`bootstrap.GigaStorageManager` that provides both the 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 the 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.

FlatKV does not yet expose balance reads and writes. EVM-only runtimes therefore
use `PlaceholderBalanceStore` for balances while committing nonce, code, and
storage changes to the manager-owned state database. The placeholder applies
post-block balances only after the persistent state commit succeeds.

`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
64 changes: 64 additions & 0 deletions giga/evmonly/balance_store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package evmonly

import (
"math/big"
"sync"

"github.com/ethereum/go-ethereum/common"
)

// BalanceReader returns an account's current EVM balance.
type BalanceReader interface {
GetBalance(common.Address) *big.Int
}

// BalanceStore holds current EVM balances over an immutable initial balance
// source.
type BalanceStore interface {
BalanceReader
ApplyBalanceChanges([]BalanceChange)
}

// PlaceholderBalanceStore is a process-local BalanceStore for runtimes whose
// persistent state backend does not expose EVM balances.
type PlaceholderBalanceStore struct {
mu sync.RWMutex
initial BalanceReader
balances map[common.Address]*big.Int
}

// NewPlaceholderBalanceStore returns a balance store backed by initial for
// accounts without an applied balance change.
func NewPlaceholderBalanceStore(initial BalanceReader) *PlaceholderBalanceStore {
return &PlaceholderBalanceStore{
initial: initial,
balances: make(map[common.Address]*big.Int),
}
}

// GetBalance returns the latest applied balance or the account's initial
// balance when it has not changed.
func (s *PlaceholderBalanceStore) GetBalance(address common.Address) *big.Int {
s.mu.RLock()
balance, ok := s.balances[address]
if ok {
balance = cloneBig(balance)
}
s.mu.RUnlock()
if ok {
return balance
}
if s.initial == nil {
return new(big.Int)
}
return cloneBig(s.initial.GetBalance(address))
}

// ApplyBalanceChanges installs the post-block balances in changes.
func (s *PlaceholderBalanceStore) ApplyBalanceChanges(changes []BalanceChange) {
s.mu.Lock()
defer s.mu.Unlock()
for _, change := range changes {
s.balances[change.Address] = cloneBig(change.Balance)
}
}
26 changes: 26 additions & 0 deletions giga/evmonly/balance_store_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package evmonly

import (
"math/big"
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)

func TestPlaceholderBalanceStoreAppliesOwnedBalanceChanges(t *testing.T) {
address := common.Address{0x11}
initial := NewMemoryState()
initial.SetBalance(address, big.NewInt(10))
store := NewPlaceholderBalanceStore(initial)

require.Equal(t, big.NewInt(10), store.GetBalance(address))

updated := big.NewInt(20)
store.ApplyBalanceChanges([]BalanceChange{{Address: address, Balance: updated}})
updated.SetInt64(30)
got := store.GetBalance(address)
require.Equal(t, big.NewInt(20), got)
got.SetInt64(40)
require.Equal(t, big.NewInt(20), store.GetBalance(address))
}
29 changes: 19 additions & 10 deletions giga/evmonly/cmd/evmonly-loadtest/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# 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 complete production
storage manager, including the block store, in a temporary directory that is
removed when the load test exits.

The synthetic workload defaults to local EVM chain ID `1337`; override it with
`--chain-id` when testing another signing domain.
Expand All @@ -11,9 +13,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 +181,20 @@ 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 nonce, code, and storage state to FlatKV at
height 1.
- Balances use a process-local placeholder until FlatKV exposes balance reads
and writes. The placeholder applies every post-block balance change, so load
execution preserves balance semantics without changing the storage package.
- The measured workload begins at height 2 and commits non-balance 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
Loading
Loading