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
166 changes: 107 additions & 59 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"fmt"
"io"
"math"
"math/big"
"net/http"
"os"
"path/filepath"
Expand Down Expand Up @@ -92,6 +93,7 @@ import (
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/ethclient"
ethparams "github.com/ethereum/go-ethereum/params"
ethrpc "github.com/ethereum/go-ethereum/rpc"
"github.com/sei-protocol/sei-chain/giga/deps/tasks"

Expand Down Expand Up @@ -318,6 +320,38 @@ func GetWasmEnabledProposals() []wasm.ProposalType {
// App extends an ABCI application, but with most of its parameters exported.
// They are exported for convenience in creating helper functions, as object
// capabilities aren't needed for testing.
// gigaBlockCache holds block-constant values that are identical for all txs in a block.
// Populated once before block execution, read-only during parallel execution, cleared after.
type gigaBlockCache struct {
chainID *big.Int
blockCtx vm.BlockContext
chainConfig *ethparams.ChainConfig
baseFee *big.Int

// when true, Cosmos-level events (coin_spent, coin_received, etc.) are
// discarded during giga executor tx execution. EVM logs are unaffected.
suppressCosmosEvents bool
}

func newGigaBlockCache(ctx sdk.Context, keeper *gigaevmkeeper.Keeper) (*gigaBlockCache, error) {
chainID := keeper.ChainID(ctx)
gp := keeper.GetGasPool()
blockCtx, err := keeper.GetVMBlockContext(ctx, gp)
if err != nil {
return nil, err
}
sstore := keeper.GetParams(ctx).SeiSstoreSetGasEip2200
chainConfig := evmtypes.DefaultChainConfig().EthereumConfigWithSstore(chainID, &sstore)
baseFee := keeper.GetBaseFee(ctx)
return &gigaBlockCache{
chainID: chainID,
blockCtx: *blockCtx,
chainConfig: chainConfig,
baseFee: baseFee,
suppressCosmosEvents: os.Getenv("GIGA_SUPPRESS_COSMOS_EVENTS") == "true",
}, nil
}

type App struct {
*baseapp.BaseApp

Expand Down Expand Up @@ -1373,6 +1407,14 @@ func (app *App) ProcessTxsSynchronousGiga(ctx sdk.Context, txs [][]byte, typedTx
ms := ctx.MultiStore().CacheMultiStore()
defer ms.Write()
ctx = ctx.WithMultiStore(ms)

// Cache block-level constants (identical for all txs in this block).
cache, cacheErr := newGigaBlockCache(ctx, &app.GigaEvmKeeper)
if cacheErr != nil {
ctx.Logger().Error("failed to build giga block cache", "error", cacheErr, "height", ctx.BlockHeight())
return nil
}

txResults := make([]*abci.ExecTxResult, len(txs))
for i, tx := range txs {
ctx = ctx.WithTxIndex(absoluteTxIndices[i])
Expand All @@ -1386,7 +1428,7 @@ func (app *App) ProcessTxsSynchronousGiga(ctx sdk.Context, txs [][]byte, typedTx
}

// Execute EVM transaction through giga executor
result, execErr := app.executeEVMTxWithGigaExecutor(ctx, evmMsg)
result, execErr := app.executeEVMTxWithGigaExecutor(ctx, evmMsg, cache)
if execErr != nil {
// Check if this is a fail-fast error (Cosmos precompile interop detected)
if gigautils.ShouldExecutionAbort(execErr) {
Expand Down Expand Up @@ -1544,15 +1586,24 @@ func (app *App) ProcessTXsWithOCCGiga(ctx sdk.Context, txs [][]byte, typedTxs []
}
}

// Create OCC scheduler with giga executor deliverTx.
// Run EVM txs against a cache so we can discard all changes on fallback.
evmCtx, evmCache := app.CacheContext(ctx)

// Cache block-level constants (identical for all txs in this block).
// Must use evmCtx (not ctx) because giga KV stores are registered in CacheContext.
cache, cacheErr := newGigaBlockCache(evmCtx, &app.GigaEvmKeeper)
if cacheErr != nil {
ctx.Logger().Error("failed to build giga block cache", "error", cacheErr, "height", ctx.BlockHeight())
return nil, ctx
}

// Create OCC scheduler with giga executor deliverTx capturing the cache.
evmScheduler := tasks.NewScheduler(
app.ConcurrencyWorkers(),
app.TracingInfo,
app.gigaDeliverTx,
app.makeGigaDeliverTx(cache),
)

// Run EVM txs against a cache so we can discard all changes on fallback.
evmCtx, evmCache := app.CacheContext(ctx)
evmBatchResult, evmSchedErr := evmScheduler.ProcessAll(evmCtx, evmEntries)
if evmSchedErr != nil {
// TODO: DeliverTxBatch panics in this case
Expand Down Expand Up @@ -1713,14 +1764,14 @@ func (app *App) ProcessBlock(ctx sdk.Context, txs [][]byte, req BlockProcessRequ

// executeEVMTxWithGigaExecutor executes a single EVM transaction using the giga executor.
// The sender address is recovered directly from the transaction signature - no Cosmos SDK ante handlers needed.
func (app *App) executeEVMTxWithGigaExecutor(ctx sdk.Context, msg *evmtypes.MsgEVMTransaction) (*abci.ExecTxResult, error) {
func (app *App) executeEVMTxWithGigaExecutor(ctx sdk.Context, msg *evmtypes.MsgEVMTransaction, cache *gigaBlockCache) (*abci.ExecTxResult, error) {
// Get the Ethereum transaction from the message
ethTx, txData := msg.AsTransaction()
if ethTx == nil || txData == nil {
return nil, fmt.Errorf("failed to convert to eth transaction")
}

chainID := app.GigaEvmKeeper.ChainID(ctx)
chainID := cache.chainID

// Recover sender using the same logic as preprocess.go (version-based signer selection)
sender, seiAddr, pubkey, recoverErr := evmante.RecoverSenderFromEthTx(ctx, ethTx, chainID)
Expand All @@ -1747,29 +1798,23 @@ func (app *App) executeEVMTxWithGigaExecutor(ctx sdk.Context, msg *evmtypes.MsgE

// Create state DB for this transaction
stateDB := gigaevmstate.NewDBImpl(ctx, &app.GigaEvmKeeper, false)
if cache.suppressCosmosEvents {
stateDB.SuppressCosmosEvents()
}
defer stateDB.Cleanup()

// Get gas pool
// Get gas pool (mutated per tx, cannot be cached)
gp := app.GigaEvmKeeper.GetGasPool()

// Get block context
blockCtx, blockCtxErr := app.GigaEvmKeeper.GetVMBlockContext(ctx, gp)
if blockCtxErr != nil {
return &abci.ExecTxResult{
Code: 1,
Log: fmt.Sprintf("failed to get block context: %v", blockCtxErr),
}, nil
}

// Get chain config
sstore := app.GigaEvmKeeper.GetParams(ctx).SeiSstoreSetGasEip2200
cfg := evmtypes.DefaultChainConfig().EthereumConfigWithSstore(app.GigaEvmKeeper.ChainID(ctx), &sstore)
// Use cached block-level constants
blockCtx := cache.blockCtx
cfg := cache.chainConfig

// Create Giga executor VM
gigaExecutor := gigaexecutor.NewGethExecutor(*blockCtx, stateDB, cfg, vm.Config{}, gigaprecompiles.AllCustomPrecompilesFailFast)
gigaExecutor := gigaexecutor.NewGethExecutor(blockCtx, stateDB, cfg, vm.Config{}, gigaprecompiles.AllCustomPrecompilesFailFast)

// Execute the transaction through giga VM
execResult, execErr := gigaExecutor.ExecuteTransaction(ethTx, sender, app.GigaEvmKeeper.GetBaseFee(ctx), &gp)
execResult, execErr := gigaExecutor.ExecuteTransaction(ethTx, sender, cache.baseFee, &gp)
if execErr != nil {
return &abci.ExecTxResult{
Code: 1,
Expand Down Expand Up @@ -1888,49 +1933,52 @@ func (app *App) executeEVMTxWithGigaExecutor(ctx sdk.Context, msg *evmtypes.MsgE
}

// gigaDeliverTx is the OCC-compatible deliverTx function for the giga executor.
// The ctx.MultiStore() is already wrapped with VersionIndexedStore by the scheduler.
func (app *App) gigaDeliverTx(ctx sdk.Context, req abci.RequestDeliverTxV2, tx sdk.Tx, checksum [32]byte) abci.ResponseDeliverTx {
defer func() {
if r := recover(); r != nil {
// OCC abort panics are expected - the scheduler uses them to detect conflicts
// and reschedule transactions. Don't log these as errors.
if _, isOCCAbort := r.(occ.Abort); !isOCCAbort {
ctx.Logger().Error("benchmark panic in gigaDeliverTx", "panic", r, "stack", string(debug.Stack()))
// makeGigaDeliverTx returns an OCC-compatible deliverTx callback that captures the given
// block cache, avoiding mutable state on App for cache lifecycle management.
func (app *App) makeGigaDeliverTx(cache *gigaBlockCache) func(sdk.Context, abci.RequestDeliverTxV2, sdk.Tx, [32]byte) abci.ResponseDeliverTx {
return func(ctx sdk.Context, req abci.RequestDeliverTxV2, tx sdk.Tx, checksum [32]byte) abci.ResponseDeliverTx {
defer func() {
if r := recover(); r != nil {
// OCC abort panics are expected - the scheduler uses them to detect conflicts
// and reschedule transactions. Don't log these as errors.
if _, isOCCAbort := r.(occ.Abort); !isOCCAbort {
ctx.Logger().Error("benchmark panic in gigaDeliverTx", "panic", r, "stack", string(debug.Stack()))
}
}
}
}()
}()

evmMsg := app.GetEVMMsg(tx)
if evmMsg == nil {
return abci.ResponseDeliverTx{Code: 1, Log: "not an EVM transaction"}
}
evmMsg := app.GetEVMMsg(tx)
if evmMsg == nil {
return abci.ResponseDeliverTx{Code: 1, Log: "not an EVM transaction"}
}

result, err := app.executeEVMTxWithGigaExecutor(ctx, evmMsg)
if err != nil {
// Check if this is a fail-fast error (Cosmos precompile interop detected)
if gigautils.ShouldExecutionAbort(err) {
// Return a sentinel response so the caller can fall back to v2.
return abci.ResponseDeliverTx{
Code: gigautils.GigaAbortCode,
Codespace: gigautils.GigaAbortCodespace,
Info: gigautils.GigaAbortInfo,
Log: "giga executor abort: fall back to v2",
result, err := app.executeEVMTxWithGigaExecutor(ctx, evmMsg, cache)
if err != nil {
// Check if this is a fail-fast error (Cosmos precompile interop detected)
if gigautils.ShouldExecutionAbort(err) {
// Return a sentinel response so the caller can fall back to v2.
return abci.ResponseDeliverTx{
Code: gigautils.GigaAbortCode,
Codespace: gigautils.GigaAbortCodespace,
Info: gigautils.GigaAbortInfo,
Log: "giga executor abort: fall back to v2",
}
}
}

return abci.ResponseDeliverTx{Code: 1, Log: fmt.Sprintf("giga executor error: %v", err)}
}
return abci.ResponseDeliverTx{Code: 1, Log: fmt.Sprintf("giga executor error: %v", err)}
}

return abci.ResponseDeliverTx{
Code: result.Code,
Data: result.Data,
Log: result.Log,
Info: result.Info,
GasWanted: result.GasWanted,
GasUsed: result.GasUsed,
Events: result.Events,
Codespace: result.Codespace,
EvmTxInfo: result.EvmTxInfo,
return abci.ResponseDeliverTx{
Code: result.Code,
Data: result.Data,
Log: result.Log,
Info: result.Info,
GasWanted: result.GasWanted,
GasUsed: result.GasUsed,
Events: result.Events,
Codespace: result.Codespace,
EvmTxInfo: result.EvmTxInfo,
}
}
}

Expand Down
6 changes: 5 additions & 1 deletion giga/deps/xevm/state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,11 @@ func (s *DBImpl) HasSelfDestructed(acc common.Address) bool {
}

func (s *DBImpl) Snapshot() int {
newCtx := s.ctx.WithMultiStore(s.ctx.MultiStore().CacheMultiStore()).WithEventManager(sdk.NewEventManager())
em := sdk.NewEventManager()
if s.cosmosEventsSuppressed {
em = sdk.NewSuppressedEventManager()
}
newCtx := s.ctx.WithMultiStore(s.ctx.MultiStore().CacheMultiStore()).WithEventManager(em)
s.snapshottedCtxs = append(s.snapshottedCtxs, s.ctx)
s.ctx = newCtx
version := len(s.snapshottedCtxs) - 1
Expand Down
20 changes: 16 additions & 4 deletions giga/deps/xevm/state/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ type DBImpl struct {
// for cases like bank.send_native, we want to suppress transfer events
eventsSuppressed bool

// when true, all Cosmos-level events (coin_spent, coin_received, etc.)
// are discarded. EVM logs are unaffected. Use this on the giga executor
// path where Cosmos events are constructed but never consumed.
cosmosEventsSuppressed bool

logger *tracing.Hooks
}

Expand Down Expand Up @@ -66,6 +71,10 @@ func (s *DBImpl) EnableEvents() {
s.eventsSuppressed = false
}

func (s *DBImpl) SuppressCosmosEvents() {
s.cosmosEventsSuppressed = true
}

func (s *DBImpl) SetLogger(logger *tracing.Hooks) {
s.logger = logger
}
Expand Down Expand Up @@ -112,11 +121,14 @@ func (s *DBImpl) Finalize() (surplus sdk.Int, err error) {
s.clearAccountStateIfDestructed(s.tempState)

s.flushCtxs()
// write all events in order
for i := 1; i < len(s.snapshottedCtxs); i++ {
s.flushEvents(s.snapshottedCtxs[i])
// write all events in order (skip when cosmos events are suppressed —
// the EventManagers are no-ops so there is nothing to consolidate)
if !s.cosmosEventsSuppressed {
for i := 1; i < len(s.snapshottedCtxs); i++ {
s.flushEvents(s.snapshottedCtxs[i])
}
s.flushEvents(s.ctx)
}
s.flushEvents(s.ctx)

surplus = s.tempState.surplus
return
Expand Down
17 changes: 16 additions & 1 deletion sei-cosmos/types/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ import (
type EventManager struct {
events Events

mtx sync.RWMutex
mtx sync.RWMutex
suppressed bool
}

// Common Event Types and Attributes
Expand All @@ -55,11 +56,22 @@ func NewEventManager() *EventManager {
return &em
}

// NewSuppressedEventManager returns an EventManager that silently discards all
// emitted events. Use this on hot paths where events are constructed but never
// consumed (e.g. the giga executor) to avoid mutex contention, bech32 encoding
// in event attributes, and allocation overhead.
func NewSuppressedEventManager() *EventManager {
return &EventManager{suppressed: true}
}

func (em *EventManager) Events() Events { return em.events }

// EmitEvent stores a single Event object.
// Deprecated: Use EmitTypedEvent
func (em *EventManager) EmitEvent(event Event) {
if em.suppressed {
return
}
em.mtx.Lock()
defer em.mtx.Unlock()
em.events = em.events.AppendEvent(event)
Expand All @@ -68,6 +80,9 @@ func (em *EventManager) EmitEvent(event Event) {
// EmitEvents stores a series of Event objects.
// Deprecated: Use EmitTypedEvents
func (em *EventManager) EmitEvents(events Events) {
if em.suppressed {
return
}
em.mtx.Lock()
defer em.mtx.Unlock()
em.events = em.events.AppendEvents(events)
Expand Down
Loading