77ed4aa754
* Store eth tx index separately Closes: #1075 Solution: - run a optional indexer service - adapt the json-rpc to the more efficient query changelog changelog fix lint fix backward compatibility fix lint timeout better strconv fix linter fix package name add cli command to index old tx fix for loop indexer cmd don't have access to local rpc workaround exceed block gas limit situation add unit tests for indexer refactor polish the indexer module Update server/config/toml.go Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> improve comments share code between GetTxByEthHash and GetTxByIndex fix unit test Update server/indexer.go Co-authored-by: Freddy Caceres <facs95@gmail.com> * Apply suggestions from code review * test enable-indexer in integration test * fix go lint * address review suggestions * fix linter * address review suggestions - test indexer in backend unit test - add comments * fix build * fix test * service name Co-authored-by: Freddy Caceres <facs95@gmail.com> Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>
266 lines
7.2 KiB
Go
266 lines
7.2 KiB
Go
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"
|
|
)
|
|
|
|
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
|
|
}
|
|
|
|
// 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
|
|
}
|