From 35e7a98ab2947f7a95c1bacb9262d252347c918b Mon Sep 17 00:00:00 2001 From: noot <36753753+noot@users.noreply.github.com> Date: Wed, 1 Apr 2020 20:43:59 -0400 Subject: [PATCH] filters: begin implementation (#230) * adds Filter type and related methods * updates PublicFilterAPI to include backend, filter mapping * stub out filter related eth_ functions --- rpc/apis.go | 20 ++-- rpc/backend.go | 180 ++++++++++++++++++++++++++++++++ rpc/eth_api.go | 126 +++------------------- rpc/filter_api.go | 57 ++++++++-- rpc/filters.go | 72 ++++++++++--- x/evm/handler_test.go | 52 +++++++++ x/evm/types/state_transition.go | 1 + x/evm/types/statedb.go | 3 +- x/evm/types/utils.go | 1 + 9 files changed, 368 insertions(+), 144 deletions(-) create mode 100644 rpc/backend.go diff --git a/rpc/apis.go b/rpc/apis.go index dc51e16f..e6672dd9 100644 --- a/rpc/apis.go +++ b/rpc/apis.go @@ -8,36 +8,42 @@ import ( "github.com/ethereum/go-ethereum/rpc" ) +const Web3Namespace = "web3" +const EthNamespace = "eth" +const PersonalNamespace = "personal" +const NetNamespace = "net" + // GetRPCAPIs returns the list of all APIs func GetRPCAPIs(cliCtx context.CLIContext, key emintcrypto.PrivKeySecp256k1) []rpc.API { nonceLock := new(AddrLocker) + backend := NewEthermintBackend(cliCtx) return []rpc.API{ { - Namespace: "web3", + Namespace: Web3Namespace, Version: "1.0", Service: NewPublicWeb3API(), Public: true, }, { - Namespace: "eth", + Namespace: EthNamespace, Version: "1.0", - Service: NewPublicEthAPI(cliCtx, nonceLock, key), + Service: NewPublicEthAPI(cliCtx, backend, nonceLock, key), Public: true, }, { - Namespace: "personal", + Namespace: PersonalNamespace, Version: "1.0", Service: NewPersonalEthAPI(cliCtx, nonceLock), Public: false, }, { - Namespace: "eth", + Namespace: EthNamespace, Version: "1.0", - Service: NewPublicFilterAPI(cliCtx), + Service: NewPublicFilterAPI(cliCtx, backend), Public: true, }, { - Namespace: "net", + Namespace: NetNamespace, Version: "1.0", Service: NewPublicNetAPI(cliCtx), Public: true, diff --git a/rpc/backend.go b/rpc/backend.go new file mode 100644 index 00000000..18a909f2 --- /dev/null +++ b/rpc/backend.go @@ -0,0 +1,180 @@ +package rpc + +import ( + "fmt" + "math/big" + "strconv" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/ethermint/x/evm" + "github.com/cosmos/ethermint/x/evm/types" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + ethtypes "github.com/ethereum/go-ethereum/core/types" +) + +// Backend implements the functionality needed to filter changes. +// Implemented by EthermintBackend. +type Backend interface { + // Used by block filter; also used for polling + BlockNumber() (hexutil.Uint64, error) + GetBlockByNumber(blockNum BlockNumber, fullTx bool) (map[string]interface{}, error) + getEthBlockByNumber(height int64, fullTx bool) (map[string]interface{}, error) + getGasLimit() (int64, error) + + // Used by pending transaction filter + PendingTransactions() ([]*Transaction, error) + + // Used by log filter + GetTxLogs(txHash common.Hash) ([]*ethtypes.Log, error) + // TODO: Bloom methods +} + +// EmintBackend implements Backend +type EthermintBackend struct { + cliCtx context.CLIContext + gasLimit int64 +} + +func NewEthermintBackend(cliCtx context.CLIContext) *EthermintBackend { + return &EthermintBackend{ + cliCtx: cliCtx, + gasLimit: int64(^uint32(0)), + } +} + +// BlockNumber returns the current block number. +func (e *EthermintBackend) BlockNumber() (hexutil.Uint64, error) { + res, _, err := e.cliCtx.QueryWithData(fmt.Sprintf("custom/%s/blockNumber", types.ModuleName), nil) + if err != nil { + return hexutil.Uint64(0), err + } + + var out types.QueryResBlockNumber + e.cliCtx.Codec.MustUnmarshalJSON(res, &out) + return hexutil.Uint64(out.Number), nil +} + +// GetBlockByNumber returns the block identified by number. +func (e *EthermintBackend) GetBlockByNumber(blockNum BlockNumber, fullTx bool) (map[string]interface{}, error) { + value := blockNum.Int64() + return e.getEthBlockByNumber(value, fullTx) +} + +func (e *EthermintBackend) getEthBlockByNumber(height int64, fullTx bool) (map[string]interface{}, error) { + // Remove this check when 0 query is fixed ref: (https://github.com/tendermint/tendermint/issues/4014) + var blkNumPtr *int64 + if height != 0 { + blkNumPtr = &height + } + + block, err := e.cliCtx.Client.Block(blkNumPtr) + if err != nil { + return nil, err + } + header := block.Block.Header + + gasLimit, err := e.getGasLimit() + if err != nil { + return nil, err + } + + var ( + gasUsed *big.Int + transactions []interface{} + ) + + if fullTx { + // Populate full transaction data + transactions, gasUsed, err = convertTransactionsToRPC( + e.cliCtx, block.Block.Txs, common.BytesToHash(header.Hash()), uint64(header.Height), + ) + if err != nil { + return nil, err + } + } else { + // TODO: Gas used not saved and cannot be calculated by hashes + // Return slice of transaction hashes + transactions = make([]interface{}, len(block.Block.Txs)) + for i, tx := range block.Block.Txs { + transactions[i] = common.BytesToHash(tx.Hash()) + } + } + + res, _, err := e.cliCtx.Query(fmt.Sprintf("custom/%s/%s/%s", types.ModuleName, evm.QueryLogsBloom, strconv.FormatInt(block.Block.Height, 10))) + if err != nil { + return nil, err + } + + var out types.QueryBloomFilter + e.cliCtx.Codec.MustUnmarshalJSON(res, &out) + + return formatBlock(header, block.Block.Size(), gasLimit, gasUsed, transactions, out.Bloom), nil +} + +// getGasLimit returns the gas limit per block set in genesis +func (e *EthermintBackend) getGasLimit() (int64, error) { + // Retrieve from gasLimit variable cache + if e.gasLimit != -1 { + return e.gasLimit, nil + } + + // Query genesis block if hasn't been retrieved yet + genesis, err := e.cliCtx.Client.Genesis() + if err != nil { + return 0, err + } + + // Save value to gasLimit cached value + gasLimit := genesis.Genesis.ConsensusParams.Block.MaxGas + if gasLimit == -1 { + // Sets gas limit to max uint32 to not error with javascript dev tooling + // This -1 value indicating no block gas limit is set to max uint64 with geth hexutils + // which errors certain javascript dev tooling which only supports up to 53 bits + gasLimit = int64(^uint32(0)) + } + e.gasLimit = gasLimit + return gasLimit, nil +} + +// GetTxLogs returns the logs given a transaction hash. +func (e *EthermintBackend) GetTxLogs(txHash common.Hash) ([]*ethtypes.Log, error) { + // do we need to use the block height somewhere? + ctx := e.cliCtx + res, _, err := ctx.QueryWithData(fmt.Sprintf("custom/%s/%s/%s", types.ModuleName, evm.QueryTxLogs, txHash.Hex()), nil) + if err != nil { + return nil, err + } + + var out types.QueryETHLogs + e.cliCtx.Codec.MustUnmarshalJSON(res, &out) + return out.Logs, nil +} + +// PendingTransactions returns the transactions that are in the transaction pool +// and have a from address that is one of the accounts this node manages. +func (e *EthermintBackend) PendingTransactions() ([]*Transaction, error) { + pendingTxs, err := e.cliCtx.Client.UnconfirmedTxs(100) + if err != nil { + return nil, err + } + + transactions := make([]*Transaction, 0, 100) + for _, tx := range pendingTxs.Txs { + ethTx, err := bytesToEthTx(e.cliCtx, tx) + if err != nil { + return nil, err + } + + // * Should check signer and reference against accounts the node manages in future + rpcTx, err := newRPCTransaction(*ethTx, common.Hash{}, nil, 0) + if err != nil { + return nil, err + } + + transactions = append(transactions, rpcTx) + } + + return transactions, nil +} diff --git a/rpc/eth_api.go b/rpc/eth_api.go index fc460d39..27ae05ff 100644 --- a/rpc/eth_api.go +++ b/rpc/eth_api.go @@ -6,7 +6,6 @@ import ( "fmt" "log" "math/big" - "strconv" "sync" "github.com/spf13/viper" @@ -40,18 +39,19 @@ import ( // PublicEthAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec. type PublicEthAPI struct { cliCtx context.CLIContext + backend Backend key emintcrypto.PrivKeySecp256k1 nonceLock *AddrLocker keybaseLock sync.Mutex - gasLimit *int64 } // NewPublicEthAPI creates an instance of the public ETH Web3 API. -func NewPublicEthAPI(cliCtx context.CLIContext, nonceLock *AddrLocker, +func NewPublicEthAPI(cliCtx context.CLIContext, backend Backend, nonceLock *AddrLocker, key emintcrypto.PrivKeySecp256k1) *PublicEthAPI { return &PublicEthAPI{ cliCtx: cliCtx, + backend: backend, key: key, nonceLock: nonceLock, } @@ -132,14 +132,7 @@ func (e *PublicEthAPI) Accounts() ([]common.Address, error) { // BlockNumber returns the current block number. func (e *PublicEthAPI) BlockNumber() (hexutil.Uint64, error) { - res, _, err := e.cliCtx.QueryWithData(fmt.Sprintf("custom/%s/blockNumber", types.ModuleName), nil) - if err != nil { - return hexutil.Uint64(0), err - } - - var out types.QueryResBlockNumber - e.cliCtx.Codec.MustUnmarshalJSON(res, &out) - return hexutil.Uint64(out.Number), nil + return e.backend.BlockNumber() } // GetBalance returns the provided account's balance up to the provided block number. @@ -229,7 +222,7 @@ func (e *PublicEthAPI) GetUncleCountByBlockNumber(blockNum BlockNumber) hexutil. // GetCode returns the contract code at the given address and block number. func (e *PublicEthAPI) GetCode(address common.Address, blockNumber BlockNumber) (hexutil.Bytes, error) { ctx := e.cliCtx.WithHeight(blockNumber.Int64()) - res, _, err := ctx.QueryWithData(fmt.Sprintf("custom/%s/code/%s", types.ModuleName, address.Hex()), nil) + res, _, err := ctx.QueryWithData(fmt.Sprintf("custom/%s/%s/%s", types.ModuleName, evm.QueryCode, address.Hex()), nil) if err != nil { return nil, err } @@ -239,6 +232,11 @@ func (e *PublicEthAPI) GetCode(address common.Address, blockNumber BlockNumber) return out.Code, nil } +// GetTxLogs returns the logs given a transaction hash. +func (e *PublicEthAPI) GetTxLogs(txHash common.Hash) ([]*ethtypes.Log, error) { + return e.backend.GetTxLogs(txHash) +} + // Sign signs the provided data using the private key of address via Geth's signature standard. func (e *PublicEthAPI) Sign(address common.Address, data hexutil.Bytes) (hexutil.Bytes, error) { // TODO: Change this functionality to find an unlocked account by address @@ -480,64 +478,12 @@ func (e *PublicEthAPI) GetBlockByHash(hash common.Hash, fullTx bool) (map[string var out types.QueryResBlockNumber e.cliCtx.Codec.MustUnmarshalJSON(res, &out) - return e.getEthBlockByNumber(out.Number, fullTx) + return e.backend.getEthBlockByNumber(out.Number, fullTx) } // GetBlockByNumber returns the block identified by number. func (e *PublicEthAPI) GetBlockByNumber(blockNum BlockNumber, fullTx bool) (map[string]interface{}, error) { - value := blockNum.Int64() - return e.getEthBlockByNumber(value, fullTx) -} - -func (e *PublicEthAPI) getEthBlockByNumber(height int64, fullTx bool) (map[string]interface{}, error) { - // Remove this check when 0 query is fixed ref: (https://github.com/tendermint/tendermint/issues/4014) - var blkNumPtr *int64 - if height != 0 { - blkNumPtr = &height - } - - block, err := e.cliCtx.Client.Block(blkNumPtr) - if err != nil { - return nil, err - } - header := block.Block.Header - - gasLimit, err := e.getGasLimit() - if err != nil { - return nil, err - } - - var ( - gasUsed *big.Int - transactions []interface{} - ) - - if fullTx { - // Populate full transaction data - transactions, gasUsed, err = convertTransactionsToRPC( - e.cliCtx, block.Block.Txs, common.BytesToHash(header.Hash()), uint64(header.Height), - ) - if err != nil { - return nil, err - } - } else { - // TODO: Gas used not saved and cannot be calculated by hashes - // Return slice of transaction hashes - transactions = make([]interface{}, len(block.Block.Txs)) - for i, tx := range block.Block.Txs { - transactions[i] = common.BytesToHash(tx.Hash()) - } - } - - res, _, err := e.cliCtx.Query(fmt.Sprintf("custom/%s/%s/%s", types.ModuleName, evm.QueryLogsBloom, strconv.FormatInt(block.Block.Height, 10))) - if err != nil { - return nil, err - } - - var out types.QueryBloomFilter - e.cliCtx.Codec.MustUnmarshalJSON(res, &out) - - return formatBlock(header, block.Block.Size(), gasLimit, gasUsed, transactions, out.Bloom), nil + return e.backend.GetBlockByNumber(blockNum, fullTx) } func formatBlock( @@ -782,28 +728,7 @@ func (e *PublicEthAPI) GetTransactionReceipt(hash common.Hash) (map[string]inter // PendingTransactions returns the transactions that are in the transaction pool // and have a from address that is one of the accounts this node manages. func (e *PublicEthAPI) PendingTransactions() ([]*Transaction, error) { - pendingTxs, err := e.cliCtx.Client.UnconfirmedTxs(100) - if err != nil { - return nil, err - } - - transactions := make([]*Transaction, 0, 100) - for _, tx := range pendingTxs.Txs { - ethTx, err := bytesToEthTx(e.cliCtx, tx) - if err != nil { - return nil, err - } - - // * Should check signer and reference against accounts the node manages in future - rpcTx, err := newRPCTransaction(*ethTx, common.Hash{}, nil, 0) - if err != nil { - return nil, err - } - - transactions = append(transactions, rpcTx) - } - - return transactions, nil + return e.backend.PendingTransactions() } // GetUncleByBlockHashAndIndex returns the uncle identified by hash and index. Always returns nil. @@ -878,31 +803,6 @@ func (e *PublicEthAPI) GetProof(address common.Address, storageKeys []string, bl }, nil } -// getGasLimit returns the gas limit per block set in genesis -func (e *PublicEthAPI) getGasLimit() (int64, error) { - // Retrieve from gasLimit variable cache - if e.gasLimit != nil { - return *e.gasLimit, nil - } - - // Query genesis block if hasn't been retrieved yet - genesis, err := e.cliCtx.Client.Genesis() - if err != nil { - return 0, err - } - - // Save value to gasLimit cached value - gasLimit := genesis.Genesis.ConsensusParams.Block.MaxGas - if gasLimit == -1 { - // Sets gas limit to max uint32 to not error with javascript dev tooling - // This -1 value indicating no block gas limit is set to max uint64 with geth hexutils - // which errors certain javascript dev tooling which only supports up to 53 bits - gasLimit = int64(^uint32(0)) - } - e.gasLimit = &gasLimit - return gasLimit, nil -} - // generateFromArgs populates tx message with args (used in RPC API) func (e *PublicEthAPI) generateFromArgs(args params.SendTxArgs) (*types.MsgEthereumTx, error) { var ( diff --git a/rpc/filter_api.go b/rpc/filter_api.go index 06a929ea..cc8f73fc 100644 --- a/rpc/filter_api.go +++ b/rpc/filter_api.go @@ -3,12 +3,11 @@ package rpc import ( "encoding/json" "fmt" + "math/big" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/ethermint/x/evm/types" - "math/big" - ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/rpc" @@ -16,16 +15,62 @@ import ( // PublicFilterAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec. type PublicFilterAPI struct { - cliCtx context.CLIContext + cliCtx context.CLIContext + backend Backend + filters map[rpc.ID]*Filter // ID to filter; TODO: change to sync.Map in case of concurrent writes } // NewPublicEthAPI creates an instance of the public ETH Web3 API. -func NewPublicFilterAPI(cliCtx context.CLIContext) *PublicFilterAPI { +func NewPublicFilterAPI(cliCtx context.CLIContext, backend Backend) *PublicFilterAPI { return &PublicFilterAPI{ - cliCtx: cliCtx, + cliCtx: cliCtx, + backend: backend, + filters: make(map[rpc.ID]*Filter), } } +// NewFilter instantiates a new filter. +func (e *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) rpc.ID { + id := rpc.NewID() + e.filters[id] = NewFilter(e.backend, &criteria) + return id +} + +// NewBlockFilter instantiates a new block filter. +func (e *PublicFilterAPI) NewBlockFilter() rpc.ID { + id := rpc.NewID() + e.filters[id] = NewBlockFilter(e.backend) + return id +} + +// NewPendingTransactionFilter instantiates a new pending transaction filter. +func (e *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID { + id := rpc.NewID() + e.filters[id] = NewPendingTransactionFilter(e.backend) + return id +} + +// UninstallFilter uninstalls a filter with the given ID. +func (e *PublicFilterAPI) UninstallFilter(id rpc.ID) bool { + // TODO + e.filters[id].uninstallFilter() + delete(e.filters, id) + return true +} + +// GetFilterChanges returns an array of changes since the last poll. +// If the filter is a log filter, it returns an array of Logs. +// If the filter is a block filter, it returns an array of block hashes. +// If the filter is a pending transaction filter, it returns an array of transaction hashes. +func (e *PublicFilterAPI) GetFilterChanges(id rpc.ID) interface{} { + return e.filters[id].getFilterChanges() +} + +// GetFilterLogs returns an array of all logs matching filter with given id. +func (e *PublicFilterAPI) GetFilterLogs(id rpc.ID) []*ethtypes.Log { + return e.filters[id].getFilterLogs() +} + // GetLogs returns logs matching the given argument that are stored within the state. // // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getlogs @@ -35,7 +80,7 @@ func (e *PublicFilterAPI) GetLogs(criteria filters.FilterCriteria) ([]*ethtypes. /* Still need to add blockhash in prepare function for log entry */ - filter = NewBlockFilter(*criteria.BlockHash, criteria.Addresses, criteria.Topics) + filter = NewFilterWithBlockHash(e.backend, &criteria) results := e.getLogs() logs := filterLogs(results, nil, nil, filter.addresses, filter.topics) return logs, nil diff --git a/rpc/filters.go b/rpc/filters.go index 8631ff15..6e63f317 100644 --- a/rpc/filters.go +++ b/rpc/filters.go @@ -5,6 +5,7 @@ import ( "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/filters" ) /* @@ -14,28 +15,65 @@ import ( // Filter can be used to retrieve and filter logs. type Filter struct { - addresses []common.Address - topics [][]common.Hash - - block common.Hash // Block hash if filtering a single block + backend Backend + fromBlock, toBlock *big.Int // start and end block numbers + addresses []common.Address // contract addresses to watch + topics [][]common.Hash // log topics to watch for + blockHash *common.Hash // Block hash if filtering a single block } -// NewBlockFilter creates a new filter which directly inspects the contents of -// a block to figure out whether it is interesting or not. -func NewBlockFilter(block common.Hash, addresses []common.Address, topics [][]common.Hash) *Filter { - // Create a generic filter and convert it into a block filter - filter := newFilter(addresses, topics) - filter.block = block +// NewFilter returns a new Filter +func NewFilter(backend Backend, criteria *filters.FilterCriteria) *Filter { + return &Filter{ + backend: backend, + fromBlock: criteria.FromBlock, + toBlock: criteria.ToBlock, + addresses: criteria.Addresses, + topics: criteria.Topics, + } +} + +// NewFilterWithBlockHash returns a new Filter with a blockHash. +func NewFilterWithBlockHash(backend Backend, criteria *filters.FilterCriteria) *Filter { + return &Filter{ + backend: backend, + fromBlock: criteria.FromBlock, + toBlock: criteria.ToBlock, + addresses: criteria.Addresses, + topics: criteria.Topics, + blockHash: criteria.BlockHash, + } +} + +// NewBlockFilter creates a new filter that notifies when a block arrives. +func NewBlockFilter(backend Backend) *Filter { + // TODO: finish + filter := NewFilter(backend, nil) return filter } -// newFilter creates a generic filter that can either filter based on a block hash, -// or based on range queries. The search criteria needs to be explicitly set. -func newFilter(addresses []common.Address, topics [][]common.Hash) *Filter { - return &Filter{ - addresses: addresses, - topics: topics, - } +// NewPendingTransactionFilter creates a new filter that notifies when a pending transaction arrives. +func NewPendingTransactionFilter(backend Backend) *Filter { + // TODO: finish + filter := NewFilter(backend, nil) + return filter +} + +func (f *Filter) uninstallFilter() { + // TODO +} + +func (f *Filter) getFilterChanges() interface{} { + // TODO + // we might want to use an interface for Filters themselves because of this function, it may return an array of logs + // or an array of hashes, depending of whether Filter is a log filter or a block/transaction filter. + // or, we can add a type field to Filter. + return nil +} + +func (f *Filter) getFilterLogs() []*ethtypes.Log { + // TODO + return nil } func includes(addresses []common.Address, a common.Address) bool { diff --git a/x/evm/handler_test.go b/x/evm/handler_test.go index 4419bd77..c8c2bce9 100644 --- a/x/evm/handler_test.go +++ b/x/evm/handler_test.go @@ -1,12 +1,14 @@ package evm_test import ( + "fmt" "math/big" "testing" "time" "github.com/stretchr/testify/suite" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" @@ -14,6 +16,7 @@ import ( "github.com/cosmos/ethermint/app" "github.com/cosmos/ethermint/x/evm" + "github.com/cosmos/ethermint/x/evm/keeper" "github.com/cosmos/ethermint/x/evm/types" abci "github.com/tendermint/tendermint/abci/types" @@ -24,7 +27,9 @@ type EvmTestSuite struct { ctx sdk.Context handler sdk.Handler + querier sdk.Querier app *app.EthermintApp + codec *codec.Codec } func (suite *EvmTestSuite) SetupTest() { @@ -33,6 +38,8 @@ func (suite *EvmTestSuite) SetupTest() { suite.app = app.Setup(checkTx) suite.ctx = suite.app.BaseApp.NewContext(checkTx, abci.Header{Height: 1, ChainID: "3", Time: time.Now().UTC()}) suite.handler = evm.NewHandler(suite.app.EvmKeeper) + suite.querier = keeper.NewQuerier(suite.app.EvmKeeper) + suite.codec = codec.New() } func TestEvmTestSuite(t *testing.T) { @@ -87,3 +94,48 @@ func (suite *EvmTestSuite) TestHandler_Logs() { suite.Require().Equal(logs, resultData.Logs) } + +func (suite *EvmTestSuite) TestQueryTxLogs() { + gasLimit := uint64(100000) + gasPrice := big.NewInt(1000000) + + priv, err := crypto.GenerateKey() + suite.Require().NoError(err, "failed to create key") + + // send contract deployment transaction with an event in the constructor + bytecode := common.FromHex("0x6080604052348015600f57600080fd5b5060117f775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd73889860405160405180910390a2603580604b6000396000f3fe6080604052600080fdfea165627a7a723058206cab665f0f557620554bb45adf266708d2bd349b8a4314bdff205ee8440e3c240029") + tx := types.NewMsgEthereumTx(1, nil, big.NewInt(0), gasLimit, gasPrice, bytecode) + tx.Sign(big.NewInt(3), priv) + + // result, err := evm.HandleEthTxMsg(suite.ctx, suite.app.EvmKeeper, tx) + // suite.Require().NoError(err, "failed to handle eth tx msg") + + result := suite.handler(suite.ctx, tx) + suite.Require().True(result.IsOK()) + + resultData, err := types.DecodeResultData(result.Data) + suite.Require().NoError(err, "failed to decode result data") + + suite.Require().Equal(len(resultData.Logs), 1) + suite.Require().Equal(len(resultData.Logs[0].Topics), 2) + + // get logs by tx hash + hash := resultData.TxHash.Bytes() + + logs, err := suite.app.EvmKeeper.GetTransactionLogs(suite.ctx, hash) + suite.Require().NoError(err, "failed to get logs") + + suite.Require().Equal(logs, resultData.Logs) + + // query tx logs + path := []string{"txLogs", fmt.Sprintf("0x%x", hash)} + res, err := suite.querier(suite.ctx, path, abci.RequestQuery{}) + suite.Require().NoError(err, "failed to query txLogs") + + var txLogs types.QueryETHLogs + suite.codec.MustUnmarshalJSON(res, &txLogs) + + // amino decodes an empty byte array as nil, whereas JSON decodes it as []byte{} causing a discrepancy + resultData.Logs[0].Data = []byte{} + suite.Require().Equal(txLogs.Logs[0], resultData.Logs[0]) +} diff --git a/x/evm/types/state_transition.go b/x/evm/types/state_transition.go index af0dece8..5846e66d 100644 --- a/x/evm/types/state_transition.go +++ b/x/evm/types/state_transition.go @@ -138,6 +138,7 @@ func (st StateTransition) TransitionCSDB(ctx sdk.Context) (*ReturnData, error) { Bloom: bloomFilter, Logs: logs, Ret: ret, + TxHash: *st.THash, } resultData, err := EncodeResultData(res) diff --git a/x/evm/types/statedb.go b/x/evm/types/statedb.go index 576a625a..b4896c81 100644 --- a/x/evm/types/statedb.go +++ b/x/evm/types/statedb.go @@ -7,6 +7,7 @@ import ( "sync" sdk "github.com/cosmos/cosmos-sdk/types" + emint "github.com/cosmos/ethermint/types" ethcmn "github.com/ethereum/go-ethereum/common" @@ -302,7 +303,7 @@ func (csdb *CommitStateDB) GetLogs(hash ethcmn.Hash) ([]*ethtypes.Log, error) { logs, err := DecodeLogs(encLogs) if err != nil { - return []*ethtypes.Log{}, err + return nil, err } return logs, nil diff --git a/x/evm/types/utils.go b/x/evm/types/utils.go index cb9c1d46..41fd5c36 100644 --- a/x/evm/types/utils.go +++ b/x/evm/types/utils.go @@ -53,6 +53,7 @@ type ResultData struct { Bloom ethtypes.Bloom Logs []*ethtypes.Log Ret []byte + TxHash ethcmn.Hash } // EncodeReturnData takes all of the necessary data from the EVM execution