diff --git a/app/app.go b/app/app.go index c7a8317ba2..b1ab76fd36 100644 --- a/app/app.go +++ b/app/app.go @@ -2565,7 +2565,7 @@ func (app *App) RegisterLocalServices(node client.LocalClient, txConfig client.T rpcCtxProvider := app.RPCContextProvider traceCtxProvider := app.SnapshotAwareRPCContextProvider() if app.evmRPCConfig.HTTPEnabled { - evmHTTPServer, err := evmrpc.NewEVMHTTPServer(app.evmRPCConfig, node, &app.EvmKeeper, app.BeginBlockKeepers, app.BaseApp, app.TracerAnteHandler, app.RPCContextProvider, txConfigProvider, DefaultNodeHome, app.GetStateStore(), nil, traceCtxProvider) + evmHTTPServer, err := evmrpc.NewEVMHTTPServer(app.evmRPCConfig, node, &app.EvmKeeper, app.BeginBlockKeepers, app.BaseApp, app.TracerAnteHandler, app.RPCContextProvider, txConfigProvider, DefaultNodeHome, app.GetStateStore(), traceCtxProvider) if err != nil { panic(err) } diff --git a/evmrpc/server.go b/evmrpc/server.go index 0a1a2c375a..f42c812bf5 100644 --- a/evmrpc/server.go +++ b/evmrpc/server.go @@ -1,11 +1,9 @@ package evmrpc import ( - "context" "strings" "sync" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/rpc" "github.com/sei-protocol/sei-chain/app/legacyabci" @@ -43,7 +41,6 @@ func NewEVMHTTPServer( txConfigProvider func(int64) client.TxConfig, homeDir string, stateStore types.StateStore, - isPanicOrSyntheticTxFunc func(ctx context.Context, hash common.Hash) (bool, error), // used in *ExcludeTraceFail endpoints traceCtxProviders ...TraceContextProvider, ) (EVMServer, error) { @@ -105,11 +102,7 @@ func NewEVMHTTPServer( TipFn: func() int64 { return ctxProvider(LatestCtxHeight).BlockHeight() }, }) } - if isPanicOrSyntheticTxFunc == nil { - isPanicOrSyntheticTxFunc = func(ctx context.Context, hash common.Hash) (bool, error) { - return debugAPI.isPanicOrSyntheticTx(ctx, hash) - } - } + isPanicOrSyntheticTxFunc := debugAPI.isPanicOrSyntheticTx seiLegacyAllowlist := BuildSeiLegacyEnabledSet(config.EnabledLegacySeiApis) seiTxAPI := NewSeiTransactionAPI(tmClient, k, ctxProvider, txConfigProvider, homeDir, ConnectionTypeHTTP, isPanicOrSyntheticTxFunc, watermarks, globalBlockCache, cacheCreationMutex) diff --git a/evmrpc/setup_test.go b/evmrpc/setup_test.go index c611f8945a..300bcca7be 100644 --- a/evmrpc/setup_test.go +++ b/evmrpc/setup_test.go @@ -599,7 +599,7 @@ func init() { goodConfig.MaxLogNoBlock = 10 goodConfig.EnabledLegacySeiApis = evmrpc.SeiLegacyAllGatedMethodNames() txConfigProvider := func(int64) client.TxConfig { return TxConfig } - HttpServer, err := evmrpc.NewEVMHTTPServer(goodConfig, &MockClient{}, EVMKeeper, testApp.BeginBlockKeepers, testApp.BaseApp, testApp.TracerAnteHandler, ctxProvider, txConfigProvider, "", nil, isPanicTxFunc) + HttpServer, err := evmrpc.NewEVMHTTPServer(goodConfig, &MockClient{}, EVMKeeper, testApp.BeginBlockKeepers, testApp.BaseApp, testApp.TracerAnteHandler, ctxProvider, txConfigProvider, "", nil) if err != nil { panic(err) } @@ -611,7 +611,7 @@ func init() { badConfig := evmrpcconfig.DefaultConfig badConfig.HTTPPort = TestBadPort badConfig.FilterTimeout = 500 * time.Millisecond - badHTTPServer, err := evmrpc.NewEVMHTTPServer(badConfig, &MockBadClient{}, EVMKeeper, testApp.BeginBlockKeepers, testApp.BaseApp, testApp.TracerAnteHandler, ctxProvider, txConfigProvider, "", nil, nil) + badHTTPServer, err := evmrpc.NewEVMHTTPServer(badConfig, &MockBadClient{}, EVMKeeper, testApp.BeginBlockKeepers, testApp.BaseApp, testApp.TracerAnteHandler, ctxProvider, txConfigProvider, "", nil) if err != nil { panic(err) } @@ -636,7 +636,6 @@ func init() { txConfigProvider, "", nil, - isPanicTxFunc, ) if err != nil { panic(err) @@ -661,7 +660,6 @@ func init() { txConfigProvider, "", nil, - isPanicTxFunc, ) if err != nil { panic(err) @@ -1296,8 +1294,3 @@ func TestEcho(t *testing.T) { require.Nil(t, err) require.Equal(t, "{\"jsonrpc\":\"2.0\",\"id\":\"test\",\"result\":\"something\"}\n", string(buf)) } - -func isPanicTxFunc(ctx context.Context, hash common.Hash) (bool, error) { - result := hash == common.HexToHash(TestPanicTxHash) || hash == common.HexToHash(TestSyntheticTxHash) - return result, nil -} diff --git a/evmrpc/tests/utils.go b/evmrpc/tests/utils.go index 2b6f82a590..530f62114a 100644 --- a/evmrpc/tests/utils.go +++ b/evmrpc/tests/utils.go @@ -162,9 +162,6 @@ func setupTestServer( func(int64) client.TxConfig { return a.GetTxConfig() }, "", nil, - func(ctx context.Context, hash common.Hash) (bool, error) { - return false, nil - }, ) if err != nil { panic(err) diff --git a/evmrpc/tracers.go b/evmrpc/tracers.go index 208509867a..bd2dabc056 100644 --- a/evmrpc/tracers.go +++ b/evmrpc/tracers.go @@ -484,11 +484,9 @@ func (api *DebugAPI) isPanicOrSyntheticTx(ctx context.Context, hash common.Hash) sdkctx := api.ctxProvider(LatestCtxHeight) receipt, rerr := api.keeper.GetReceipt(sdkctx, hash) if rerr != nil { - // No receipt: treat as panic/synthetic. Ante-rejected txs and unknown - // hashes both land here; either way, no trace to surface. - if api.isPanicCache != nil { - api.isPanicCache.Add(hash, true) - } + // No receipt: treat as panic/synthetic. Not cached — the receipt + // store can lag the RPC for a freshly committed tx, so this answer + // may flip to "include" once the write lands. return true, nil } diff --git a/evmrpc/tx_test.go b/evmrpc/tx_test.go index 0d0367bcc5..acd6902282 100644 --- a/evmrpc/tx_test.go +++ b/evmrpc/tx_test.go @@ -1,6 +1,7 @@ package evmrpc_test import ( + "crypto/rand" "encoding/json" "fmt" "io" @@ -157,6 +158,53 @@ func TestGetTransactionReceiptExcludeTraceFail(t *testing.T) { require.Nil(t, resObj["result"]) } +// The panic/synthetic decision for a missing receipt must not be cached. +// Receipt-store writes can lag the RPC for a freshly committed tx, so a hash +// that initially looks panic-like must flip to "include" once its receipt +// (Status=1) lands within the cache TTL. +func TestGetTransactionReceiptExcludeTraceFailLateReceipt(t *testing.T) { + // Fresh hash per invocation so the test stays correct under -count>1 + // (the receipt store and isPanicCache persist across iterations). + var hashBytes [32]byte + _, err := rand.Read(hashBytes[:]) + require.NoError(t, err) + hash := common.Hash(hashBytes) + + call := func() map[string]interface{} { + body := fmt.Sprintf("{\"jsonrpc\": \"2.0\",\"method\": \"sei_getTransactionReceiptExcludeTraceFail\",\"params\":[\"%s\"],\"id\":\"test\"}", hash.Hex()) + req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s:%d", TestAddr, TestPort), strings.NewReader(body)) + require.Nil(t, err) + req.Header.Set("Content-Type", "application/json") + res, err := http.DefaultClient.Do(req) + require.Nil(t, err) + resBody, err := io.ReadAll(res.Body) + require.Nil(t, err) + resObj := map[string]interface{}{} + require.Nil(t, json.Unmarshal(resBody, &resObj)) + return resObj + } + + // First call: no receipt → endpoint reports the tx as panic. + resObj := call() + errObj, ok := resObj["error"].(map[string]interface{}) + require.True(t, ok) + require.Equal(t, evmrpc.ErrPanicTx.Error(), errObj["message"]) + + // Receipt lands with Status=1. The next call must NOT still report the + // tx as panic — that would mean the prior "no receipt" answer was cached. + require.NoError(t, EVMKeeper.MockReceipt(Ctx, hash, &types.Receipt{ + BlockNumber: MockHeight8, + TransactionIndex: 0, + TxHashHex: hash.Hex(), + Status: 1, + EffectiveGasPrice: 1000000, + })) + resObj = call() + if errObj, ok := resObj["error"].(map[string]interface{}); ok { + require.NotEqual(t, evmrpc.ErrPanicTx.Error(), errObj["message"], "cache was poisoned by the prior missing-receipt lookup") + } +} + func TestCumulativeGasUsedPopulation(t *testing.T) { blockHeight := int64(1000) Ctx = Ctx.WithBlockHeight(blockHeight)