diff --git a/app/app.go b/app/app.go index e60522181c..a4c0f0f800 100644 --- a/app/app.go +++ b/app/app.go @@ -9,6 +9,7 @@ import ( "fmt" "io" "math" + "math/big" "net/http" "os" "path/filepath" @@ -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" @@ -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 @@ -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]) @@ -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) { @@ -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 @@ -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) @@ -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, @@ -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, + } } } diff --git a/giga/deps/xevm/state/state.go b/giga/deps/xevm/state/state.go index e57e962221..e0b1048222 100644 --- a/giga/deps/xevm/state/state.go +++ b/giga/deps/xevm/state/state.go @@ -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 diff --git a/giga/deps/xevm/state/statedb.go b/giga/deps/xevm/state/statedb.go index eb95116d05..d2c78c089f 100644 --- a/giga/deps/xevm/state/statedb.go +++ b/giga/deps/xevm/state/statedb.go @@ -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 } @@ -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 } @@ -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 diff --git a/sei-cosmos/types/events.go b/sei-cosmos/types/events.go index 2317720a9c..b8feedf55a 100644 --- a/sei-cosmos/types/events.go +++ b/sei-cosmos/types/events.go @@ -28,7 +28,8 @@ import ( type EventManager struct { events Events - mtx sync.RWMutex + mtx sync.RWMutex + suppressed bool } // Common Event Types and Attributes @@ -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) @@ -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)