diff --git a/go.mod b/go.mod index 71ab89c6bd..4972198e46 100644 --- a/go.mod +++ b/go.mod @@ -275,7 +275,7 @@ replace ( cosmossdk.io/store => github.com/InjectiveLabs/cosmos-sdk/store v0.0.0-20240904140803-b4127ecb5410 cosmossdk.io/x/tx => github.com/InjectiveLabs/cosmos-sdk/x/tx v0.0.0-20240904140803-b4127ecb5410 - github.com/cometbft/cometbft => github.com/streamingfast/cometbft v0.38.11-inj-5 + github.com/cometbft/cometbft => github.com/Injectivelabs/cometbft v0.38.11-inj-5 github.com/cosmos/cosmos-sdk => github.com/InjectiveLabs/cosmos-sdk v0.50.10-0.20241010141128-de2b5199b23e github.com/ethereum/go-ethereum => github.com/InjectiveLabs/go-ethereum v1.9.22-0.20240923100242-5e28e23d353e nhooyr.io/websocket => github.com/coder/websocket v1.8.10 // replaced as instructed here:https://coder.com/blog/websocket diff --git a/go.sum b/go.sum index e6f406d916..9e2e7ceb91 100644 --- a/go.sum +++ b/go.sum @@ -250,6 +250,8 @@ github.com/InjectiveLabs/go-ethereum v1.9.22-0.20240923100242-5e28e23d353e h1:yX github.com/InjectiveLabs/go-ethereum v1.9.22-0.20240923100242-5e28e23d353e/go.mod h1:PHp+ZPjvQGBT2/iU18sro79JX+Ffy8IIqU7lnuVX3oE= github.com/InjectiveLabs/metrics v0.0.10 h1:BoOwXnCtRRIPmq06jcI20pXZYE758eusaCI5jDOoN4U= github.com/InjectiveLabs/metrics v0.0.10/go.mod h1:eYu++0DVUjk/jjV9WgvCo8gQU+16Yoyhp1iu+ghKNME= +github.com/Injectivelabs/cometbft v0.38.11-inj-5 h1:VrfeIvIEhdHwF7KfMZ1KmQRiT0TseSVRmx0NDoQc0Dw= +github.com/Injectivelabs/cometbft v0.38.11-inj-5/go.mod h1:WreBJKC7CtZ9cNsb5p6uNpfCBwNWDu+risVb58GTLfY= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= @@ -1119,8 +1121,6 @@ github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9 github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= -github.com/streamingfast/cometbft v0.38.11-inj-5 h1:6nEaRsiBtihMb3QUhcgATQzCG7V+DKxFGH707hA1Ju8= -github.com/streamingfast/cometbft v0.38.11-inj-5/go.mod h1:WreBJKC7CtZ9cNsb5p6uNpfCBwNWDu+risVb58GTLfY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= diff --git a/rpc/backend/tx_info.go b/rpc/backend/tx_info.go index 2e626e2565..814900686c 100644 --- a/rpc/backend/tx_info.go +++ b/rpc/backend/tx_info.go @@ -71,7 +71,8 @@ func (b *Backend) GetTransactionByHash(txHash common.Hash) (*rpctypes.RPCTransac } } } - // if we still unable to find the eth tx index, return error, shouldn't happen. + + // if we are still unable to find the eth tx index, return error, shouldn't happen. if res.EthTxIndex == -1 { return nil, errors.New("can't find index of ethereum tx") } diff --git a/x/evm/keeper/abci.go b/x/evm/keeper/abci.go index da6774aab1..4b046f7ccb 100644 --- a/x/evm/keeper/abci.go +++ b/x/evm/keeper/abci.go @@ -17,6 +17,7 @@ package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" + cosmostracing "github.com/evmos/ethermint/x/evm/tracing" ) // BeginBlock sets the sdk Context and EIP155 chain id to the Keeper. @@ -29,8 +30,9 @@ func (k *Keeper) BeginBlock(ctx sdk.Context) error { return err } - if k.evmTracer != nil && k.evmTracer.OnCosmosBlockStart != nil { - k.evmTracer.OnCosmosBlockStart( + // In the case of BeginBlock hook, we can extract the tracer from the context + if tracer := cosmostracing.GetCtxBlockchainTracer(ctx); tracer != nil && tracer.OnCosmosBlockStart != nil { + tracer.OnCosmosBlockStart( ToCosmosStartBlockEvent( k, ctx, @@ -50,10 +52,9 @@ func (k *Keeper) EndBlock(ctx sdk.Context) error { k.CollectTxBloom(ctx) k.RemoveParamsCache(ctx) - if k.evmTracer != nil && k.evmTracer.OnCosmosBlockEnd != nil { - defer func() { - k.evmTracer.OnCosmosBlockEnd(ToCosmosEndBlockEvent(k, ctx), nil) - }() + // In the case of EndBlock hook, we can extract the tracer from the context + if tracer := cosmostracing.GetCtxBlockchainTracer(ctx); tracer != nil && tracer.OnCosmosBlockEnd != nil { + tracer.OnCosmosBlockEnd(ToCosmosEndBlockEvent(k, ctx), nil) } return nil diff --git a/x/evm/keeper/config.go b/x/evm/keeper/config.go index 61d9abff1d..4226ba73fe 100644 --- a/x/evm/keeper/config.go +++ b/x/evm/keeper/config.go @@ -16,7 +16,7 @@ package keeper import ( - "github.com/ethereum/go-ethereum/eth/tracers" + cosmostracing "github.com/evmos/ethermint/x/evm/tracing" "math/big" errorsmod "cosmossdk.io/errors" @@ -53,7 +53,7 @@ type EVMBlockConfig struct { type EVMConfig struct { *EVMBlockConfig TxConfig statedb.TxConfig - Tracer *tracers.Tracer + Tracer *cosmostracing.Hooks DebugTrace bool Overrides *rpctypes.StateOverride BlockOverrides *rpctypes.BlockOverrides @@ -131,13 +131,19 @@ func (k *Keeper) EVMConfig(ctx sdk.Context, chainID *big.Int, txHash common.Hash TxConfig: txConfig, } + return cfg, nil +} + +// EVMConfigWithTracer creates the EVMConfig based on current state and returns the evmTracer +func (k *Keeper) EVMConfigWithTracer(ctx sdk.Context, chainID *big.Int, txHash common.Hash) (*EVMConfig, error) { + cfg, err := k.EVMConfig(ctx, chainID, txHash) + + if err != nil { + return nil, err + } + if k.evmTracer != nil { - t := &tracers.Tracer{ - Hooks: k.evmTracer.Hooks, - GetResult: nil, - Stop: nil, - } - cfg.Tracer = t + cfg.Tracer = k.evmTracer } return cfg, nil diff --git a/x/evm/keeper/grpc_query.go b/x/evm/keeper/grpc_query.go index b4eacd911f..5eee851acd 100644 --- a/x/evm/keeper/grpc_query.go +++ b/x/evm/keeper/grpc_query.go @@ -20,6 +20,7 @@ import ( "encoding/json" "errors" "fmt" + cosmostracing "github.com/evmos/ethermint/x/evm/tracing" "math/big" "time" @@ -719,7 +720,9 @@ func (k *Keeper) prepareTrace( cfg.BlockOverrides = &blockOverrides } - cfg.Tracer = tracer + cfg.Tracer = &cosmostracing.Hooks{ + Hooks: tracer.Hooks, + } cfg.DebugTrace = true res, err := k.ApplyMessageWithConfig(ctx, msg, cfg, commitMessage) if err != nil { diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go index 4c44d3d60e..bae0832897 100644 --- a/x/evm/keeper/keeper.go +++ b/x/evm/keeper/keeper.go @@ -139,9 +139,9 @@ func (k Keeper) ChainID() *big.Int { return k.eip155ChainID } -func (k *Keeper) InitChainer() { - if k.evmTracer != nil && k.evmTracer.OnBlockchainInit != nil { - k.evmTracer.OnBlockchainInit(evmtypes.DefaultChainConfig().EthereumConfig(k.ChainID())) +func (k *Keeper) InitChainer(ctx sdk.Context) { + if tracer := cosmostracing.GetCtxBlockchainTracer(ctx); tracer != nil && tracer.OnBlockchainInit != nil { + tracer.OnBlockchainInit(evmtypes.DefaultChainConfig().EthereumConfig(k.ChainID())) } } diff --git a/x/evm/keeper/state_transition.go b/x/evm/keeper/state_transition.go index 8640687f61..dae7dd2bc4 100644 --- a/x/evm/keeper/state_transition.go +++ b/x/evm/keeper/state_transition.go @@ -18,6 +18,7 @@ package keeper import ( "bytes" "fmt" + cosmostracing "github.com/evmos/ethermint/x/evm/tracing" "math/big" "sort" @@ -33,7 +34,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/tracing" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" @@ -41,6 +41,8 @@ import ( "github.com/ethereum/go-ethereum/params" ) +var configOverridesErrDesc = "failed to apply state override" + // NewEVM generates a go-ethereum VM from the provided Message fields and the chain parameters // (ChainConfig and module Params). It additionally sets the validator operator address as the // coinbase address to make it available for the COINBASE opcode, even though there is no @@ -73,13 +75,8 @@ func (k *Keeper) NewEVM( txCtx := core.NewEVMTxContext(msg) // Set Config Tracer if it was not already initialized - if k.evmTracer != nil { - t := &tracers.Tracer{ - Hooks: k.evmTracer.Hooks, - GetResult: nil, - Stop: nil, - } - cfg.Tracer = t + if tracer := cosmostracing.GetCtxBlockchainTracer(ctx); tracer != nil { + cfg.Tracer = tracer } vmConfig := k.VMConfig(ctx, cfg) @@ -159,7 +156,7 @@ func (k Keeper) GetHashFn(ctx sdk.Context) vm.GetHashFunc { } } -// ApplyTransaction runs and attempts to perform a state transition with the given transaction (i.e Message), that will +// ApplyTransaction runs and attempts to perform a state transition with the given transaction (i.e. Message), that will // only be persisted (committed) to the underlying KVStore if the transaction does not fail. // // # Gas tracking @@ -178,7 +175,7 @@ func (k Keeper) GetHashFn(ctx sdk.Context) vm.GetHashFunc { // For relevant discussion see: https://github.com/cosmos/cosmos-sdk/discussions/9072 func (k *Keeper) ApplyTransaction(ctx sdk.Context, msgEth *types.MsgEthereumTx) (*types.MsgEthereumTxResponse, error) { ethTx := msgEth.AsTransaction() - cfg, err := k.EVMConfig(ctx, k.eip155ChainID, ethTx.Hash()) + cfg, err := k.EVMConfigWithTracer(ctx, k.eip155ChainID, ethTx.Hash()) if err != nil { return nil, errorsmod.Wrap(err, "failed to load evm config") } @@ -196,9 +193,9 @@ func (k *Keeper) ApplyTransaction(ctx sdk.Context, msgEth *types.MsgEthereumTx) } // pass true to commit the StateDB - res, err := k.ApplyMessageWithConfig(tmpCtx, msg, cfg, true) - if err != nil { - return nil, errorsmod.Wrap(err, "failed to apply ethereum core message") + res, applyMessageErr := k.ApplyMessageWithConfig(tmpCtx, msg, cfg, true) + if applyMessageErr != nil { + return nil, errorsmod.Wrap(applyMessageErr, "failed to apply ethereum core message") } logs := types.LogsToEthereum(res.Logs) @@ -254,6 +251,19 @@ func (k *Keeper) ApplyTransaction(ctx sdk.Context, msgEth *types.MsgEthereumTx) // reset the gas meter for current cosmos transaction k.ResetGasMeterAndConsumeGas(ctx, totalGasUsed) + + defer func() { + // These errors are not vm related, so they should not be passed to the vm tracer + if !errorsmod.IsOf(applyMessageErr, types.ErrCreateDisabled, types.ErrCallDisabled) && (applyMessageErr != nil && applyMessageErr.Error() != configOverridesErrDesc) { + if cfg.Tracer != nil && cfg.Tracer.OnTxEnd != nil { + cfg.Tracer.OnTxEnd( + receipt, + applyMessageErr, + ) + } + } + }() + return res, nil } @@ -337,69 +347,33 @@ func (k *Keeper) ApplyMessageWithConfig( var evm *vm.EVM if cfg.Overrides != nil { if err := cfg.Overrides.Apply(stateDB); err != nil { - return nil, errorsmod.Wrap(err, "failed to apply state override") + return nil, errorsmod.Wrap(err, configOverridesErrDesc) } } + evm = k.NewEVM(ctx, msg, cfg, stateDB) leftoverGas := msg.GasLimit sender := vm.AccountRef(msg.From) - // Allow the tracer captures the tx level events, mainly the gas consumption. - vmCfg := evm.Config + tx := ethtypes.NewTx(ðtypes.LegacyTx{ + Nonce: msg.Nonce, + Gas: msg.GasLimit, + GasPrice: msg.GasPrice, + To: msg.To, + Value: msg.Value, + Data: msg.Data, + }) - if vmCfg.Tracer != nil { - stateDB.SetTracer(k.evmTracer) - vmCfg.Tracer.OnTxStart( + if cfg.Tracer != nil && cfg.Tracer.OnCosmosTxStart != nil { + stateDB.SetTracer(cfg.Tracer) + cfg.Tracer.OnCosmosTxStart( evm.GetVMContext(), - ethtypes.NewTx(ðtypes.LegacyTx{ - Nonce: msg.Nonce, - Gas: msg.GasLimit, - GasPrice: msg.GasPrice, - To: msg.To, - Value: msg.Value, - Data: msg.Data, - }), + tx, + cfg.TxConfig.TxHash, msg.From, ) - - //if cfg.DebugTrace { - // // msg.GasPrice should have been set to effective gas price - // senderAddr := sender.Address() - // stateDB.SubBalance( - // sender.Address(), - // uint256.MustFromBig(new(big.Int).Mul(msg.GasPrice, new(big.Int).SetUint64(msg.GasLimit))), - // tracing.BalanceChangeUnspecified, - // ) - // stateDB.SetNonce(senderAddr, stateDB.GetNonce(senderAddr)+1) - //} - - defer func() { - //if cfg.DebugTrace { - // stateDB.AddBalance( - // sender.Address(), - // uint256.MustFromBig(new(big.Int).Mul(msg.GasPrice, new(big.Int).SetUint64(leftoverGas))), - // tracing.BalanceChangeUnspecified, - // ) - //} - - traceErr := err - if vmErr != nil { - traceErr = vmErr - } - - vmCfg.Tracer.OnTxEnd( - ðtypes.Receipt{ - GasUsed: gasUsed, - }, - traceErr, - ) - }() } - //todo: on each of the exit conditions before the calls to if contractCreation { - // and manually catch them in the defer.vmcfg.tracer.ontxend - // also, I think that we have to manually craft a Call/Create? - rules := cfg.Rules contractCreation := msg.To == nil intrinsicGas, err := k.GetEthIntrinsicGas(msg, rules, contractCreation) @@ -428,6 +402,7 @@ func (k *Keeper) ApplyMessageWithConfig( stateDB.Prepare(rules, msg.From, cfg.CoinBase, msg.To, vm.DefaultActivePrecompiles(rules), msg.AccessList) if contractCreation { + // FIXME: also why do we want to set the nonce in the statedb twice here? // take over the nonce management from evm: // - reset sender's nonce to msg.Nonce() before calling evm. // - increase sender's nonce by one no matter the result. @@ -436,6 +411,12 @@ func (k *Keeper) ApplyMessageWithConfig( stateDB.SetNonce(sender.Address(), msg.Nonce+1) } else { ret, leftoverGas, vmErr = evm.Call(sender, *msg.To, msg.Data, leftoverGas, uint256.MustFromBig(msg.Value)) + + // if they do not want to make the nonce set to the statedb or do we want to manually call the onNonceChange hook + //stateDB.SetNonce(sender.Address(), msg.Nonce+1) + if cfg.Tracer != nil && cfg.Tracer.OnNonceChange != nil { + cfg.Tracer.OnNonceChange(sender.Address(), msg.Nonce, msg.Nonce+1) + } } refundQuotient := params.RefundQuotient @@ -486,11 +467,6 @@ func (k *Keeper) ApplyMessageWithConfig( // reset leftoverGas, to be used by the tracer leftoverGas = msg.GasLimit - gasUsed - if k.evmTracer != nil && k.evmTracer.OnGasChange != nil { - //TODO: add test to make sure the onGasChange is not doubly called - k.evmTracer.OnGasChange(msg.GasLimit, leftoverGas, tracing.GasChangeTxLeftOverReturned) - } - return &types.MsgEthereumTxResponse{ GasUsed: gasUsed, VmError: vmError, diff --git a/x/evm/keeper/state_transition_test.go b/x/evm/keeper/state_transition_test.go index 8a05eccb4a..d0d9f154e1 100644 --- a/x/evm/keeper/state_transition_test.go +++ b/x/evm/keeper/state_transition_test.go @@ -1,8 +1,10 @@ package keeper_test import ( + "cosmossdk.io/core/comet" "fmt" "github.com/ethereum/go-ethereum/core/tracing" + cosmostracing "github.com/evmos/ethermint/x/evm/tracing" "math" "math/big" "testing" @@ -639,6 +641,91 @@ func (suite *StateTransitionTestSuite) TestApplyMessage() { suite.Require().False(res.Failed()) } +func (suite *StateTransitionTestSuite) TestApplyMessageTracer() { + expectedGasUsed := params.TxGas + var msg *core.Message + + suite.SetupTest() + suite.Ctx = suite.Ctx.WithCometInfo(NewMockCometInfo()) + suite.Ctx = suite.Ctx.WithConsensusParams(*testutil.DefaultConsensusParams) + + _, err := suite.App.EvmKeeper.EVMConfig(suite.Ctx, big.NewInt(9000), common.Hash{}) + suite.Require().NoError(err) + + keeperParams := suite.App.EvmKeeper.GetParams(suite.Ctx) + chainCfg := keeperParams.ChainConfig.EthereumConfig(suite.App.EvmKeeper.ChainID()) + rules := chainCfg.Rules(big.NewInt(suite.Ctx.BlockHeight()), chainCfg.MergeNetsplitBlock != nil, uint64(suite.Ctx.BlockHeader().Time.Unix())) + signer := ethtypes.LatestSignerForChainID(suite.App.EvmKeeper.ChainID()) + tracer := types.NewTracer("", msg, rules) + vmdb := suite.StateDB() + + t, err := types.NewFirehoseCosmosLiveTracer() + require.NoError(suite.T(), err) + suite.Ctx = cosmostracing.SetCtxBlockchainTracer(suite.Ctx, t) + + onCosmosTxStartHookCalled := false + onGasChangedHookCalled := false + onEnterHookCalled := false + onExitHookCalled := false + + startTxHook := t.OnCosmosTxStart + gasChangedHook := t.OnGasChange + enterHook := t.OnEnter + exitHook := t.OnExit + + t.OnCosmosTxStart = func(vm *tracing.VMContext, tx *ethtypes.Transaction, hash common.Hash, from common.Address) { + // call original hook + startTxHook(vm, tx, hash, from) + onCosmosTxStartHookCalled = true + } + t.OnGasChange = func(old, new uint64, reason tracing.GasChangeReason) { + // call original hook + gasChangedHook(old, new, reason) + onGasChangedHookCalled = true + } + t.OnEnter = func(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + // call original hook + enterHook(depth, typ, from, to, input, gas, value) + onEnterHookCalled = true + } + t.OnExit = func(depth int, output []byte, gasUsed uint64, err error, reverted bool) { + // call original hook + exitHook(depth, output, gasUsed, err, reverted) + onExitHookCalled = true + } + + // manually call on blockchain init + t.OnBlockchainInit(chainCfg) + suite.StateDB().SetTracer(t) + + msg, err = newNativeMessage( + vmdb.GetNonce(suite.Address), + suite.Ctx.BlockHeight(), + suite.Address, + chainCfg, + suite.Signer, + signer, + ethtypes.LegacyTxType, + nil, + nil, + ) + suite.Require().NoError(err) + + // manually call begin block + err = suite.App.EvmKeeper.BeginBlock(suite.Ctx) + suite.Require().NoError(err) + res, err := suite.App.EvmKeeper.ApplyMessage(suite.Ctx, msg, tracer, true) + + suite.Require().NoError(err) + suite.Require().Equal(expectedGasUsed, res.GasUsed) + suite.Require().False(res.Failed()) + + suite.Require().True(onCosmosTxStartHookCalled) + suite.Require().True(onGasChangedHookCalled) + suite.Require().True(onEnterHookCalled) + suite.Require().True(onExitHookCalled) +} + func (suite *StateTransitionTestSuite) TestApplyMessageWithConfig() { var ( msg *core.Message @@ -656,24 +743,6 @@ func (suite *StateTransitionTestSuite) TestApplyMessageWithConfig() { malleate func() expErr bool }{ - { - "message applied with firehose tracer", - func() { - msg, err = newNativeMessage( - vmdb.GetNonce(suite.Address), - suite.Ctx.BlockHeight(), - suite.Address, - chainCfg, - suite.Signer, - signer, - ethtypes.AccessListTxType, - nil, - nil, - ) - suite.Require().NoError(err) - }, - false, - }, { "message applied ok", func() { @@ -807,3 +876,26 @@ func (suite *StateTransitionTestSuite) TestGetProposerAddress() { }) } } + +type MockCometInfo struct { +} + +func NewMockCometInfo() *MockCometInfo { + return &MockCometInfo{} +} + +func (c *MockCometInfo) GetEvidence() comet.EvidenceList { + return nil +} + +func (c *MockCometInfo) GetValidatorsHash() []byte { + return []byte{} +} + +func (c *MockCometInfo) GetProposerAddress() []byte { + return []byte{} +} + +func (c *MockCometInfo) GetLastCommit() comet.CommitInfo { + return nil +} diff --git a/x/evm/statedb/statedb.go b/x/evm/statedb/statedb.go index bf62f02b63..17ca7495d9 100644 --- a/x/evm/statedb/statedb.go +++ b/x/evm/statedb/statedb.go @@ -123,7 +123,7 @@ func NewWithParams(ctx sdk.Context, keeper Keeper, txConfig TxConfig, evmDenom s cacheMS = parentCacheMS.Clone() commitMS = func() { parentCacheMS.Restore(cacheMS) } } else { - // in unit test, it could be run with a uncached multistore + // in unit test, it could be run with an uncached multistore if cacheMS, ok = ctx.MultiStore().CacheWrap().(cachemulti.Store); !ok { panic("expect the CacheWrap result to be cachemulti.Store") } @@ -148,7 +148,9 @@ func NewWithParams(ctx sdk.Context, keeper Keeper, txConfig TxConfig, evmDenom s } func (s *StateDB) SetTracer(tracer *cosmostracing.Hooks) { - s.evmTracer = tracer + if s.evmTracer == nil { + s.evmTracer = tracer + } } func (s *StateDB) NativeEvents() sdk.Events { @@ -167,6 +169,10 @@ func (s *StateDB) AddLog(log *ethtypes.Log) { log.TxIndex = s.txConfig.TxIndex log.Index = s.txConfig.LogIndex + uint(len(s.logs)) s.logs = append(s.logs, log) + + if s.evmTracer != nil && s.evmTracer.OnLog != nil { + s.evmTracer.OnLog(log) + } } // Logs returns the logs of current transaction. diff --git a/x/evm/statedb/statedb_test.go b/x/evm/statedb/statedb_test.go index f90b41cc5a..905d468684 100644 --- a/x/evm/statedb/statedb_test.go +++ b/x/evm/statedb/statedb_test.go @@ -341,7 +341,6 @@ func (suite *StateDBTestSuite) TestTracer_Nonce() { t.OnNonceChange = func(addr common.Address, prev, new uint64) { nonceChanges = append(nonceChanges, new) } - keeper.SetTracer(t) db := statedb.New(ctx, keeper, emptyTxConfig) db.SetTracer(t) tc.malleate(db) @@ -1079,7 +1078,6 @@ func (suite *StateDBTestSuite) TestTracer_SetStorage() { t.OnStorageChange = func(addr common.Address, slot common.Hash, prev, new common.Hash) { sChanges = append(sChanges, newStorageChange(addr.String(), slot.String(), prev.String(), new.String())) } - keeper.SetTracer(t) stateDB := statedb.New(ctx, keeper, emptyTxConfig) stateDB.SetTracer(t) diff --git a/x/evm/tracers/firehose.go b/x/evm/tracers/firehose.go index 148de47a00..7ae90d20a0 100644 --- a/x/evm/tracers/firehose.go +++ b/x/evm/tracers/firehose.go @@ -136,6 +136,7 @@ func NewCosmosTracingHooksFromFirehose(hooks *tracing.Hooks, firehose *Firehose) OnCosmosBlockStart: firehose.OnCosmosBlockStart, OnCosmosBlockEnd: firehose.OnCosmosBlockEnd, + OnCosmosTxStart: firehose.OnCosmosTxStart, } } @@ -265,6 +266,7 @@ func newFirehose(config *FirehoseConfig) (*Firehose, error) { // Transaction state transactionLogIndex: 0, + transactionIsolated: false, // Call state callStack: NewCallStack(), @@ -303,7 +305,7 @@ func (f *Firehose) newIsolatedTransactionTracer(tracerID string) *Firehose { // Transaction state transactionLogIndex: 0, - transactionIsolated: true, + transactionIsolated: false, // Call state callStack: NewCallStack(), @@ -654,7 +656,23 @@ func (f *Firehose) OnTxStart(evm *tracing.VMContext, tx *types.Transaction, from f.onTxStart(tx, tx.Hash(), from, to) } -// onTxStart is used internally a two places, in the normal "tracer" and in the "OnGenesisBlock", +func (f *Firehose) OnCosmosTxStart(evm *tracing.VMContext, tx *types.Transaction, hash common.Hash, from common.Address) { + firehoseInfo("trx start (tracer=%s hash=%s type=%d gas=%d isolated=%t input=%s)", f.tracerID, tx.Hash(), tx.Type(), tx.Gas(), f.transactionIsolated, inputView(tx.Data())) + + f.ensureInBlockAndNotInTrxAndNotInCall() + + f.evm = evm + var to common.Address + if tx.To() == nil { + to = crypto.CreateAddress(from, evm.StateDB.GetNonce(from)) + } else { + to = *tx.To() + } + + f.onTxStart(tx, hash, from, to) +} + +// onTxStart is used internally two places, in the normal "tracer" and in the "OnGenesisBlock", // we manually pass some override to the `tx` because genesis block has a different way of creating // the transaction that wraps the genesis block. func (f *Firehose) onTxStart(tx *types.Transaction, hash common.Hash, from, to common.Address) { @@ -692,18 +710,12 @@ func (f *Firehose) OnTxEnd(receipt *types.Receipt, err error) { firehoseInfo("trx ending (tracer=%s, isolated=%t, error=%s)", f.tracerID, f.transactionIsolated, errorView(err)) f.ensureInBlockAndInTrx() + // TODO: we do nothing with the error here ??? + // Shouldn't there be something that checks the type of error and sets the status of the transaction accordingly? trxTrace := f.completeTransaction(receipt) - // In this case, we are in some kind of parallel processing and we must simply add the transaction - // to a transient storage (and not in the block directly). Adding it to the block will be done by the - // `OnTxCommit` callback. if f.transactionIsolated { - f.transactionTransient = trxTrace - - // We must not reset transaction here. In the isolated transaction tracer, the transaction is reset - // by the `OnTxReset` callback which comes from outside the tracer. Second, resetting the transaction - // also resets the [f.transactionTransient] field which is the one we want to keep on completion - // of an isolated transaction. + panic("isolated transactions are not supported") } else { f.block.TransactionTraces = append(f.block.TransactionTraces, trxTrace) @@ -739,8 +751,6 @@ func (f *Firehose) completeTransaction(receipt *types.Receipt) *pbeth.Transactio f.transaction.Status = transactionStatusFromChainTxReceipt(receipt.Status) } - // todo: in some blockchains, when there is not received, it means that the transaction is failed, what about here? should it be the same? - // It's possible that the transaction was reverted, but we still have a receipt, in that case, we must // check the root call if rootCall.StatusReverted { @@ -804,7 +814,7 @@ func (f *Firehose) assignOrdinalAndIndexToReceiptLogs() { trx := f.transaction - callLogs := []*pbeth.Log{} + var callLogs []*pbeth.Log for _, call := range trx.Calls { firehoseTrace("checking call (reverted=%t logs=%d)", call.StateReverted, len(call.Logs)) if call.StateReverted { diff --git a/x/evm/tracers/tracing.go b/x/evm/tracers/tracing.go index 0e1411bf0d..c8d19ef146 100644 --- a/x/evm/tracers/tracing.go +++ b/x/evm/tracers/tracing.go @@ -1,9 +1,6 @@ package tracers import ( - "context" - sdk "github.com/cosmos/cosmos-sdk/types" - cosmostracing "github.com/ethereum/go-ethereum/core/tracing" "github.com/evmos/ethermint/x/evm/tracing" ) @@ -13,54 +10,3 @@ import ( // The scheme of the URL is going to be used to determine which tracer to use // by the registry. type BlockchainTracerFactory = func(backwardCompatibility bool) (*tracing.Hooks, error) - -type CtxBlockchainTracerKeyType string - -const CtxBlockchainTracerKey = CtxBlockchainTracerKeyType("evm_and_state_logger") - -func SetCtxBlockchainTracer(ctx context.Context, hooks *cosmostracing.Hooks) context.Context { - return context.WithValue(ctx, CtxBlockchainTracerKey, hooks) -} - -func GetCtxBlockchainTracer(ctx sdk.Context) *cosmostracing.Hooks { - rawVal := ctx.Context().Value(CtxBlockchainTracerKey) - if rawVal == nil { - return nil - } - logger, ok := rawVal.(*cosmostracing.Hooks) - if !ok { - return nil - } - return logger -} - -func GetCtxEthTracingHooks(ctx sdk.Context) *cosmostracing.Hooks { - if logger := GetCtxBlockchainTracer(ctx); logger != nil { - return logger - } - - return nil -} - -type TxTracerHooks struct { - Hooks *cosmostracing.Hooks - - OnTxReset func() - OnTxCommit func() -} - -func (h TxTracerHooks) InjectInContext(ctx sdk.Context) context.Context { - return SetCtxBlockchainTracer(ctx, h.Hooks) -} - -func (h TxTracerHooks) Reset() { - if h.OnTxReset != nil { - h.OnTxReset() - } -} - -func (h TxTracerHooks) Commit() { - if h.OnTxCommit != nil { - h.OnTxCommit() - } -} diff --git a/x/evm/tracing/context.go b/x/evm/tracing/context.go new file mode 100644 index 0000000000..aaaf052ae4 --- /dev/null +++ b/x/evm/tracing/context.go @@ -0,0 +1,36 @@ +package tracing + +import ( + "context" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +type CtxBlockchainTracerKeyType string + +const CtxBlockchainTracerKey = CtxBlockchainTracerKeyType("evm_and_state_logger") + +func SetCtxBlockchainTracer(ctx sdk.Context, logger *Hooks) sdk.Context { + return ctx.WithContext(context.WithValue(ctx.Context(), CtxBlockchainTracerKey, logger)) +} + +// GetCtxBlockchainTracer function to get the Cosmos specific [tracing.Hooks] struct +// used to trace EVM blocks and transactions. +func GetCtxBlockchainTracer(ctx sdk.Context) *Hooks { + rawVal := ctx.Context().Value(CtxBlockchainTracerKey) + if rawVal == nil { + return nil + } + logger, ok := rawVal.(*Hooks) + if !ok { + return nil + } + return logger +} + +func GetCtxEthTracingHooks(ctx sdk.Context) *Hooks { + if logger := GetCtxBlockchainTracer(ctx); logger != nil { + return logger + } + + return nil +} diff --git a/x/evm/tracing/hooks.go b/x/evm/tracing/hooks.go index 84ff1e4fd1..36d1412ef8 100644 --- a/x/evm/tracing/hooks.go +++ b/x/evm/tracing/hooks.go @@ -2,6 +2,7 @@ package tracing import ( "github.com/cometbft/cometbft/types" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/tracing" ethtypes "github.com/ethereum/go-ethereum/core/types" "math/big" @@ -10,13 +11,22 @@ import ( type ( OnCosmosBlockStart func(CosmosStartBlockEvent) OnCosmosBlockEnd func(CosmosEndBlockEvent, error) + OnCosmosTxStart func(evm *tracing.VMContext, tx *ethtypes.Transaction, txHash common.Hash, from common.Address) ) type Hooks struct { *tracing.Hooks + // OnCosmosBlockStart is called when a new block is started. OnCosmosBlockStart OnCosmosBlockStart - OnCosmosBlockEnd OnCosmosBlockEnd + + // OnCosmosBlockEnd is called when a block is finished. + OnCosmosBlockEnd OnCosmosBlockEnd + + // OnCosmosTxStart is called when a new transaction is started. + // The transaction hash calculated by the EVM is passed as an argument as it + // is not the same as the one calculated by tx.Hash() + OnCosmosTxStart OnCosmosTxStart } type CosmosStartBlockEvent struct {