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>
262 lines
9.0 KiB
Go
262 lines
9.0 KiB
Go
package types
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"math/big"
|
|
"strings"
|
|
|
|
abci "github.com/tendermint/tendermint/abci/types"
|
|
tmtypes "github.com/tendermint/tendermint/types"
|
|
|
|
"github.com/cosmos/cosmos-sdk/client"
|
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
|
|
|
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
|
feemarkettypes "github.com/evmos/ethermint/x/feemarket/types"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
"github.com/ethereum/go-ethereum/common/math"
|
|
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
|
"github.com/ethereum/go-ethereum/params"
|
|
)
|
|
|
|
// 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:"
|
|
|
|
// RawTxToEthTx returns a evm MsgEthereum transaction from raw tx bytes.
|
|
func RawTxToEthTx(clientCtx client.Context, txBz tmtypes.Tx) ([]*evmtypes.MsgEthereumTx, error) {
|
|
tx, err := clientCtx.TxConfig.TxDecoder()(txBz)
|
|
if err != nil {
|
|
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
|
|
}
|
|
|
|
ethTxs := make([]*evmtypes.MsgEthereumTx, len(tx.GetMsgs()))
|
|
for i, msg := range tx.GetMsgs() {
|
|
ethTx, ok := msg.(*evmtypes.MsgEthereumTx)
|
|
if !ok {
|
|
return nil, fmt.Errorf("invalid message type %T, expected %T", msg, &evmtypes.MsgEthereumTx{})
|
|
}
|
|
ethTx.Hash = ethTx.AsTransaction().Hash().Hex()
|
|
ethTxs[i] = ethTx
|
|
}
|
|
return ethTxs, nil
|
|
}
|
|
|
|
// EthHeaderFromTendermint is an util function that returns an Ethereum Header
|
|
// from a tendermint Header.
|
|
func EthHeaderFromTendermint(header tmtypes.Header, bloom ethtypes.Bloom, baseFee *big.Int) *ethtypes.Header {
|
|
txHash := ethtypes.EmptyRootHash
|
|
if len(header.DataHash) == 0 {
|
|
txHash = common.BytesToHash(header.DataHash)
|
|
}
|
|
|
|
return ðtypes.Header{
|
|
ParentHash: common.BytesToHash(header.LastBlockID.Hash.Bytes()),
|
|
UncleHash: ethtypes.EmptyUncleHash,
|
|
Coinbase: common.BytesToAddress(header.ProposerAddress),
|
|
Root: common.BytesToHash(header.AppHash),
|
|
TxHash: txHash,
|
|
ReceiptHash: ethtypes.EmptyRootHash,
|
|
Bloom: bloom,
|
|
Difficulty: big.NewInt(0),
|
|
Number: big.NewInt(header.Height),
|
|
GasLimit: 0,
|
|
GasUsed: 0,
|
|
Time: uint64(header.Time.UTC().Unix()),
|
|
Extra: []byte{},
|
|
MixDigest: common.Hash{},
|
|
Nonce: ethtypes.BlockNonce{},
|
|
BaseFee: baseFee,
|
|
}
|
|
}
|
|
|
|
// BlockMaxGasFromConsensusParams returns the gas limit for the current block from the chain consensus params.
|
|
func BlockMaxGasFromConsensusParams(goCtx context.Context, clientCtx client.Context, blockHeight int64) (int64, error) {
|
|
resConsParams, err := clientCtx.Client.ConsensusParams(goCtx, &blockHeight)
|
|
if err != nil {
|
|
return int64(^uint32(0)), err
|
|
}
|
|
|
|
gasLimit := resConsParams.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))
|
|
}
|
|
|
|
return gasLimit, nil
|
|
}
|
|
|
|
// FormatBlock creates an ethereum block from a tendermint header and ethereum-formatted
|
|
// transactions.
|
|
func FormatBlock(
|
|
header tmtypes.Header, size int, gasLimit int64,
|
|
gasUsed *big.Int, transactions []interface{}, bloom ethtypes.Bloom,
|
|
validatorAddr common.Address, baseFee *big.Int,
|
|
) map[string]interface{} {
|
|
var transactionsRoot common.Hash
|
|
if len(transactions) == 0 {
|
|
transactionsRoot = ethtypes.EmptyRootHash
|
|
} else {
|
|
transactionsRoot = common.BytesToHash(header.DataHash)
|
|
}
|
|
|
|
result := map[string]interface{}{
|
|
"number": hexutil.Uint64(header.Height),
|
|
"hash": hexutil.Bytes(header.Hash()),
|
|
"parentHash": common.BytesToHash(header.LastBlockID.Hash.Bytes()),
|
|
"nonce": ethtypes.BlockNonce{}, // PoW specific
|
|
"sha3Uncles": ethtypes.EmptyUncleHash, // No uncles in Tendermint
|
|
"logsBloom": bloom,
|
|
"stateRoot": hexutil.Bytes(header.AppHash),
|
|
"miner": validatorAddr,
|
|
"mixHash": common.Hash{},
|
|
"difficulty": (*hexutil.Big)(big.NewInt(0)),
|
|
"extraData": "0x",
|
|
"size": hexutil.Uint64(size),
|
|
"gasLimit": hexutil.Uint64(gasLimit), // Static gas limit
|
|
"gasUsed": (*hexutil.Big)(gasUsed),
|
|
"timestamp": hexutil.Uint64(header.Time.Unix()),
|
|
"transactionsRoot": transactionsRoot,
|
|
"receiptsRoot": ethtypes.EmptyRootHash,
|
|
|
|
"uncles": []common.Hash{},
|
|
"transactions": transactions,
|
|
"totalDifficulty": (*hexutil.Big)(big.NewInt(0)),
|
|
}
|
|
|
|
if baseFee != nil {
|
|
result["baseFeePerGas"] = (*hexutil.Big)(baseFee)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// NewTransactionFromMsg returns a transaction that will serialize to the RPC
|
|
// representation, with the given location metadata set (if available).
|
|
func NewTransactionFromMsg(
|
|
msg *evmtypes.MsgEthereumTx,
|
|
blockHash common.Hash,
|
|
blockNumber, index uint64,
|
|
baseFee *big.Int,
|
|
) (*RPCTransaction, error) {
|
|
tx := msg.AsTransaction()
|
|
return NewRPCTransaction(tx, blockHash, blockNumber, index, baseFee)
|
|
}
|
|
|
|
// NewTransactionFromData returns a transaction that will serialize to the RPC
|
|
// representation, with the given location metadata set (if available).
|
|
func NewRPCTransaction(
|
|
tx *ethtypes.Transaction, blockHash common.Hash, blockNumber, index uint64, baseFee *big.Int,
|
|
) (*RPCTransaction, error) {
|
|
// Determine the signer. For replay-protected transactions, use the most permissive
|
|
// signer, because we assume that signers are backwards-compatible with old
|
|
// transactions. For non-protected transactions, the homestead signer signer is used
|
|
// because the return value of ChainId is zero for those transactions.
|
|
var signer ethtypes.Signer
|
|
if tx.Protected() {
|
|
signer = ethtypes.LatestSignerForChainID(tx.ChainId())
|
|
} else {
|
|
signer = ethtypes.HomesteadSigner{}
|
|
}
|
|
from, _ := ethtypes.Sender(signer, tx)
|
|
v, r, s := tx.RawSignatureValues()
|
|
result := &RPCTransaction{
|
|
Type: hexutil.Uint64(tx.Type()),
|
|
From: from,
|
|
Gas: hexutil.Uint64(tx.Gas()),
|
|
GasPrice: (*hexutil.Big)(tx.GasPrice()),
|
|
Hash: tx.Hash(),
|
|
Input: hexutil.Bytes(tx.Data()),
|
|
Nonce: hexutil.Uint64(tx.Nonce()),
|
|
To: tx.To(),
|
|
Value: (*hexutil.Big)(tx.Value()),
|
|
V: (*hexutil.Big)(v),
|
|
R: (*hexutil.Big)(r),
|
|
S: (*hexutil.Big)(s),
|
|
}
|
|
if blockHash != (common.Hash{}) {
|
|
result.BlockHash = &blockHash
|
|
result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber))
|
|
result.TransactionIndex = (*hexutil.Uint64)(&index)
|
|
}
|
|
switch tx.Type() {
|
|
case ethtypes.AccessListTxType:
|
|
al := tx.AccessList()
|
|
result.Accesses = &al
|
|
result.ChainID = (*hexutil.Big)(tx.ChainId())
|
|
case ethtypes.DynamicFeeTxType:
|
|
al := tx.AccessList()
|
|
result.Accesses = &al
|
|
result.ChainID = (*hexutil.Big)(tx.ChainId())
|
|
result.GasFeeCap = (*hexutil.Big)(tx.GasFeeCap())
|
|
result.GasTipCap = (*hexutil.Big)(tx.GasTipCap())
|
|
// if the transaction has been mined, compute the effective gas price
|
|
if baseFee != nil && blockHash != (common.Hash{}) {
|
|
// price = min(tip, gasFeeCap - baseFee) + baseFee
|
|
price := math.BigMin(new(big.Int).Add(tx.GasTipCap(), baseFee), tx.GasFeeCap())
|
|
result.GasPrice = (*hexutil.Big)(price)
|
|
} else {
|
|
result.GasPrice = (*hexutil.Big)(tx.GasFeeCap())
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// BaseFeeFromEvents parses the feemarket basefee from cosmos events
|
|
func BaseFeeFromEvents(events []abci.Event) *big.Int {
|
|
for _, event := range events {
|
|
if event.Type != feemarkettypes.EventTypeFeeMarket {
|
|
continue
|
|
}
|
|
|
|
for _, attr := range event.Attributes {
|
|
if bytes.Equal(attr.Key, []byte(feemarkettypes.AttributeKeyBaseFee)) {
|
|
result, success := new(big.Int).SetString(string(attr.Value), 10)
|
|
if success {
|
|
return result
|
|
}
|
|
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CheckTxFee is an internal function used to check whether the fee of
|
|
// the given transaction is _reasonable_(under the cap).
|
|
func CheckTxFee(gasPrice *big.Int, gas uint64, cap float64) error {
|
|
// Short circuit if there is no cap for transaction fee at all.
|
|
if cap == 0 {
|
|
return nil
|
|
}
|
|
totalfee := new(big.Float).SetInt(new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(gas)))
|
|
// 1 photon in 10^18 aphoton
|
|
oneToken := new(big.Float).SetInt(big.NewInt(params.Ether))
|
|
// quo = rounded(x/y)
|
|
feeEth := new(big.Float).Quo(totalfee, oneToken)
|
|
// no need to check error from parsing
|
|
feeFloat, _ := feeEth.Float64()
|
|
if feeFloat > cap {
|
|
return fmt.Errorf("tx fee (%.2f ether) exceeds the configured cap (%.2f ether)", feeFloat, cap)
|
|
}
|
|
return 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)
|
|
}
|