package backend import ( "bytes" "encoding/json" "fmt" "math/big" "sort" "strings" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" ethtypes "github.com/ethereum/go-ethereum/core/types" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/libs/log" tmrpctypes "github.com/tendermint/tendermint/rpc/core/types" "github.com/evmos/ethermint/rpc/types" evmtypes "github.com/evmos/ethermint/x/evm/types" ) // ExceedBlockGasLimitError defines the error message when tx execution exceeds the block gas limit. // The tx fee is deducted in ante handler, so it shouldn't be ignored in JSON-RPC API. const ExceedBlockGasLimitError = "out of gas in location: block gas meter; gasWanted:" type txGasAndReward struct { gasUsed uint64 reward *big.Int } type sortGasAndReward []txGasAndReward func (s sortGasAndReward) Len() int { return len(s) } func (s sortGasAndReward) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s sortGasAndReward) Less(i, j int) bool { return s[i].reward.Cmp(s[j].reward) < 0 } // getAccountNonce returns the account nonce for the given account address. // If the pending value is true, it will iterate over the mempool (pending) // txs in order to compute and return the pending tx sequence. // Todo: include the ability to specify a blockNumber func (b *Backend) getAccountNonce(accAddr common.Address, pending bool, height int64, logger log.Logger) (uint64, error) { queryClient := authtypes.NewQueryClient(b.clientCtx) res, err := queryClient.Account(types.ContextWithHeight(height), &authtypes.QueryAccountRequest{Address: sdk.AccAddress(accAddr.Bytes()).String()}) if err != nil { return 0, err } var acc authtypes.AccountI if err := b.clientCtx.InterfaceRegistry.UnpackAny(res.Account, &acc); err != nil { return 0, err } nonce := acc.GetSequence() if !pending { return nonce, nil } // the account retriever doesn't include the uncommitted transactions on the nonce so we need to // to manually add them. pendingTxs, err := b.PendingTransactions() if err != nil { logger.Error("failed to fetch pending transactions", "error", err.Error()) return nonce, nil } // add the uncommitted txs to the nonce counter // only supports `MsgEthereumTx` style tx for _, tx := range pendingTxs { for _, msg := range (*tx).GetMsgs() { ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) if !ok { // not ethereum tx break } sender, err := ethMsg.GetSender(b.chainID) if err != nil { continue } if sender == accAddr { nonce++ } } } return nonce, nil } // output: targetOneFeeHistory func (b *Backend) processBlock( tendermintBlock *tmrpctypes.ResultBlock, ethBlock *map[string]interface{}, rewardPercentiles []float64, tendermintBlockResult *tmrpctypes.ResultBlockResults, targetOneFeeHistory *types.OneFeeHistory, ) error { blockHeight := tendermintBlock.Block.Height blockBaseFee, err := b.BaseFee(tendermintBlockResult) if err != nil { return err } // set basefee targetOneFeeHistory.BaseFee = blockBaseFee // set gas used ratio gasLimitUint64, ok := (*ethBlock)["gasLimit"].(hexutil.Uint64) if !ok { return fmt.Errorf("invalid gas limit type: %T", (*ethBlock)["gasLimit"]) } gasUsedBig, ok := (*ethBlock)["gasUsed"].(*hexutil.Big) if !ok { return fmt.Errorf("invalid gas used type: %T", (*ethBlock)["gasUsed"]) } gasusedfloat, _ := new(big.Float).SetInt(gasUsedBig.ToInt()).Float64() if gasLimitUint64 <= 0 { return fmt.Errorf("gasLimit of block height %d should be bigger than 0 , current gaslimit %d", blockHeight, gasLimitUint64) } gasUsedRatio := gasusedfloat / float64(gasLimitUint64) blockGasUsed := gasusedfloat targetOneFeeHistory.GasUsedRatio = gasUsedRatio rewardCount := len(rewardPercentiles) targetOneFeeHistory.Reward = make([]*big.Int, rewardCount) for i := 0; i < rewardCount; i++ { targetOneFeeHistory.Reward[i] = big.NewInt(0) } // check tendermintTxs tendermintTxs := tendermintBlock.Block.Txs tendermintTxResults := tendermintBlockResult.TxsResults tendermintTxCount := len(tendermintTxs) var sorter sortGasAndReward for i := 0; i < tendermintTxCount; i++ { eachTendermintTx := tendermintTxs[i] eachTendermintTxResult := tendermintTxResults[i] tx, err := b.clientCtx.TxConfig.TxDecoder()(eachTendermintTx) if err != nil { b.logger.Debug("failed to decode transaction in block", "height", blockHeight, "error", err.Error()) continue } txGasUsed := uint64(eachTendermintTxResult.GasUsed) for _, msg := range tx.GetMsgs() { ethMsg, ok := msg.(*evmtypes.MsgEthereumTx) if !ok { continue } tx := ethMsg.AsTransaction() reward := tx.EffectiveGasTipValue(blockBaseFee) if reward == nil { reward = big.NewInt(0) } sorter = append(sorter, txGasAndReward{gasUsed: txGasUsed, reward: reward}) } } // return an all zero row if there are no transactions to gather data from ethTxCount := len(sorter) if ethTxCount == 0 { return nil } sort.Sort(sorter) var txIndex int sumGasUsed := sorter[0].gasUsed for i, p := range rewardPercentiles { thresholdGasUsed := uint64(blockGasUsed * p / 100) for sumGasUsed < thresholdGasUsed && txIndex < ethTxCount-1 { txIndex++ sumGasUsed += sorter[txIndex].gasUsed } targetOneFeeHistory.Reward[i] = sorter[txIndex].reward } return nil } // AllTxLogsFromEvents parses all ethereum logs from cosmos events func AllTxLogsFromEvents(events []abci.Event) ([][]*ethtypes.Log, error) { allLogs := make([][]*ethtypes.Log, 0, 4) for _, event := range events { if event.Type != evmtypes.EventTypeTxLog { continue } logs, err := ParseTxLogsFromEvent(event) if err != nil { return nil, err } allLogs = append(allLogs, logs) } return allLogs, nil } // TxLogsFromEvents parses ethereum logs from cosmos events for specific msg index func TxLogsFromEvents(events []abci.Event, msgIndex int) ([]*ethtypes.Log, error) { for _, event := range events { if event.Type != evmtypes.EventTypeTxLog { continue } if msgIndex > 0 { // not the eth tx we want msgIndex-- continue } return ParseTxLogsFromEvent(event) } return nil, fmt.Errorf("eth tx logs not found for message index %d", msgIndex) } // ParseTxLogsFromEvent parse tx logs from one event func ParseTxLogsFromEvent(event abci.Event) ([]*ethtypes.Log, error) { logs := make([]*evmtypes.Log, 0, len(event.Attributes)) for _, attr := range event.Attributes { if !bytes.Equal(attr.Key, []byte(evmtypes.AttributeKeyTxLog)) { continue } var log evmtypes.Log if err := json.Unmarshal(attr.Value, &log); err != nil { return nil, err } logs = append(logs, &log) } return evmtypes.LogsToEthereum(logs), nil } // TxExceedBlockGasLimit returns true if the tx exceeds block gas limit. func TxExceedBlockGasLimit(res *abci.ResponseDeliverTx) bool { return strings.Contains(res.Log, ExceedBlockGasLimitError) } // TxSuccessOrExceedsBlockGasLimit returnsrue if the transaction was successful // or if it failed with an ExceedBlockGasLimit error func TxSuccessOrExceedsBlockGasLimit(res *abci.ResponseDeliverTx) bool { return res.Code == 0 || TxExceedBlockGasLimit(res) } // ShouldIgnoreGasUsed returns true if the gasUsed in result should be ignored // workaround for issue: https://github.com/cosmos/cosmos-sdk/issues/10832 func ShouldIgnoreGasUsed(res *abci.ResponseDeliverTx) bool { return res.GetCode() == 11 && strings.Contains(res.GetLog(), "no block gas left to run tx: out of gas") } // GetLogsFromBlockResults returns the list of event logs from the tendermint block result response func GetLogsFromBlockResults(blockRes *tmrpctypes.ResultBlockResults) ([][]*ethtypes.Log, error) { blockLogs := [][]*ethtypes.Log{} for _, txResult := range blockRes.TxsResults { logs, err := AllTxLogsFromEvents(txResult.Events) if err != nil { return nil, err } blockLogs = append(blockLogs, logs...) } return blockLogs, nil }