Skip to content

Commit fce7d6a

Browse files
pdrobnjakclaude
andcommitted
perf: cache block-level constants in executeEVMTxWithGigaExecutor
Cache ChainID, BlockContext, ChainConfig, and BaseFee once per block instead of recomputing them for every transaction. These values are identical for all txs in a block but were previously causing redundant store reads, Keccak256 hashing, and big.Int allocations on every call. The cache is constructed as a local variable and passed as a parameter to executeEVMTxWithGigaExecutor, avoiding any mutable state on App. Both giga execution paths (synchronous and OCC) are covered. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 8fc1d5b commit fce7d6a

File tree

1 file changed

+99
-59
lines changed

1 file changed

+99
-59
lines changed

app/app.go

Lines changed: 99 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"fmt"
1010
"io"
1111
"math"
12+
"math/big"
1213
"net/http"
1314
"os"
1415
"path/filepath"
@@ -92,6 +93,7 @@ import (
9293
ethtypes "github.com/ethereum/go-ethereum/core/types"
9394
"github.com/ethereum/go-ethereum/core/vm"
9495
"github.com/ethereum/go-ethereum/ethclient"
96+
ethparams "github.com/ethereum/go-ethereum/params"
9597
ethrpc "github.com/ethereum/go-ethereum/rpc"
9698
"github.com/sei-protocol/sei-chain/giga/deps/tasks"
9799

@@ -318,6 +320,33 @@ func GetWasmEnabledProposals() []wasm.ProposalType {
318320
// App extends an ABCI application, but with most of its parameters exported.
319321
// They are exported for convenience in creating helper functions, as object
320322
// capabilities aren't needed for testing.
323+
// gigaBlockCache holds block-constant values that are identical for all txs in a block.
324+
// Populated once before block execution, read-only during parallel execution, cleared after.
325+
type gigaBlockCache struct {
326+
chainID *big.Int
327+
blockCtx vm.BlockContext
328+
chainConfig *ethparams.ChainConfig
329+
baseFee *big.Int
330+
}
331+
332+
func newGigaBlockCache(ctx sdk.Context, keeper *gigaevmkeeper.Keeper) (*gigaBlockCache, error) {
333+
chainID := keeper.ChainID(ctx)
334+
gp := keeper.GetGasPool()
335+
blockCtx, err := keeper.GetVMBlockContext(ctx, gp)
336+
if err != nil {
337+
return nil, err
338+
}
339+
sstore := keeper.GetParams(ctx).SeiSstoreSetGasEip2200
340+
chainConfig := evmtypes.DefaultChainConfig().EthereumConfigWithSstore(chainID, &sstore)
341+
baseFee := keeper.GetBaseFee(ctx)
342+
return &gigaBlockCache{
343+
chainID: chainID,
344+
blockCtx: *blockCtx,
345+
chainConfig: chainConfig,
346+
baseFee: baseFee,
347+
}, nil
348+
}
349+
321350
type App struct {
322351
*baseapp.BaseApp
323352

@@ -1373,6 +1402,14 @@ func (app *App) ProcessTxsSynchronousGiga(ctx sdk.Context, txs [][]byte, typedTx
13731402
ms := ctx.MultiStore().CacheMultiStore()
13741403
defer ms.Write()
13751404
ctx = ctx.WithMultiStore(ms)
1405+
1406+
// Cache block-level constants (identical for all txs in this block).
1407+
cache, cacheErr := newGigaBlockCache(ctx, &app.GigaEvmKeeper)
1408+
if cacheErr != nil {
1409+
ctx.Logger().Error("failed to build giga block cache", "error", cacheErr, "height", ctx.BlockHeight())
1410+
return nil
1411+
}
1412+
13761413
txResults := make([]*abci.ExecTxResult, len(txs))
13771414
for i, tx := range txs {
13781415
ctx = ctx.WithTxIndex(absoluteTxIndices[i])
@@ -1386,7 +1423,7 @@ func (app *App) ProcessTxsSynchronousGiga(ctx sdk.Context, txs [][]byte, typedTx
13861423
}
13871424

13881425
// Execute EVM transaction through giga executor
1389-
result, execErr := app.executeEVMTxWithGigaExecutor(ctx, evmMsg)
1426+
result, execErr := app.executeEVMTxWithGigaExecutor(ctx, evmMsg, cache)
13901427
if execErr != nil {
13911428
// Check if this is a fail-fast error (Cosmos precompile interop detected)
13921429
if gigautils.ShouldExecutionAbort(execErr) {
@@ -1544,15 +1581,24 @@ func (app *App) ProcessTXsWithOCCGiga(ctx sdk.Context, txs [][]byte, typedTxs []
15441581
}
15451582
}
15461583

1547-
// Create OCC scheduler with giga executor deliverTx.
1584+
// Run EVM txs against a cache so we can discard all changes on fallback.
1585+
evmCtx, evmCache := app.CacheContext(ctx)
1586+
1587+
// Cache block-level constants (identical for all txs in this block).
1588+
// Must use evmCtx (not ctx) because giga KV stores are registered in CacheContext.
1589+
cache, cacheErr := newGigaBlockCache(evmCtx, &app.GigaEvmKeeper)
1590+
if cacheErr != nil {
1591+
ctx.Logger().Error("failed to build giga block cache", "error", cacheErr, "height", ctx.BlockHeight())
1592+
return nil, ctx
1593+
}
1594+
1595+
// Create OCC scheduler with giga executor deliverTx capturing the cache.
15481596
evmScheduler := tasks.NewScheduler(
15491597
app.ConcurrencyWorkers(),
15501598
app.TracingInfo,
1551-
app.gigaDeliverTx,
1599+
app.makeGigaDeliverTx(cache),
15521600
)
15531601

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

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

1723-
chainID := app.GigaEvmKeeper.ChainID(ctx)
1769+
chainID := cache.chainID
17241770

17251771
// Recover sender using the same logic as preprocess.go (version-based signer selection)
17261772
sender, seiAddr, pubkey, recoverErr := evmante.RecoverSenderFromEthTx(ctx, ethTx, chainID)
@@ -1749,27 +1795,18 @@ func (app *App) executeEVMTxWithGigaExecutor(ctx sdk.Context, msg *evmtypes.MsgE
17491795
stateDB := gigaevmstate.NewDBImpl(ctx, &app.GigaEvmKeeper, false)
17501796
defer stateDB.Cleanup()
17511797

1752-
// Get gas pool
1798+
// Get gas pool (mutated per tx, cannot be cached)
17531799
gp := app.GigaEvmKeeper.GetGasPool()
17541800

1755-
// Get block context
1756-
blockCtx, blockCtxErr := app.GigaEvmKeeper.GetVMBlockContext(ctx, gp)
1757-
if blockCtxErr != nil {
1758-
return &abci.ExecTxResult{
1759-
Code: 1,
1760-
Log: fmt.Sprintf("failed to get block context: %v", blockCtxErr),
1761-
}, nil
1762-
}
1763-
1764-
// Get chain config
1765-
sstore := app.GigaEvmKeeper.GetParams(ctx).SeiSstoreSetGasEip2200
1766-
cfg := evmtypes.DefaultChainConfig().EthereumConfigWithSstore(app.GigaEvmKeeper.ChainID(ctx), &sstore)
1801+
// Use cached block-level constants
1802+
blockCtx := cache.blockCtx
1803+
cfg := cache.chainConfig
17671804

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

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

18901927
// gigaDeliverTx is the OCC-compatible deliverTx function for the giga executor.
1891-
// The ctx.MultiStore() is already wrapped with VersionIndexedStore by the scheduler.
1892-
func (app *App) gigaDeliverTx(ctx sdk.Context, req abci.RequestDeliverTxV2, tx sdk.Tx, checksum [32]byte) abci.ResponseDeliverTx {
1893-
defer func() {
1894-
if r := recover(); r != nil {
1895-
// OCC abort panics are expected - the scheduler uses them to detect conflicts
1896-
// and reschedule transactions. Don't log these as errors.
1897-
if _, isOCCAbort := r.(occ.Abort); !isOCCAbort {
1898-
ctx.Logger().Error("benchmark panic in gigaDeliverTx", "panic", r, "stack", string(debug.Stack()))
1928+
// makeGigaDeliverTx returns an OCC-compatible deliverTx callback that captures the given
1929+
// block cache, avoiding mutable state on App for cache lifecycle management.
1930+
func (app *App) makeGigaDeliverTx(cache *gigaBlockCache) func(sdk.Context, abci.RequestDeliverTxV2, sdk.Tx, [32]byte) abci.ResponseDeliverTx {
1931+
return func(ctx sdk.Context, req abci.RequestDeliverTxV2, tx sdk.Tx, checksum [32]byte) abci.ResponseDeliverTx {
1932+
defer func() {
1933+
if r := recover(); r != nil {
1934+
// OCC abort panics are expected - the scheduler uses them to detect conflicts
1935+
// and reschedule transactions. Don't log these as errors.
1936+
if _, isOCCAbort := r.(occ.Abort); !isOCCAbort {
1937+
ctx.Logger().Error("benchmark panic in gigaDeliverTx", "panic", r, "stack", string(debug.Stack()))
1938+
}
18991939
}
1900-
}
1901-
}()
1940+
}()
19021941

1903-
evmMsg := app.GetEVMMsg(tx)
1904-
if evmMsg == nil {
1905-
return abci.ResponseDeliverTx{Code: 1, Log: "not an EVM transaction"}
1906-
}
1942+
evmMsg := app.GetEVMMsg(tx)
1943+
if evmMsg == nil {
1944+
return abci.ResponseDeliverTx{Code: 1, Log: "not an EVM transaction"}
1945+
}
19071946

1908-
result, err := app.executeEVMTxWithGigaExecutor(ctx, evmMsg)
1909-
if err != nil {
1910-
// Check if this is a fail-fast error (Cosmos precompile interop detected)
1911-
if gigautils.ShouldExecutionAbort(err) {
1912-
// Return a sentinel response so the caller can fall back to v2.
1913-
return abci.ResponseDeliverTx{
1914-
Code: gigautils.GigaAbortCode,
1915-
Codespace: gigautils.GigaAbortCodespace,
1916-
Info: gigautils.GigaAbortInfo,
1917-
Log: "giga executor abort: fall back to v2",
1947+
result, err := app.executeEVMTxWithGigaExecutor(ctx, evmMsg, cache)
1948+
if err != nil {
1949+
// Check if this is a fail-fast error (Cosmos precompile interop detected)
1950+
if gigautils.ShouldExecutionAbort(err) {
1951+
// Return a sentinel response so the caller can fall back to v2.
1952+
return abci.ResponseDeliverTx{
1953+
Code: gigautils.GigaAbortCode,
1954+
Codespace: gigautils.GigaAbortCodespace,
1955+
Info: gigautils.GigaAbortInfo,
1956+
Log: "giga executor abort: fall back to v2",
1957+
}
19181958
}
1919-
}
19201959

1921-
return abci.ResponseDeliverTx{Code: 1, Log: fmt.Sprintf("giga executor error: %v", err)}
1922-
}
1960+
return abci.ResponseDeliverTx{Code: 1, Log: fmt.Sprintf("giga executor error: %v", err)}
1961+
}
19231962

1924-
return abci.ResponseDeliverTx{
1925-
Code: result.Code,
1926-
Data: result.Data,
1927-
Log: result.Log,
1928-
Info: result.Info,
1929-
GasWanted: result.GasWanted,
1930-
GasUsed: result.GasUsed,
1931-
Events: result.Events,
1932-
Codespace: result.Codespace,
1933-
EvmTxInfo: result.EvmTxInfo,
1963+
return abci.ResponseDeliverTx{
1964+
Code: result.Code,
1965+
Data: result.Data,
1966+
Log: result.Log,
1967+
Info: result.Info,
1968+
GasWanted: result.GasWanted,
1969+
GasUsed: result.GasUsed,
1970+
Events: result.Events,
1971+
Codespace: result.Codespace,
1972+
EvmTxInfo: result.EvmTxInfo,
1973+
}
19341974
}
19351975
}
19361976

0 commit comments

Comments
 (0)