forked from cerc-io/laconicd-deprecated
rpc: refactor rpc packages and backend to support cosmos namespace (#1070)
* rpc: refactor rpc packages and backend to support cosmos namespace * changelog * typo
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/server"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
"github.com/tharsis/ethermint/rpc/types"
|
||||
"github.com/tharsis/ethermint/server/config"
|
||||
ethermint "github.com/tharsis/ethermint/types"
|
||||
evmtypes "github.com/tharsis/ethermint/x/evm/types"
|
||||
)
|
||||
|
||||
// BackendI implements the Cosmos and EVM backend.
|
||||
type BackendI interface { // nolint: revive
|
||||
CosmosBackend
|
||||
EVMBackend
|
||||
}
|
||||
|
||||
// CosmosBackend implements the functionality shared within cosmos namespaces
|
||||
// as defined by Wallet Connect V2: https://docs.walletconnect.com/2.0/json-rpc/cosmos.
|
||||
// Implemented by Backend.
|
||||
type CosmosBackend interface {
|
||||
// TODO: define
|
||||
// GetAccounts()
|
||||
// SignDirect()
|
||||
// SignAmino()
|
||||
}
|
||||
|
||||
// EVMBackend implements the functionality shared within ethereum namespaces
|
||||
// as defined by EIP-1474: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1474.md
|
||||
// Implemented by Backend.
|
||||
type EVMBackend interface {
|
||||
// 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(baseFee *big.Int) (*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)
|
||||
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)
|
||||
GetTxByTxIndex(height int64, txIndex uint) (*tmrpctypes.ResultTx, error)
|
||||
EstimateGas(args evmtypes.TransactionArgs, blockNrOptional *types.BlockNumber) (hexutil.Uint64, error)
|
||||
BaseFee(height int64) (*big.Int, error)
|
||||
|
||||
// Fee API
|
||||
FeeHistory(blockCount rpc.DecimalOrHex, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*types.FeeHistoryResult, error)
|
||||
|
||||
// Filter API
|
||||
BloomStatus() (uint64, uint64)
|
||||
GetLogs(hash common.Hash) ([][]*ethtypes.Log, error)
|
||||
GetLogsByHeight(height *int64) ([][]*ethtypes.Log, error)
|
||||
ChainConfig() *params.ChainConfig
|
||||
SetTxDefaults(args evmtypes.TransactionArgs) (evmtypes.TransactionArgs, error)
|
||||
GetEthereumMsgsFromTendermintBlock(block *tmrpctypes.ResultBlock, blockRes *tmrpctypes.ResultBlockResults) []*evmtypes.MsgEthereumTx
|
||||
}
|
||||
|
||||
var _ BackendI = (*Backend)(nil)
|
||||
|
||||
// Backend implements the BackendI interface
|
||||
type Backend struct {
|
||||
ctx context.Context
|
||||
clientCtx client.Context
|
||||
queryClient *types.QueryClient // gRPC query client
|
||||
logger log.Logger
|
||||
chainID *big.Int
|
||||
cfg config.Config
|
||||
}
|
||||
|
||||
// NewBackend creates a new Backend instance for cosmos and ethereum namespaces
|
||||
func NewBackend(ctx *server.Context, logger log.Logger, clientCtx client.Context) *Backend {
|
||||
chainID, err := ethermint.ParseChainID(clientCtx.ChainID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
appConf := config.GetConfig(ctx.Viper)
|
||||
|
||||
return &Backend{
|
||||
ctx: context.Background(),
|
||||
clientCtx: clientCtx,
|
||||
queryClient: types.NewQueryClient(clientCtx),
|
||||
logger: logger.With("module", "backend"),
|
||||
chainID: chainID,
|
||||
cfg: appConf,
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,371 @@
|
||||
package backend
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sort"
|
||||
|
||||
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/tharsis/ethermint/rpc/types"
|
||||
evmtypes "github.com/tharsis/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
|
||||
}
|
||||
|
||||
// SetTxDefaults populates tx message with default values in case they are not
|
||||
// provided on the args
|
||||
func (b *Backend) SetTxDefaults(args evmtypes.TransactionArgs) (evmtypes.TransactionArgs, error) {
|
||||
if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) {
|
||||
return args, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
|
||||
}
|
||||
|
||||
head := b.CurrentHeader()
|
||||
if head == nil {
|
||||
return args, errors.New("latest header is nil")
|
||||
}
|
||||
|
||||
// If user specifies both maxPriorityfee and maxFee, then we do not
|
||||
// need to consult the chain for defaults. It's definitely a London tx.
|
||||
if args.MaxPriorityFeePerGas == nil || args.MaxFeePerGas == nil {
|
||||
// In this clause, user left some fields unspecified.
|
||||
if head.BaseFee != nil && args.GasPrice == nil {
|
||||
if args.MaxPriorityFeePerGas == nil {
|
||||
tip, err := b.SuggestGasTipCap(head.BaseFee)
|
||||
if err != nil {
|
||||
return args, err
|
||||
}
|
||||
args.MaxPriorityFeePerGas = (*hexutil.Big)(tip)
|
||||
}
|
||||
|
||||
if args.MaxFeePerGas == nil {
|
||||
gasFeeCap := new(big.Int).Add(
|
||||
(*big.Int)(args.MaxPriorityFeePerGas),
|
||||
new(big.Int).Mul(head.BaseFee, big.NewInt(2)),
|
||||
)
|
||||
args.MaxFeePerGas = (*hexutil.Big)(gasFeeCap)
|
||||
}
|
||||
|
||||
if args.MaxFeePerGas.ToInt().Cmp(args.MaxPriorityFeePerGas.ToInt()) < 0 {
|
||||
return args, fmt.Errorf("maxFeePerGas (%v) < maxPriorityFeePerGas (%v)", args.MaxFeePerGas, args.MaxPriorityFeePerGas)
|
||||
}
|
||||
|
||||
} else {
|
||||
if args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil {
|
||||
return args, errors.New("maxFeePerGas or maxPriorityFeePerGas specified but london is not active yet")
|
||||
}
|
||||
|
||||
if args.GasPrice == nil {
|
||||
price, err := b.SuggestGasTipCap(head.BaseFee)
|
||||
if err != nil {
|
||||
return args, err
|
||||
}
|
||||
if head.BaseFee != nil {
|
||||
// The legacy tx gas price suggestion should not add 2x base fee
|
||||
// because all fees are consumed, so it would result in a spiral
|
||||
// upwards.
|
||||
price.Add(price, head.BaseFee)
|
||||
}
|
||||
args.GasPrice = (*hexutil.Big)(price)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Both maxPriorityfee and maxFee set by caller. Sanity-check their internal relation
|
||||
if args.MaxFeePerGas.ToInt().Cmp(args.MaxPriorityFeePerGas.ToInt()) < 0 {
|
||||
return args, fmt.Errorf("maxFeePerGas (%v) < maxPriorityFeePerGas (%v)", args.MaxFeePerGas, args.MaxPriorityFeePerGas)
|
||||
}
|
||||
}
|
||||
|
||||
if args.Value == nil {
|
||||
args.Value = new(hexutil.Big)
|
||||
}
|
||||
if args.Nonce == nil {
|
||||
// get the nonce from the account retriever
|
||||
// ignore error in case tge account doesn't exist yet
|
||||
nonce, _ := b.getAccountNonce(*args.From, true, 0, b.logger)
|
||||
args.Nonce = (*hexutil.Uint64)(&nonce)
|
||||
}
|
||||
|
||||
if args.Data != nil && args.Input != nil && !bytes.Equal(*args.Data, *args.Input) {
|
||||
return args, errors.New("both 'data' and 'input' are set and not equal. Please use 'input' to pass transaction call data")
|
||||
}
|
||||
|
||||
if args.To == nil {
|
||||
// Contract creation
|
||||
var input []byte
|
||||
if args.Data != nil {
|
||||
input = *args.Data
|
||||
} else if args.Input != nil {
|
||||
input = *args.Input
|
||||
}
|
||||
|
||||
if len(input) == 0 {
|
||||
return args, errors.New("contract creation without any data provided")
|
||||
}
|
||||
}
|
||||
|
||||
if args.Gas == nil {
|
||||
// For backwards-compatibility reason, we try both input and data
|
||||
// but input is preferred.
|
||||
input := args.Input
|
||||
if input == nil {
|
||||
input = args.Data
|
||||
}
|
||||
|
||||
callArgs := evmtypes.TransactionArgs{
|
||||
From: args.From,
|
||||
To: args.To,
|
||||
Gas: args.Gas,
|
||||
GasPrice: args.GasPrice,
|
||||
MaxFeePerGas: args.MaxFeePerGas,
|
||||
MaxPriorityFeePerGas: args.MaxPriorityFeePerGas,
|
||||
Value: args.Value,
|
||||
Data: input,
|
||||
AccessList: args.AccessList,
|
||||
}
|
||||
|
||||
blockNr := types.NewBlockNumber(big.NewInt(0))
|
||||
estimated, err := b.EstimateGas(callArgs, &blockNr)
|
||||
if err != nil {
|
||||
return args, err
|
||||
}
|
||||
args.Gas = &estimated
|
||||
b.logger.Debug("estimate gas usage automatically", "gas", args.Gas)
|
||||
}
|
||||
|
||||
if args.ChainID == nil {
|
||||
args.ChainID = (*hexutil.Big)(b.chainID)
|
||||
}
|
||||
|
||||
return args, nil
|
||||
}
|
||||
|
||||
// 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(blockHeight)
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user