package backend import ( "bytes" "context" "encoding/json" "fmt" "math/big" "strconv" "time" "github.com/cosmos/cosmos-sdk/client/flags" codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/server" authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" tmrpctypes "github.com/tendermint/tendermint/rpc/core/types" "google.golang.org/grpc" "google.golang.org/grpc/metadata" "github.com/pkg/errors" "github.com/tendermint/tendermint/libs/log" tmtypes "github.com/tendermint/tendermint/types" "github.com/cosmos/cosmos-sdk/client" sdk "github.com/cosmos/cosmos-sdk/types" grpctypes "github.com/cosmos/cosmos-sdk/types/grpc" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/tharsis/ethermint/rpc/ethereum/namespaces/eth/filters" "github.com/tharsis/ethermint/rpc/ethereum/types" "github.com/tharsis/ethermint/server/config" ethermint "github.com/tharsis/ethermint/types" evmtypes "github.com/tharsis/ethermint/x/evm/types" feemarkettypes "github.com/tharsis/ethermint/x/feemarket/types" ) // Backend implements the functionality shared within namespaces. // Implemented by EVMBackend. type Backend interface { // Fee API FeeHistory(blockCount rpc.DecimalOrHex, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*types.FeeHistoryResult, error) // General Ethereum API RPCGasCap() uint64 // global gas cap for eth_call over rpc: DoS protection RPCEVMTimeout() time.Duration // global timeout for eth_call over rpc: DoS protection RPCTxFeeCap() float64 // RPCTxFeeCap is the global transaction fee(price * gaslimit) cap for send-transaction variants. The unit is ether. RPCMinGasPrice() int64 SuggestGasTipCap() (*big.Int, error) // Blockchain API BlockNumber() (hexutil.Uint64, error) GetTendermintBlockByNumber(blockNum types.BlockNumber) (*tmrpctypes.ResultBlock, error) GetTendermintBlockByHash(blockHash common.Hash) (*tmrpctypes.ResultBlock, error) GetBlockByNumber(blockNum types.BlockNumber, fullTx bool) (map[string]interface{}, error) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) BlockByNumber(blockNum types.BlockNumber) (*ethtypes.Block, error) BlockByHash(blockHash common.Hash) (*ethtypes.Block, error) CurrentHeader() *ethtypes.Header HeaderByNumber(blockNum types.BlockNumber) (*ethtypes.Header, error) HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error) PendingTransactions() ([]*sdk.Tx, error) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error) GetTransactionCount(address common.Address, blockNum types.BlockNumber) (*hexutil.Uint64, error) SendTransaction(args evmtypes.TransactionArgs) (common.Hash, error) GetCoinbase() (sdk.AccAddress, error) GetTransactionByHash(txHash common.Hash) (*types.RPCTransaction, error) GetTxByEthHash(txHash common.Hash) (*tmrpctypes.ResultTx, error) EstimateGas(args evmtypes.TransactionArgs, blockNrOptional *types.BlockNumber) (hexutil.Uint64, error) BaseFee(height int64) (*big.Int, error) // Filter API BloomStatus() (uint64, uint64) GetLogs(hash common.Hash) ([][]*ethtypes.Log, error) GetLogsByHeight(height *int64) ([][]*ethtypes.Log, error) GetFilteredBlocks(from int64, to int64, filter [][]filters.BloomIV, filterAddresses bool) ([]int64, error) ChainConfig() *params.ChainConfig SetTxDefaults(args evmtypes.TransactionArgs) (evmtypes.TransactionArgs, error) GetEthereumMsgsFromTendermintBlock(block *tmrpctypes.ResultBlock) []*evmtypes.MsgEthereumTx } var _ Backend = (*EVMBackend)(nil) var bAttributeKeyEthereumBloom = []byte(evmtypes.AttributeKeyEthereumBloom) // EVMBackend implements the Backend interface type EVMBackend struct { ctx context.Context clientCtx client.Context queryClient *types.QueryClient // gRPC query client logger log.Logger chainID *big.Int cfg config.Config } // NewEVMBackend creates a new EVMBackend instance func NewEVMBackend(ctx *server.Context, logger log.Logger, clientCtx client.Context) *EVMBackend { chainID, err := ethermint.ParseChainID(clientCtx.ChainID) if err != nil { panic(err) } appConf := config.GetConfig(ctx.Viper) return &EVMBackend{ ctx: context.Background(), clientCtx: clientCtx, queryClient: types.NewQueryClient(clientCtx), logger: logger.With("module", "evm-backend"), chainID: chainID, cfg: appConf, } } // BlockNumber returns the current block number in abci app state. // Because abci app state could lag behind from tendermint latest block, it's more stable // for the client to use the latest block number in abci app state than tendermint rpc. func (e *EVMBackend) BlockNumber() (hexutil.Uint64, error) { // do any grpc query, ignore the response and use the returned block height var header metadata.MD _, err := e.queryClient.Params(e.ctx, &evmtypes.QueryParamsRequest{}, grpc.Header(&header)) if err != nil { return hexutil.Uint64(0), err } blockHeightHeader := header.Get(grpctypes.GRPCBlockHeightHeader) if headerLen := len(blockHeightHeader); headerLen != 1 { return 0, fmt.Errorf("unexpected '%s' gRPC header length; got %d, expected: %d", grpctypes.GRPCBlockHeightHeader, headerLen, 1) } height, err := strconv.ParseUint(blockHeightHeader[0], 10, 64) if err != nil { return 0, fmt.Errorf("failed to parse block height: %w", err) } return hexutil.Uint64(height), nil } // GetBlockByNumber returns the block identified by number. func (e *EVMBackend) GetBlockByNumber(blockNum types.BlockNumber, fullTx bool) (map[string]interface{}, error) { resBlock, err := e.GetTendermintBlockByNumber(blockNum) if err != nil { return nil, err } // return if requested block height is greater than the current one if resBlock == nil || resBlock.Block == nil { return nil, nil } res, err := e.EthBlockFromTendermint(resBlock.Block, fullTx) if err != nil { e.logger.Debug("EthBlockFromTendermint failed", "height", blockNum, "error", err.Error()) return nil, err } return res, nil } // GetBlockByHash returns the block identified by hash. func (e *EVMBackend) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) { resBlock, err := e.clientCtx.Client.BlockByHash(e.ctx, hash.Bytes()) if err != nil { e.logger.Debug("BlockByHash block not found", "hash", hash.Hex(), "error", err.Error()) return nil, err } if resBlock == nil || resBlock.Block == nil { e.logger.Debug("BlockByHash block not found", "hash", hash.Hex()) return nil, nil } return e.EthBlockFromTendermint(resBlock.Block, fullTx) } // BlockByNumber returns the block identified by number. func (e *EVMBackend) BlockByNumber(blockNum types.BlockNumber) (*ethtypes.Block, error) { height := blockNum.Int64() switch blockNum { case types.EthLatestBlockNumber: currentBlockNumber, _ := e.BlockNumber() if currentBlockNumber > 0 { height = int64(currentBlockNumber) } case types.EthPendingBlockNumber: currentBlockNumber, _ := e.BlockNumber() if currentBlockNumber > 0 { height = int64(currentBlockNumber) } case types.EthEarliestBlockNumber: height = 1 default: if blockNum < 0 { return nil, errors.Errorf("incorrect block height: %d", height) } } resBlock, err := e.clientCtx.Client.Block(e.ctx, &height) if err != nil { e.logger.Debug("HeaderByNumber failed", "height", height) return nil, err } if resBlock == nil || resBlock.Block == nil { return nil, errors.Errorf("block not found for height %d", height) } return e.EthBlockFromTm(resBlock.Block) } // BlockByHash returns the block identified by hash. func (e *EVMBackend) BlockByHash(hash common.Hash) (*ethtypes.Block, error) { resBlock, err := e.clientCtx.Client.BlockByHash(e.ctx, hash.Bytes()) if err != nil { e.logger.Debug("HeaderByHash failed", "hash", hash.Hex()) return nil, err } if resBlock == nil || resBlock.Block == nil { return nil, errors.Errorf("block not found for hash %s", hash) } return e.EthBlockFromTm(resBlock.Block) } func (e *EVMBackend) EthBlockFromTm(block *tmtypes.Block) (*ethtypes.Block, error) { height := block.Height bloom, err := e.BlockBloom(&height) if err != nil { e.logger.Debug("HeaderByNumber BlockBloom failed", "height", height) } baseFee, err := e.BaseFee(height) if err != nil { e.logger.Debug("HeaderByNumber BaseFee failed", "height", height, "error", err.Error()) return nil, err } ethHeader := types.EthHeaderFromTendermint(block.Header, bloom, baseFee) var txs []*ethtypes.Transaction for _, txBz := range block.Txs { tx, err := e.clientCtx.TxConfig.TxDecoder()(txBz) if err != nil { e.logger.Debug("failed to decode transaction in block", "height", height, "error", err.Error()) continue } for _, msg := range tx.GetMsgs() { ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) if !ok { continue } tx := ethMsg.AsTransaction() txs = append(txs, tx) } } // TODO: add tx receipts ethBlock := ethtypes.NewBlock(ethHeader, txs, nil, nil, nil) return ethBlock, nil } // GetTendermintBlockByNumber returns a Tendermint format block by block number func (e *EVMBackend) GetTendermintBlockByNumber(blockNum types.BlockNumber) (*tmrpctypes.ResultBlock, error) { height := blockNum.Int64() currentBlockNumber, _ := e.BlockNumber() switch blockNum { case types.EthLatestBlockNumber: if currentBlockNumber > 0 { height = int64(currentBlockNumber) } case types.EthPendingBlockNumber: if currentBlockNumber > 0 { height = int64(currentBlockNumber) } case types.EthEarliestBlockNumber: height = 1 default: if blockNum < 0 { return nil, errors.Errorf("cannot fetch a negative block height: %d", height) } if height > int64(currentBlockNumber) { return nil, nil } } resBlock, err := e.clientCtx.Client.Block(e.ctx, &height) if err != nil { if resBlock, err = e.clientCtx.Client.Block(e.ctx, nil); err != nil { e.logger.Debug("tendermint client failed to get latest block", "height", height, "error", err.Error()) return nil, nil } } if resBlock.Block == nil { e.logger.Debug("GetBlockByNumber block not found", "height", height) return nil, nil } return resBlock, nil } // GetTendermintBlockByHash returns a Tendermint format block by block number func (e *EVMBackend) GetTendermintBlockByHash(blockHash common.Hash) (*tmrpctypes.ResultBlock, error) { resBlock, err := e.clientCtx.Client.BlockByHash(e.ctx, blockHash.Bytes()) if err != nil { e.logger.Debug("tendermint client failed to get block", "blockHash", blockHash.Hex(), "error", err.Error()) } if resBlock == nil || resBlock.Block == nil { e.logger.Debug("GetBlockByNumber block not found", "blockHash", blockHash.Hex()) return nil, nil } return resBlock, nil } // BlockBloom query block bloom filter from block results func (e *EVMBackend) BlockBloom(height *int64) (ethtypes.Bloom, error) { result, err := e.clientCtx.Client.BlockResults(e.ctx, height) if err != nil { return ethtypes.Bloom{}, err } for _, event := range result.EndBlockEvents { if event.Type != evmtypes.EventTypeBlockBloom { continue } for _, attr := range event.Attributes { if bytes.Equal(attr.Key, bAttributeKeyEthereumBloom) { return ethtypes.BytesToBloom(attr.Value), nil } } } return ethtypes.Bloom{}, errors.New("block bloom event is not found") } // EthBlockFromTendermint returns a JSON-RPC compatible Ethereum block from a given Tendermint block and its block result. func (e *EVMBackend) EthBlockFromTendermint( block *tmtypes.Block, fullTx bool, ) (map[string]interface{}, error) { ethRPCTxs := []interface{}{} ctx := types.ContextWithHeight(block.Height) baseFee, err := e.BaseFee(block.Height) if err != nil { return nil, err } for i, txBz := range block.Txs { tx, err := e.clientCtx.TxConfig.TxDecoder()(txBz) if err != nil { e.logger.Debug("failed to decode transaction in block", "height", block.Height, "error", err.Error()) continue } for _, msg := range tx.GetMsgs() { ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) if !ok { continue } tx := ethMsg.AsTransaction() // check tx exists on EVM and it has the correct block height ethTx, err := e.GetTxByEthHash(tx.Hash()) if err != nil || ethTx.Height != block.Height { e.logger.Debug("failed to query eth tx", "hash", tx.Hash().Hex()) continue } if !fullTx { hash := tx.Hash() ethRPCTxs = append(ethRPCTxs, hash) continue } rpcTx, err := types.NewRPCTransaction( tx, common.BytesToHash(block.Hash()), uint64(block.Height), uint64(i), baseFee, ) if err != nil { e.logger.Debug("NewTransactionFromData for receipt failed", "hash", tx.Hash().Hex(), "error", err.Error()) continue } ethRPCTxs = append(ethRPCTxs, rpcTx) } } bloom, err := e.BlockBloom(&block.Height) if err != nil { e.logger.Debug("failed to query BlockBloom", "height", block.Height, "error", err.Error()) } req := &evmtypes.QueryValidatorAccountRequest{ ConsAddress: sdk.ConsAddress(block.Header.ProposerAddress).String(), } res, err := e.queryClient.ValidatorAccount(ctx, req) if err != nil { e.logger.Debug( "failed to query validator operator address", "height", block.Height, "cons-address", req.ConsAddress, "error", err.Error(), ) return nil, err } addr, err := sdk.AccAddressFromBech32(res.AccountAddress) if err != nil { return nil, err } validatorAddr := common.BytesToAddress(addr) gasLimit, err := types.BlockMaxGasFromConsensusParams(ctx, e.clientCtx) if err != nil { e.logger.Error("failed to query consensus params", "error", err.Error()) } resBlockResult, err := e.clientCtx.Client.BlockResults(e.ctx, &block.Height) if err != nil { e.logger.Debug("EthBlockFromTendermint block result not found", "height", block.Height, "error", err.Error()) return nil, err } gasUsed := uint64(0) for _, txsResult := range resBlockResult.TxsResults { gasUsed += uint64(txsResult.GetGasUsed()) } formattedBlock := types.FormatBlock( block.Header, block.Size(), gasLimit, new(big.Int).SetUint64(gasUsed), ethRPCTxs, bloom, validatorAddr, baseFee, ) return formattedBlock, nil } // CurrentHeader returns the latest block header func (e *EVMBackend) CurrentHeader() *ethtypes.Header { header, _ := e.HeaderByNumber(types.EthLatestBlockNumber) return header } // HeaderByNumber returns the block header identified by height. func (e *EVMBackend) HeaderByNumber(blockNum types.BlockNumber) (*ethtypes.Header, error) { height := blockNum.Int64() switch blockNum { case types.EthLatestBlockNumber: currentBlockNumber, _ := e.BlockNumber() if currentBlockNumber > 0 { height = int64(currentBlockNumber) } case types.EthPendingBlockNumber: currentBlockNumber, _ := e.BlockNumber() if currentBlockNumber > 0 { height = int64(currentBlockNumber) } case types.EthEarliestBlockNumber: height = 1 default: if blockNum < 0 { return nil, errors.Errorf("incorrect block height: %d", height) } } resBlock, err := e.clientCtx.Client.Block(e.ctx, &height) if err != nil { e.logger.Debug("HeaderByNumber failed") return nil, err } bloom, err := e.BlockBloom(&resBlock.Block.Height) if err != nil { e.logger.Debug("HeaderByNumber BlockBloom failed", "height", resBlock.Block.Height) } baseFee, err := e.BaseFee(resBlock.Block.Height) if err != nil { e.logger.Debug("HeaderByNumber BaseFee failed", "height", resBlock.Block.Height, "error", err.Error()) return nil, err } ethHeader := types.EthHeaderFromTendermint(resBlock.Block.Header, bloom, baseFee) return ethHeader, nil } // HeaderByHash returns the block header identified by hash. func (e *EVMBackend) HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error) { resBlock, err := e.clientCtx.Client.BlockByHash(e.ctx, blockHash.Bytes()) if err != nil { e.logger.Debug("HeaderByHash failed", "hash", blockHash.Hex()) return nil, err } if resBlock == nil || resBlock.Block == nil { return nil, errors.Errorf("block not found for hash %s", blockHash.Hex()) } bloom, err := e.BlockBloom(&resBlock.Block.Height) if err != nil { e.logger.Debug("HeaderByHash BlockBloom failed", "height", resBlock.Block.Height) } baseFee, err := e.BaseFee(resBlock.Block.Height) if err != nil { e.logger.Debug("HeaderByHash BaseFee failed", "height", resBlock.Block.Height, "error", err.Error()) return nil, err } ethHeader := types.EthHeaderFromTendermint(resBlock.Block.Header, bloom, baseFee) return ethHeader, nil } // GetTransactionLogs returns the logs given a transaction hash. // It returns an error if there's an encoding error. // If no logs are found for the tx hash, the error is nil. func (e *EVMBackend) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error) { tx, err := e.GetTxByEthHash(txHash) if err != nil { return nil, err } return TxLogsFromEvents(tx.TxResult.Events) } // 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 *EVMBackend) PendingTransactions() ([]*sdk.Tx, error) { res, err := e.clientCtx.Client.UnconfirmedTxs(e.ctx, nil) if err != nil { return nil, err } result := make([]*sdk.Tx, 0, len(res.Txs)) for _, txBz := range res.Txs { tx, err := e.clientCtx.TxConfig.TxDecoder()(txBz) if err != nil { return nil, err } result = append(result, &tx) } return result, nil } // GetLogsByHeight returns all the logs from all the ethereum transactions in a block. func (e *EVMBackend) GetLogsByHeight(height *int64) ([][]*ethtypes.Log, error) { // NOTE: we query the state in case the tx result logs are not persisted after an upgrade. blockRes, err := e.clientCtx.Client.BlockResults(e.ctx, height) if err != nil { return nil, err } blockLogs := [][]*ethtypes.Log{} for _, txResult := range blockRes.TxsResults { logs, err := TxLogsFromEvents(txResult.Events) if err != nil { return nil, err } blockLogs = append(blockLogs, logs) } return blockLogs, nil } // GetLogs returns all the logs from all the ethereum transactions in a block. func (e *EVMBackend) GetLogs(hash common.Hash) ([][]*ethtypes.Log, error) { block, err := e.clientCtx.Client.BlockByHash(e.ctx, hash.Bytes()) if err != nil { return nil, err } return e.GetLogsByHeight(&block.Block.Header.Height) } func (e *EVMBackend) GetLogsByNumber(blockNum types.BlockNumber) ([][]*ethtypes.Log, error) { height := blockNum.Int64() switch blockNum { case types.EthLatestBlockNumber: currentBlockNumber, _ := e.BlockNumber() if currentBlockNumber > 0 { height = int64(currentBlockNumber) } case types.EthPendingBlockNumber: currentBlockNumber, _ := e.BlockNumber() if currentBlockNumber > 0 { height = int64(currentBlockNumber) } case types.EthEarliestBlockNumber: height = 1 default: if blockNum < 0 { return nil, errors.Errorf("incorrect block height: %d", height) } } return e.GetLogsByHeight(&height) } // BloomStatus returns the BloomBitsBlocks and the number of processed sections maintained // by the chain indexer. func (e *EVMBackend) BloomStatus() (uint64, uint64) { return 4096, 0 } // GetCoinbase is the address that staking rewards will be send to (alias for Etherbase). func (e *EVMBackend) GetCoinbase() (sdk.AccAddress, error) { node, err := e.clientCtx.GetNode() if err != nil { return nil, err } status, err := node.Status(e.ctx) if err != nil { return nil, err } req := &evmtypes.QueryValidatorAccountRequest{ ConsAddress: sdk.ConsAddress(status.ValidatorInfo.Address).String(), } res, err := e.queryClient.ValidatorAccount(e.ctx, req) if err != nil { return nil, err } address, _ := sdk.AccAddressFromBech32(res.AccountAddress) return address, nil } // GetTransactionByHash returns the Ethereum format transaction identified by Ethereum transaction hash func (e *EVMBackend) GetTransactionByHash(txHash common.Hash) (*types.RPCTransaction, error) { res, err := e.GetTxByEthHash(txHash) hexTx := txHash.Hex() if err != nil { // try to find tx in mempool txs, err := e.PendingTransactions() if err != nil { e.logger.Debug("tx not found", "hash", hexTx, "error", err.Error()) return nil, nil } for _, tx := range txs { msg, err := evmtypes.UnwrapEthereumMsg(tx) if err != nil { // not ethereum tx continue } if msg.Hash == hexTx { rpctx, err := types.NewTransactionFromMsg( msg, common.Hash{}, uint64(0), uint64(0), e.chainID, ) if err != nil { return nil, err } return rpctx, nil } } e.logger.Debug("tx not found", "hash", hexTx) return nil, nil } resBlock, err := e.clientCtx.Client.Block(e.ctx, &res.Height) if err != nil { e.logger.Debug("block not found", "height", res.Height, "error", err.Error()) return nil, nil } var txIndex uint64 msgs := e.GetEthereumMsgsFromTendermintBlock(resBlock) for i := range msgs { if msgs[i].Hash == hexTx { txIndex = uint64(i) break } } msg := msgs[txIndex] return types.NewTransactionFromMsg( msg, common.BytesToHash(resBlock.Block.Hash()), uint64(res.Height), txIndex, e.chainID, ) } // GetTxByEthHash uses `/tx_query` to find transaction by ethereum tx hash // TODO: Don't need to convert once hashing is fixed on Tendermint // https://github.com/tendermint/tendermint/issues/6539 func (e *EVMBackend) GetTxByEthHash(hash common.Hash) (*tmrpctypes.ResultTx, error) { query := fmt.Sprintf("%s.%s='%s'", evmtypes.TypeMsgEthereumTx, evmtypes.AttributeKeyEthereumTxHash, hash.Hex()) resTxs, err := e.clientCtx.Client.TxSearch(e.ctx, query, false, nil, nil, "") if err != nil { return nil, err } if len(resTxs.Txs) == 0 { return nil, errors.Errorf("ethereum tx not found for hash %s", hash.Hex()) } return resTxs.Txs[0], nil } func (e *EVMBackend) SendTransaction(args evmtypes.TransactionArgs) (common.Hash, error) { // Look up the wallet containing the requested signer _, err := e.clientCtx.Keyring.KeyByAddress(sdk.AccAddress(args.From.Bytes())) if err != nil { e.logger.Error("failed to find key in keyring", "address", args.From, "error", err.Error()) return common.Hash{}, fmt.Errorf("%s; %s", keystore.ErrNoMatch, err.Error()) } args, err = e.SetTxDefaults(args) if err != nil { return common.Hash{}, err } msg := args.ToTransaction() if err := msg.ValidateBasic(); err != nil { e.logger.Debug("tx failed basic validation", "error", err.Error()) return common.Hash{}, err } bn, err := e.BlockNumber() if err != nil { e.logger.Debug("failed to fetch latest block number", "error", err.Error()) return common.Hash{}, err } signer := ethtypes.MakeSigner(e.ChainConfig(), new(big.Int).SetUint64(uint64(bn))) // Sign transaction if err := msg.Sign(signer, e.clientCtx.Keyring); err != nil { e.logger.Debug("failed to sign tx", "error", err.Error()) return common.Hash{}, err } // Assemble transaction from fields builder, ok := e.clientCtx.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder) if !ok { e.logger.Error("clientCtx.TxConfig.NewTxBuilder returns unsupported builder", "error", err.Error()) } option, err := codectypes.NewAnyWithValue(&evmtypes.ExtensionOptionsEthereumTx{}) if err != nil { e.logger.Error("codectypes.NewAnyWithValue failed to pack an obvious value", "error", err.Error()) return common.Hash{}, err } builder.SetExtensionOptions(option) if err = builder.SetMsgs(msg); err != nil { e.logger.Error("builder.SetMsgs failed", "error", err.Error()) } // Query params to use the EVM denomination res, err := e.queryClient.QueryClient.Params(e.ctx, &evmtypes.QueryParamsRequest{}) if err != nil { e.logger.Error("failed to query evm params", "error", err.Error()) return common.Hash{}, err } txData, err := evmtypes.UnpackTxData(msg.Data) if err != nil { e.logger.Error("failed to unpack tx data", "error", err.Error()) return common.Hash{}, err } fees := sdk.Coins{sdk.NewCoin(res.Params.EvmDenom, sdk.NewIntFromBigInt(txData.Fee()))} builder.SetFeeAmount(fees) builder.SetGasLimit(msg.GetGas()) // Encode transaction by default Tx encoder txEncoder := e.clientCtx.TxConfig.TxEncoder() txBytes, err := txEncoder(builder.GetTx()) if err != nil { e.logger.Error("failed to encode eth tx using default encoder", "error", err.Error()) return common.Hash{}, err } txHash := msg.AsTransaction().Hash() // Broadcast transaction in sync mode (default) // NOTE: If error is encountered on the node, the broadcast will not return an error syncCtx := e.clientCtx.WithBroadcastMode(flags.BroadcastSync) rsp, err := syncCtx.BroadcastTx(txBytes) if err != nil || rsp.Code != 0 { if err == nil { err = errors.New(rsp.RawLog) } e.logger.Error("failed to broadcast tx", "error", err.Error()) return txHash, err } // Return transaction hash return txHash, nil } // EstimateGas returns an estimate of gas usage for the given smart contract call. func (e *EVMBackend) EstimateGas(args evmtypes.TransactionArgs, blockNrOptional *types.BlockNumber) (hexutil.Uint64, error) { blockNr := types.EthPendingBlockNumber if blockNrOptional != nil { blockNr = *blockNrOptional } bz, err := json.Marshal(&args) if err != nil { return 0, err } req := evmtypes.EthCallRequest{ Args: bz, GasCap: e.RPCGasCap(), } // From ContextWithHeight: if the provided height is 0, // it will return an empty context and the gRPC query will use // the latest block height for querying. res, err := e.queryClient.EstimateGas(types.ContextWithHeight(blockNr.Int64()), &req) if err != nil { return 0, err } return hexutil.Uint64(res.Gas), nil } // GetTransactionCount returns the number of transactions at the given address up to the given block number. func (e *EVMBackend) GetTransactionCount(address common.Address, blockNum types.BlockNumber) (*hexutil.Uint64, error) { // Get nonce (sequence) from account from := sdk.AccAddress(address.Bytes()) accRet := e.clientCtx.AccountRetriever err := accRet.EnsureExists(e.clientCtx, from) if err != nil { // account doesn't exist yet, return 0 n := hexutil.Uint64(0) return &n, nil } includePending := blockNum == types.EthPendingBlockNumber nonce, err := e.getAccountNonce(address, includePending, blockNum.Int64(), e.logger) if err != nil { return nil, err } n := hexutil.Uint64(nonce) return &n, nil } // RPCGasCap is the global gas cap for eth-call variants. func (e *EVMBackend) RPCGasCap() uint64 { return e.cfg.JSONRPC.GasCap } // RPCEVMTimeout is the global evm timeout for eth-call variants. func (e *EVMBackend) RPCEVMTimeout() time.Duration { return e.cfg.JSONRPC.EVMTimeout } // RPCGasCap is the global gas cap for eth-call variants. func (e *EVMBackend) RPCTxFeeCap() float64 { return e.cfg.JSONRPC.TxFeeCap } // RPCFilterCap is the limit for total number of filters that can be created func (e *EVMBackend) RPCFilterCap() int32 { return e.cfg.JSONRPC.FilterCap } // RPCFeeHistoryCap is the limit for total number of blocks that can be fetched func (e *EVMBackend) RPCFeeHistoryCap() int32 { return e.cfg.JSONRPC.FeeHistoryCap } // RPCMinGasPrice returns the minimum gas price for a transaction obtained from // the node config. If set value is 0, it will default to 20. func (e *EVMBackend) RPCMinGasPrice() int64 { evmParams, err := e.queryClient.Params(e.ctx, &evmtypes.QueryParamsRequest{}) if err != nil { return ethermint.DefaultGasPrice } minGasPrice := e.cfg.GetMinGasPrices() amt := minGasPrice.AmountOf(evmParams.Params.EvmDenom).TruncateInt64() if amt == 0 { return ethermint.DefaultGasPrice } return amt } // ChainConfig return the ethereum chain configuration func (e *EVMBackend) ChainConfig() *params.ChainConfig { params, err := e.queryClient.Params(e.ctx, &evmtypes.QueryParamsRequest{}) if err != nil { return nil } return params.Params.ChainConfig.EthereumConfig(e.chainID) } // SuggestGasTipCap returns the suggested tip cap func (e *EVMBackend) SuggestGasTipCap() (*big.Int, error) { out := new(big.Int).SetInt64(e.RPCMinGasPrice()) return out, nil } // BaseFee returns the base fee tracked by the Fee Market module. If the base fee is not enabled, // it returns the initial base fee amount. Return nil if London is not activated. func (e *EVMBackend) BaseFee(height int64) (*big.Int, error) { cfg := e.ChainConfig() if !cfg.IsLondon(new(big.Int).SetInt64(height)) { return nil, nil } // Checks the feemarket param NoBaseFee settings, return 0 if it is enabled. resParams, err := e.queryClient.FeeMarket.Params(e.ctx, &feemarkettypes.QueryParamsRequest{}) if err != nil { return nil, err } if resParams.Params.NoBaseFee { return big.NewInt(0), nil } blockRes, err := e.clientCtx.Client.BlockResults(e.ctx, &height) if err != nil { return nil, err } baseFee := types.BaseFeeFromEvents(blockRes.EndBlockEvents) if baseFee != nil { return baseFee, nil } // If we cannot find in events, we tried to get it from the state. // It will return feemarket.baseFee if london is activated but feemarket is not enable res, err := e.queryClient.FeeMarket.BaseFee(types.ContextWithHeight(height), &feemarkettypes.QueryBaseFeeRequest{}) if err == nil && res.BaseFee != nil { return res.BaseFee.BigInt(), nil } return nil, nil } // GetFilteredBlocks returns the block height list match the given bloom filters. func (e *EVMBackend) GetFilteredBlocks( from int64, to int64, filters [][]filters.BloomIV, filterAddresses bool, ) ([]int64, error) { matchedBlocks := make([]int64, 0) BLOCKS: for height := from; height <= to; height++ { if err := e.ctx.Err(); err != nil { e.logger.Error("EVMBackend context error", "err", err) return nil, err } h := height bloom, err := e.BlockBloom(&h) if err != nil { e.logger.Error("retrieve header failed", "blockHeight", height, "err", err) return nil, err } for i, filter := range filters { // filter the header bloom with the addresses if filterAddresses && i == 0 { if !checkMatches(bloom, filter) { continue BLOCKS } // the filter doesn't have any topics if len(filters) == 1 { matchedBlocks = append(matchedBlocks, height) continue BLOCKS } continue } // filter the bloom with topics if len(filter) > 0 && !checkMatches(bloom, filter) { continue BLOCKS } } matchedBlocks = append(matchedBlocks, height) } return matchedBlocks, nil } // GetEthereumMsgsFromTendermintBlock returns all real MsgEthereumTxs from a Tendermint block. // It also ensures consistency over the correct txs indexes across RPC endpoints func (e *EVMBackend) GetEthereumMsgsFromTendermintBlock(block *tmrpctypes.ResultBlock) []*evmtypes.MsgEthereumTx { var result []*evmtypes.MsgEthereumTx for _, tx := range block.Block.Txs { tx, err := e.clientCtx.TxConfig.TxDecoder()(tx) if err != nil { e.logger.Debug("failed to decode transaction in block", "height", block.Block.Height, "error", err.Error()) continue } for _, msg := range tx.GetMsgs() { ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) if !ok { continue } hash := ethMsg.AsTransaction().Hash() // check tx exists on EVM and has the correct block height ethTx, err := e.GetTxByEthHash(hash) if err != nil || ethTx.Height != block.Block.Height { e.logger.Debug("failed to query eth tx hash", "hash", hash.Hex()) continue } result = append(result, ethMsg) } } return result } // checkMatches revised the function from // https://github.com/ethereum/go-ethereum/blob/401354976bb44f0ad4455ca1e0b5c0dc31d9a5f5/core/types/bloom9.go#L88 func checkMatches(bloom ethtypes.Bloom, filter []filters.BloomIV) bool { for _, bloomIV := range filter { if bloomIV.V[0] == bloomIV.V[0]&bloom[bloomIV.I[0]] && bloomIV.V[1] == bloomIV.V[1]&bloom[bloomIV.I[1]] && bloomIV.V[2] == bloomIV.V[2]&bloom[bloomIV.I[2]] { return true } } return false }