laconicd-deprecated/rpc/types/utils.go
yihuang 77ed4aa754
feat!: Store eth tx index separately (#1121)
* 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>
2022-08-11 22:49:05 +02:00

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 &ethtypes.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)
}