chore(rpc): restructure JSON-RPC APIs (#1218)
* move non api methods from eth/api.go to evm_backend: ClientCtx, QueryClient, Ctx, getBlockNumber, getTransactionByBlockAndIndex, doCall * organize eth/api.go into sections and move backend logic to dedicated files * remove unnecesary comment * move resend to the backend * refractor eth api * refractor debug namespace * refactor miner namespace * refactor personal namespace * update transactionReceipt from upstream * update getBlockByNumber from upstream * update getBalance from upstream * update getProof from upstream * update getBalance from upstream * fix linter * remove duplicated import * remove duplicated import * fix backend tests * fix lint * fix duplicated imports * fix linter * reorganize blocks * backend folder refractor * remove unnecessary file * remove duplicate import Co-authored-by: Freddy Caceres <facs95@gmail.com>
This commit is contained in:
parent
d1543ce678
commit
e70d8fcb56
10
rpc/apis.go
10
rpc/apis.go
@ -19,7 +19,6 @@ import (
|
|||||||
"github.com/evmos/ethermint/rpc/namespaces/ethereum/personal"
|
"github.com/evmos/ethermint/rpc/namespaces/ethereum/personal"
|
||||||
"github.com/evmos/ethermint/rpc/namespaces/ethereum/txpool"
|
"github.com/evmos/ethermint/rpc/namespaces/ethereum/txpool"
|
||||||
"github.com/evmos/ethermint/rpc/namespaces/ethereum/web3"
|
"github.com/evmos/ethermint/rpc/namespaces/ethereum/web3"
|
||||||
"github.com/evmos/ethermint/rpc/types"
|
|
||||||
|
|
||||||
rpcclient "github.com/tendermint/tendermint/rpc/jsonrpc/client"
|
rpcclient "github.com/tendermint/tendermint/rpc/jsonrpc/client"
|
||||||
)
|
)
|
||||||
@ -57,13 +56,12 @@ var apiCreators map[string]APICreator
|
|||||||
func init() {
|
func init() {
|
||||||
apiCreators = map[string]APICreator{
|
apiCreators = map[string]APICreator{
|
||||||
EthNamespace: func(ctx *server.Context, clientCtx client.Context, tmWSClient *rpcclient.WSClient, allowUnprotectedTxs bool) []rpc.API {
|
EthNamespace: func(ctx *server.Context, clientCtx client.Context, tmWSClient *rpcclient.WSClient, allowUnprotectedTxs bool) []rpc.API {
|
||||||
nonceLock := new(types.AddrLocker)
|
|
||||||
evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs)
|
evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs)
|
||||||
return []rpc.API{
|
return []rpc.API{
|
||||||
{
|
{
|
||||||
Namespace: EthNamespace,
|
Namespace: EthNamespace,
|
||||||
Version: apiVersion,
|
Version: apiVersion,
|
||||||
Service: eth.NewPublicAPI(ctx.Logger, clientCtx, evmBackend, nonceLock),
|
Service: eth.NewPublicAPI(ctx.Logger, evmBackend),
|
||||||
Public: true,
|
Public: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -100,7 +98,7 @@ func init() {
|
|||||||
{
|
{
|
||||||
Namespace: PersonalNamespace,
|
Namespace: PersonalNamespace,
|
||||||
Version: apiVersion,
|
Version: apiVersion,
|
||||||
Service: personal.NewAPI(ctx.Logger, clientCtx, evmBackend),
|
Service: personal.NewAPI(ctx.Logger, evmBackend),
|
||||||
Public: false,
|
Public: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -121,7 +119,7 @@ func init() {
|
|||||||
{
|
{
|
||||||
Namespace: DebugNamespace,
|
Namespace: DebugNamespace,
|
||||||
Version: apiVersion,
|
Version: apiVersion,
|
||||||
Service: debug.NewAPI(ctx, evmBackend, clientCtx),
|
Service: debug.NewAPI(ctx, evmBackend),
|
||||||
Public: true,
|
Public: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -132,7 +130,7 @@ func init() {
|
|||||||
{
|
{
|
||||||
Namespace: MinerNamespace,
|
Namespace: MinerNamespace,
|
||||||
Version: apiVersion,
|
Version: apiVersion,
|
||||||
Service: miner.NewPrivateAPI(ctx, clientCtx, evmBackend),
|
Service: miner.NewPrivateAPI(ctx, evmBackend),
|
||||||
Public: false,
|
Public: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
207
rpc/backend/account_info.go
Normal file
207
rpc/backend/account_info.go
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
package backend
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
sdkmath "cosmossdk.io/math"
|
||||||
|
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"
|
||||||
|
rpctypes "github.com/evmos/ethermint/rpc/types"
|
||||||
|
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetCode returns the contract code at the given address and block number.
|
||||||
|
func (b *Backend) GetCode(address common.Address, blockNrOrHash rpctypes.BlockNumberOrHash) (hexutil.Bytes, error) {
|
||||||
|
blockNum, err := b.GetBlockNumber(blockNrOrHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &evmtypes.QueryCodeRequest{
|
||||||
|
Address: address.String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := b.queryClient.Code(rpctypes.ContextWithHeight(blockNum.Int64()), req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.Code, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProof returns an account object with proof and any storage proofs
|
||||||
|
func (b *Backend) GetProof(address common.Address, storageKeys []string, blockNrOrHash rpctypes.BlockNumberOrHash) (*rpctypes.AccountResult, error) {
|
||||||
|
blockNum, err := b.GetBlockNumber(blockNrOrHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
height := blockNum.Int64()
|
||||||
|
_, err = b.GetTendermintBlockByNumber(blockNum)
|
||||||
|
if err != nil {
|
||||||
|
// Get 'latest' proof if query is in the future
|
||||||
|
// this imitates geth behavior
|
||||||
|
height = 0
|
||||||
|
}
|
||||||
|
ctx := rpctypes.ContextWithHeight(height)
|
||||||
|
|
||||||
|
// if the height is equal to zero, meaning the query condition of the block is either "pending" or "latest"
|
||||||
|
if height == 0 {
|
||||||
|
bn, err := b.BlockNumber()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if bn > math.MaxInt64 {
|
||||||
|
return nil, fmt.Errorf("not able to query block number greater than MaxInt64")
|
||||||
|
}
|
||||||
|
|
||||||
|
height = int64(bn)
|
||||||
|
}
|
||||||
|
|
||||||
|
clientCtx := b.clientCtx.WithHeight(height)
|
||||||
|
|
||||||
|
// query storage proofs
|
||||||
|
storageProofs := make([]rpctypes.StorageResult, len(storageKeys))
|
||||||
|
|
||||||
|
for i, key := range storageKeys {
|
||||||
|
hexKey := common.HexToHash(key)
|
||||||
|
valueBz, proof, err := b.queryClient.GetProof(clientCtx, evmtypes.StoreKey, evmtypes.StateKey(address, hexKey.Bytes()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for proof
|
||||||
|
var proofStr string
|
||||||
|
if proof != nil {
|
||||||
|
proofStr = proof.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
storageProofs[i] = rpctypes.StorageResult{
|
||||||
|
Key: key,
|
||||||
|
Value: (*hexutil.Big)(new(big.Int).SetBytes(valueBz)),
|
||||||
|
Proof: []string{proofStr},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// query EVM account
|
||||||
|
req := &evmtypes.QueryAccountRequest{
|
||||||
|
Address: address.String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := b.queryClient.Account(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// query account proofs
|
||||||
|
accountKey := authtypes.AddressStoreKey(sdk.AccAddress(address.Bytes()))
|
||||||
|
_, proof, err := b.queryClient.GetProof(clientCtx, authtypes.StoreKey, accountKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for proof
|
||||||
|
var accProofStr string
|
||||||
|
if proof != nil {
|
||||||
|
accProofStr = proof.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
balance, ok := sdkmath.NewIntFromString(res.Balance)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("invalid balance")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &rpctypes.AccountResult{
|
||||||
|
Address: address,
|
||||||
|
AccountProof: []string{accProofStr},
|
||||||
|
Balance: (*hexutil.Big)(balance.BigInt()),
|
||||||
|
CodeHash: common.HexToHash(res.CodeHash),
|
||||||
|
Nonce: hexutil.Uint64(res.Nonce),
|
||||||
|
StorageHash: common.Hash{}, // NOTE: Ethermint doesn't have a storage hash. TODO: implement?
|
||||||
|
StorageProof: storageProofs,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStorageAt returns the contract storage at the given address, block number, and key.
|
||||||
|
func (b *Backend) GetStorageAt(address common.Address, key string, blockNrOrHash rpctypes.BlockNumberOrHash) (hexutil.Bytes, error) {
|
||||||
|
blockNum, err := b.GetBlockNumber(blockNrOrHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &evmtypes.QueryStorageRequest{
|
||||||
|
Address: address.String(),
|
||||||
|
Key: key,
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := b.queryClient.Storage(rpctypes.ContextWithHeight(blockNum.Int64()), req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
value := common.HexToHash(res.Value)
|
||||||
|
return value.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBalance returns the provided account's balance up to the provided block number.
|
||||||
|
func (b *Backend) GetBalance(address common.Address, blockNrOrHash rpctypes.BlockNumberOrHash) (*hexutil.Big, error) {
|
||||||
|
blockNum, err := b.GetBlockNumber(blockNrOrHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &evmtypes.QueryBalanceRequest{
|
||||||
|
Address: address.String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = b.GetTendermintBlockByNumber(blockNum)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := b.queryClient.Balance(rpctypes.ContextWithHeight(blockNum.Int64()), req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
val, ok := sdkmath.NewIntFromString(res.Balance)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("invalid balance")
|
||||||
|
}
|
||||||
|
|
||||||
|
// balance can only be negative in case of pruned node
|
||||||
|
if val.IsNegative() {
|
||||||
|
return nil, errors.New("couldn't fetch balance. Node state is pruned")
|
||||||
|
}
|
||||||
|
|
||||||
|
return (*hexutil.Big)(val.BigInt()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTransactionCount returns the number of transactions at the given address up to the given block number.
|
||||||
|
func (b *Backend) GetTransactionCount(address common.Address, blockNum rpctypes.BlockNumber) (*hexutil.Uint64, error) {
|
||||||
|
// Get nonce (sequence) from account
|
||||||
|
from := sdk.AccAddress(address.Bytes())
|
||||||
|
accRet := b.clientCtx.AccountRetriever
|
||||||
|
|
||||||
|
err := accRet.EnsureExists(b.clientCtx, from)
|
||||||
|
if err != nil {
|
||||||
|
// account doesn't exist yet, return 0
|
||||||
|
n := hexutil.Uint64(0)
|
||||||
|
return &n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
includePending := blockNum == rpctypes.EthPendingBlockNumber
|
||||||
|
nonce, err := b.getAccountNonce(address, includePending, blockNum.Int64(), b.logger)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
n := hexutil.Uint64(nonce)
|
||||||
|
return &n, nil
|
||||||
|
}
|
@ -6,6 +6,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||||
|
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
||||||
"github.com/cosmos/cosmos-sdk/server"
|
"github.com/cosmos/cosmos-sdk/server"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
@ -13,10 +15,13 @@ import (
|
|||||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
"github.com/evmos/ethermint/rpc/types"
|
"github.com/ethereum/go-ethereum/signer/core/apitypes"
|
||||||
|
"github.com/evmos/ethermint/crypto/hd"
|
||||||
|
rpctypes "github.com/evmos/ethermint/rpc/types"
|
||||||
"github.com/evmos/ethermint/server/config"
|
"github.com/evmos/ethermint/server/config"
|
||||||
ethermint "github.com/evmos/ethermint/types"
|
ethermint "github.com/evmos/ethermint/types"
|
||||||
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
||||||
|
"github.com/spf13/viper"
|
||||||
"github.com/tendermint/tendermint/libs/log"
|
"github.com/tendermint/tendermint/libs/log"
|
||||||
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
|
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||||
)
|
)
|
||||||
@ -40,58 +45,97 @@ type CosmosBackend interface { // TODO: define
|
|||||||
// as defined by EIP-1474: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1474.md
|
// as defined by EIP-1474: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1474.md
|
||||||
// Implemented by Backend.
|
// Implemented by Backend.
|
||||||
type EVMBackend interface {
|
type EVMBackend interface {
|
||||||
// General Ethereum API
|
// Node specific queries
|
||||||
|
Accounts() ([]common.Address, error)
|
||||||
|
Syncing() (interface{}, error)
|
||||||
|
SetEtherbase(etherbase common.Address) bool
|
||||||
|
ImportRawKey(privkey, password string) (common.Address, error)
|
||||||
|
ListAccounts() ([]common.Address, error)
|
||||||
|
NewMnemonic(uid string, language keyring.Language, hdPath, bip39Passphrase string, algo keyring.SignatureAlgo) (*keyring.Record, error)
|
||||||
|
UnprotectedAllowed() bool
|
||||||
RPCGasCap() uint64 // global gas cap for eth_call over rpc: DoS protection
|
RPCGasCap() uint64 // global gas cap for eth_call over rpc: DoS protection
|
||||||
RPCEVMTimeout() time.Duration // global timeout 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.
|
RPCTxFeeCap() float64 // RPCTxFeeCap is the global transaction fee(price * gaslimit) cap for send-transaction variants. The unit is ether.
|
||||||
UnprotectedAllowed() bool
|
|
||||||
|
|
||||||
RPCMinGasPrice() int64
|
RPCMinGasPrice() int64
|
||||||
SuggestGasTipCap(baseFee *big.Int) (*big.Int, error)
|
|
||||||
|
|
||||||
// Blockchain API
|
// Sign Tx
|
||||||
|
Sign(address common.Address, data hexutil.Bytes) (hexutil.Bytes, error)
|
||||||
|
SendTransaction(args evmtypes.TransactionArgs) (common.Hash, error)
|
||||||
|
SignTypedData(address common.Address, typedData apitypes.TypedData) (hexutil.Bytes, error)
|
||||||
|
|
||||||
|
// Blocks Info
|
||||||
BlockNumber() (hexutil.Uint64, error)
|
BlockNumber() (hexutil.Uint64, error)
|
||||||
GetBlockByNumber(blockNum types.BlockNumber, fullTx bool) (map[string]interface{}, error)
|
GetBlockByNumber(blockNum rpctypes.BlockNumber, fullTx bool) (map[string]interface{}, error)
|
||||||
GetTendermintBlockByNumber(blockNum types.BlockNumber) (*tmrpctypes.ResultBlock, error)
|
GetTendermintBlockByNumber(blockNum rpctypes.BlockNumber) (*tmrpctypes.ResultBlock, error)
|
||||||
GetTendermintBlockResultByNumber(height *int64) (*tmrpctypes.ResultBlockResults, error)
|
GetTendermintBlockResultByNumber(height *int64) (*tmrpctypes.ResultBlockResults, error)
|
||||||
GetTendermintBlockByHash(blockHash common.Hash) (*tmrpctypes.ResultBlock, error)
|
GetTendermintBlockByHash(blockHash common.Hash) (*tmrpctypes.ResultBlock, error)
|
||||||
GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error)
|
GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error)
|
||||||
BlockByNumber(blockNum types.BlockNumber) (*ethtypes.Block, error)
|
BlockByNumber(blockNum rpctypes.BlockNumber) (*ethtypes.Block, error)
|
||||||
BlockByHash(blockHash common.Hash) (*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)
|
|
||||||
GetBlockNumberByHash(blockHash common.Hash) (*big.Int, error)
|
GetBlockNumberByHash(blockHash common.Hash) (*big.Int, error)
|
||||||
|
GetBlockNumber(blockNrOrHash rpctypes.BlockNumberOrHash) (rpctypes.BlockNumber, error)
|
||||||
|
GetBlockTransactionCountByHash(hash common.Hash) *hexutil.Uint
|
||||||
|
GetBlockTransactionCountByNumber(blockNum rpctypes.BlockNumber) *hexutil.Uint
|
||||||
|
BlockBloom(blockRes *tmrpctypes.ResultBlockResults) (ethtypes.Bloom, error)
|
||||||
|
GetEthereumMsgsFromTendermintBlock(block *tmrpctypes.ResultBlock, blockRes *tmrpctypes.ResultBlockResults) []*evmtypes.MsgEthereumTx
|
||||||
|
HeaderByNumber(blockNum rpctypes.BlockNumber) (*ethtypes.Header, error)
|
||||||
|
HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error)
|
||||||
|
EthBlockFromTendermint(resBlock *tmrpctypes.ResultBlock, blockRes *tmrpctypes.ResultBlockResults, fullTx bool) (map[string]interface{}, error)
|
||||||
|
EthBlockFromTm(resBlock *tmrpctypes.ResultBlock, blockRes *tmrpctypes.ResultBlockResults) (*ethtypes.Block, error)
|
||||||
|
|
||||||
|
// Account Info
|
||||||
|
GetCode(address common.Address, blockNrOrHash rpctypes.BlockNumberOrHash) (hexutil.Bytes, error)
|
||||||
|
GetBalance(address common.Address, blockNrOrHash rpctypes.BlockNumberOrHash) (*hexutil.Big, error)
|
||||||
|
GetStorageAt(address common.Address, key string, blockNrOrHash rpctypes.BlockNumberOrHash) (hexutil.Bytes, error)
|
||||||
|
GetProof(address common.Address, storageKeys []string, blockNrOrHash rpctypes.BlockNumberOrHash) (*rpctypes.AccountResult, error)
|
||||||
|
GetTransactionCount(address common.Address, blockNum rpctypes.BlockNumber) (*hexutil.Uint64, error)
|
||||||
|
|
||||||
|
// Chain Info
|
||||||
|
ChainID() (*hexutil.Big, error)
|
||||||
|
ChainConfig() *params.ChainConfig
|
||||||
|
GlobalMinGasPrice() (sdk.Dec, error)
|
||||||
|
BaseFee(blockRes *tmrpctypes.ResultBlockResults) (*big.Int, error)
|
||||||
|
CurrentHeader() *ethtypes.Header
|
||||||
PendingTransactions() ([]*sdk.Tx, 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)
|
GetCoinbase() (sdk.AccAddress, error)
|
||||||
GetTransactionByHash(txHash common.Hash) (*types.RPCTransaction, error)
|
FeeHistory(blockCount rpc.DecimalOrHex, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*rpctypes.FeeHistoryResult, error)
|
||||||
|
SuggestGasTipCap(baseFee *big.Int) (*big.Int, error)
|
||||||
|
|
||||||
|
// Tx Info
|
||||||
|
GetTransactionByHash(txHash common.Hash) (*rpctypes.RPCTransaction, error)
|
||||||
GetTxByEthHash(txHash common.Hash) (*tmrpctypes.ResultTx, error)
|
GetTxByEthHash(txHash common.Hash) (*tmrpctypes.ResultTx, error)
|
||||||
GetTxByTxIndex(height int64, txIndex uint) (*tmrpctypes.ResultTx, error)
|
GetTxByTxIndex(height int64, txIndex uint) (*tmrpctypes.ResultTx, error)
|
||||||
EstimateGas(args evmtypes.TransactionArgs, blockNrOptional *types.BlockNumber) (hexutil.Uint64, error)
|
GetTransactionByBlockAndIndex(block *tmrpctypes.ResultBlock, idx hexutil.Uint) (*rpctypes.RPCTransaction, error)
|
||||||
BaseFee(blockRes *tmrpctypes.ResultBlockResults) (*big.Int, error)
|
GetTransactionReceipt(hash common.Hash) (map[string]interface{}, error)
|
||||||
GlobalMinGasPrice() (sdk.Dec, error)
|
GetTransactionByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) (*rpctypes.RPCTransaction, error)
|
||||||
|
GetTransactionByBlockNumberAndIndex(blockNum rpctypes.BlockNumber, idx hexutil.Uint) (*rpctypes.RPCTransaction, error)
|
||||||
|
|
||||||
// Fee API
|
// Send Transaction
|
||||||
FeeHistory(blockCount rpc.DecimalOrHex, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*types.FeeHistoryResult, error)
|
Resend(args evmtypes.TransactionArgs, gasPrice *hexutil.Big, gasLimit *hexutil.Uint64) (common.Hash, error)
|
||||||
|
SendRawTransaction(data hexutil.Bytes) (common.Hash, error)
|
||||||
|
SetTxDefaults(args evmtypes.TransactionArgs) (evmtypes.TransactionArgs, error)
|
||||||
|
EstimateGas(args evmtypes.TransactionArgs, blockNrOptional *rpctypes.BlockNumber) (hexutil.Uint64, error)
|
||||||
|
DoCall(args evmtypes.TransactionArgs, blockNr rpctypes.BlockNumber) (*evmtypes.MsgEthereumTxResponse, error)
|
||||||
|
|
||||||
// Filter API
|
// Filter API
|
||||||
BloomStatus() (uint64, uint64)
|
|
||||||
GetLogs(hash common.Hash) ([][]*ethtypes.Log, error)
|
GetLogs(hash common.Hash) ([][]*ethtypes.Log, error)
|
||||||
GetLogsByHeight(height *int64) ([][]*ethtypes.Log, error)
|
GetLogsByHeight(height *int64) ([][]*ethtypes.Log, error)
|
||||||
ChainConfig() *params.ChainConfig
|
BloomStatus() (uint64, uint64)
|
||||||
SetTxDefaults(args evmtypes.TransactionArgs) (evmtypes.TransactionArgs, error)
|
|
||||||
GetEthereumMsgsFromTendermintBlock(block *tmrpctypes.ResultBlock, blockRes *tmrpctypes.ResultBlockResults) []*evmtypes.MsgEthereumTx
|
// Tracing
|
||||||
|
TraceTransaction(hash common.Hash, config *evmtypes.TraceConfig) (interface{}, error)
|
||||||
|
TraceBlock(height rpctypes.BlockNumber, config *evmtypes.TraceConfig, block *tmrpctypes.ResultBlock) ([]*evmtypes.TxTraceResult, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ BackendI = (*Backend)(nil)
|
var _ BackendI = (*Backend)(nil)
|
||||||
|
|
||||||
|
var bAttributeKeyEthereumBloom = []byte(evmtypes.AttributeKeyEthereumBloom)
|
||||||
|
|
||||||
// Backend implements the BackendI interface
|
// Backend implements the BackendI interface
|
||||||
type Backend struct {
|
type Backend struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
clientCtx client.Context
|
clientCtx client.Context
|
||||||
queryClient *types.QueryClient // gRPC query client
|
queryClient *rpctypes.QueryClient // gRPC query client
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
chainID *big.Int
|
chainID *big.Int
|
||||||
cfg config.Config
|
cfg config.Config
|
||||||
@ -107,10 +151,27 @@ func NewBackend(ctx *server.Context, logger log.Logger, clientCtx client.Context
|
|||||||
|
|
||||||
appConf := config.GetConfig(ctx.Viper)
|
appConf := config.GetConfig(ctx.Viper)
|
||||||
|
|
||||||
|
algos, _ := clientCtx.Keyring.SupportedAlgorithms()
|
||||||
|
if !algos.Contains(hd.EthSecp256k1) {
|
||||||
|
kr, err := keyring.New(
|
||||||
|
sdk.KeyringServiceName(),
|
||||||
|
viper.GetString(flags.FlagKeyringBackend),
|
||||||
|
clientCtx.KeyringDir,
|
||||||
|
clientCtx.Input,
|
||||||
|
clientCtx.Codec,
|
||||||
|
hd.EthSecp256k1Option(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
clientCtx = clientCtx.WithKeyring(kr)
|
||||||
|
}
|
||||||
|
|
||||||
return &Backend{
|
return &Backend{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
clientCtx: clientCtx,
|
clientCtx: clientCtx,
|
||||||
queryClient: types.NewQueryClient(clientCtx),
|
queryClient: rpctypes.NewQueryClient(clientCtx),
|
||||||
logger: logger.With("module", "backend"),
|
logger: logger.With("module", "backend"),
|
||||||
chainID: chainID,
|
chainID: chainID,
|
||||||
cfg: appConf,
|
cfg: appConf,
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
package backend
|
package backend
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
|
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
||||||
"github.com/cosmos/cosmos-sdk/server"
|
"github.com/cosmos/cosmos-sdk/server"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
@ -13,10 +18,10 @@ import (
|
|||||||
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
|
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||||
|
|
||||||
"github.com/evmos/ethermint/app"
|
"github.com/evmos/ethermint/app"
|
||||||
|
"github.com/evmos/ethermint/crypto/hd"
|
||||||
"github.com/evmos/ethermint/encoding"
|
"github.com/evmos/ethermint/encoding"
|
||||||
"github.com/evmos/ethermint/rpc/backend/mocks"
|
"github.com/evmos/ethermint/rpc/backend/mocks"
|
||||||
ethrpc "github.com/evmos/ethermint/rpc/types"
|
rpctypes "github.com/evmos/ethermint/rpc/types"
|
||||||
rpc "github.com/evmos/ethermint/rpc/types"
|
|
||||||
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -34,17 +39,27 @@ func (suite *BackendTestSuite) SetupTest() {
|
|||||||
ctx := server.NewDefaultContext()
|
ctx := server.NewDefaultContext()
|
||||||
ctx.Viper.Set("telemetry.global-labels", []interface{}{})
|
ctx.Viper.Set("telemetry.global-labels", []interface{}{})
|
||||||
|
|
||||||
|
baseDir := suite.T().TempDir()
|
||||||
|
nodeDirName := fmt.Sprintf("node")
|
||||||
|
clientDir := filepath.Join(baseDir, nodeDirName, "evmoscli")
|
||||||
|
keyRing, err := suite.generateTestKeyring(clientDir)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
encodingConfig := encoding.MakeConfig(app.ModuleBasics)
|
encodingConfig := encoding.MakeConfig(app.ModuleBasics)
|
||||||
clientCtx := client.Context{}.WithChainID("ethermint_9000-1").
|
clientCtx := client.Context{}.WithChainID("ethermint_9000-1").
|
||||||
WithHeight(1).
|
WithHeight(1).
|
||||||
WithTxConfig(encodingConfig.TxConfig)
|
WithTxConfig(encodingConfig.TxConfig).
|
||||||
|
WithKeyringDir(clientDir).
|
||||||
|
WithKeyring(keyRing)
|
||||||
|
|
||||||
allowUnprotectedTxs := false
|
allowUnprotectedTxs := false
|
||||||
|
|
||||||
suite.backend = NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs)
|
suite.backend = NewBackend(ctx, ctx.Logger, clientCtx, allowUnprotectedTxs)
|
||||||
suite.backend.queryClient.QueryClient = mocks.NewQueryClient(suite.T())
|
suite.backend.queryClient.QueryClient = mocks.NewQueryClient(suite.T())
|
||||||
suite.backend.clientCtx.Client = mocks.NewClient(suite.T())
|
suite.backend.clientCtx.Client = mocks.NewClient(suite.T())
|
||||||
suite.backend.ctx = rpc.ContextWithHeight(1)
|
suite.backend.ctx = rpctypes.ContextWithHeight(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildEthereumTx returns an example legacy Ethereum transaction
|
// buildEthereumTx returns an example legacy Ethereum transaction
|
||||||
@ -94,7 +109,7 @@ func (suite *BackendTestSuite) buildFormattedBlock(
|
|||||||
ethRPCTxs := []interface{}{}
|
ethRPCTxs := []interface{}{}
|
||||||
if tx != nil {
|
if tx != nil {
|
||||||
if fullTx {
|
if fullTx {
|
||||||
rpcTx, err := ethrpc.NewRPCTransaction(
|
rpcTx, err := rpctypes.NewRPCTransaction(
|
||||||
tx.AsTransaction(),
|
tx.AsTransaction(),
|
||||||
common.BytesToHash(header.Hash()),
|
common.BytesToHash(header.Hash()),
|
||||||
uint64(header.Height),
|
uint64(header.Height),
|
||||||
@ -108,7 +123,7 @@ func (suite *BackendTestSuite) buildFormattedBlock(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ethrpc.FormatBlock(
|
return rpctypes.FormatBlock(
|
||||||
header,
|
header,
|
||||||
resBlock.Block.Size(),
|
resBlock.Block.Size(),
|
||||||
gasLimit,
|
gasLimit,
|
||||||
@ -119,3 +134,9 @@ func (suite *BackendTestSuite) buildFormattedBlock(
|
|||||||
baseFee,
|
baseFee,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *BackendTestSuite) generateTestKeyring(clientDir string) (keyring.Keyring, error) {
|
||||||
|
buf := bufio.NewReader(os.Stdin)
|
||||||
|
encCfg := encoding.MakeConfig(app.ModuleBasics)
|
||||||
|
return keyring.New(sdk.KeyringServiceName(), keyring.BackendTest, clientDir, buf, encCfg.Codec, []keyring.Option{hd.EthSecp256k1Option()}...)
|
||||||
|
}
|
||||||
|
512
rpc/backend/blocks_info.go
Normal file
512
rpc/backend/blocks_info.go
Normal file
@ -0,0 +1,512 @@
|
|||||||
|
package backend
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||||
|
rpctypes "github.com/evmos/ethermint/rpc/types"
|
||||||
|
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Getting Blocks
|
||||||
|
//
|
||||||
|
// Retrieves information from a particular block in the blockchain.
|
||||||
|
// BlockNumber() (hexutil.Uint64, error)
|
||||||
|
// GetBlockByNumber(ethBlockNum rpctypes.BlockNumber, fullTx bool) (map[string]interface{}, error)
|
||||||
|
// GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error)
|
||||||
|
|
||||||
|
// BlockNumber returns the current block number in abci app state.
|
||||||
|
// Because abci app state could lag behind from tendermint latest block, it's more stable
|
||||||
|
// for the client to use the latest block number in abci app state than tendermint rpc.
|
||||||
|
func (b *Backend) BlockNumber() (hexutil.Uint64, error) {
|
||||||
|
// do any grpc query, ignore the response and use the returned block height
|
||||||
|
var header metadata.MD
|
||||||
|
_, err := b.queryClient.Params(b.ctx, &evmtypes.QueryParamsRequest{}, grpc.Header(&header))
|
||||||
|
if err != nil {
|
||||||
|
return hexutil.Uint64(0), err
|
||||||
|
}
|
||||||
|
|
||||||
|
blockHeightHeader := header.Get(grpctypes.GRPCBlockHeightHeader)
|
||||||
|
if headerLen := len(blockHeightHeader); headerLen != 1 {
|
||||||
|
return 0, fmt.Errorf("unexpected '%s' gRPC header length; got %d, expected: %d", grpctypes.GRPCBlockHeightHeader, headerLen, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
height, err := strconv.ParseUint(blockHeightHeader[0], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("failed to parse block height: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return hexutil.Uint64(height), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockByNumber returns the block identified by number.
|
||||||
|
func (b *Backend) GetBlockByNumber(blockNum rpctypes.BlockNumber, fullTx bool) (map[string]interface{}, error) {
|
||||||
|
resBlock, err := b.GetTendermintBlockByNumber(blockNum)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// return if requested block height is greater than the current one
|
||||||
|
if resBlock == nil || resBlock.Block == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
blockRes, err := b.GetTendermintBlockResultByNumber(&resBlock.Block.Height)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("failed to fetch block result from Tendermint", "height", blockNum, "error", err.Error())
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := b.EthBlockFromTendermint(resBlock, blockRes, fullTx)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("EthBlockFromTendermint failed", "height", blockNum, "error", err.Error())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockByHash returns the block identified by hash.
|
||||||
|
func (b *Backend) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) {
|
||||||
|
resBlock, err := b.GetTendermintBlockByHash(hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resBlock == nil {
|
||||||
|
// block not found
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
blockRes, err := b.GetTendermintBlockResultByNumber(&resBlock.Block.Height)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("failed to fetch block result from Tendermint", "block-hash", hash.String(), "error", err.Error())
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := b.EthBlockFromTendermint(resBlock, blockRes, fullTx)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("EthBlockFromTendermint failed", "hash", hash, "error", err.Error())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTendermintBlockByNumber returns a Tendermint formatted block for a given
|
||||||
|
// block number
|
||||||
|
func (b *Backend) GetTendermintBlockByNumber(blockNum rpctypes.BlockNumber) (*tmrpctypes.ResultBlock, error) {
|
||||||
|
height := blockNum.Int64()
|
||||||
|
if height <= 0 {
|
||||||
|
// fetch the latest block number from the app state, more accurate than the tendermint block store state.
|
||||||
|
n, err := b.BlockNumber()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
height = int64(n)
|
||||||
|
}
|
||||||
|
resBlock, err := b.clientCtx.Client.Block(b.ctx, &height)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("tendermint client failed to get block", "height", height, "error", err.Error())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resBlock.Block == nil {
|
||||||
|
b.logger.Debug("GetTendermintBlockByNumber block not found", "height", height)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return resBlock, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockBloom query block bloom filter from block results
|
||||||
|
func (b *Backend) BlockBloom(blockRes *tmrpctypes.ResultBlockResults) (ethtypes.Bloom, error) {
|
||||||
|
for _, event := range blockRes.EndBlockEvents {
|
||||||
|
if event.Type != evmtypes.EventTypeBlockBloom {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, attr := range event.Attributes {
|
||||||
|
if bytes.Equal(attr.Key, bAttributeKeyEthereumBloom) {
|
||||||
|
return ethtypes.BytesToBloom(attr.Value), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ethtypes.Bloom{}, errors.New("block bloom event is not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTendermintBlockResultByNumber returns a Tendermint-formatted block result by block number
|
||||||
|
func (b *Backend) GetTendermintBlockResultByNumber(height *int64) (*tmrpctypes.ResultBlockResults, error) {
|
||||||
|
return b.clientCtx.Client.BlockResults(b.ctx, height)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTendermintBlockByHash returns a Tendermint format block by block number
|
||||||
|
func (b *Backend) GetTendermintBlockByHash(blockHash common.Hash) (*tmrpctypes.ResultBlock, error) {
|
||||||
|
resBlock, err := b.clientCtx.Client.BlockByHash(b.ctx, blockHash.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("tendermint client failed to get block", "blockHash", blockHash.Hex(), "error", err.Error())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resBlock == nil || resBlock.Block == nil {
|
||||||
|
b.logger.Debug("GetTendermintBlockByHash block not found", "blockHash", blockHash.Hex())
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return resBlock, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockByNumber returns the block identified by number.
|
||||||
|
func (b *Backend) BlockByNumber(blockNum rpctypes.BlockNumber) (*ethtypes.Block, error) {
|
||||||
|
resBlock, err := b.GetTendermintBlockByNumber(blockNum)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resBlock == nil {
|
||||||
|
// block not found
|
||||||
|
return nil, fmt.Errorf("block not found for height %d", blockNum)
|
||||||
|
}
|
||||||
|
|
||||||
|
blockRes, err := b.GetTendermintBlockResultByNumber(&resBlock.Block.Height)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("block result not found for height %d", resBlock.Block.Height)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.EthBlockFromTm(resBlock, blockRes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockByHash returns the block identified by hash.
|
||||||
|
func (b *Backend) BlockByHash(hash common.Hash) (*ethtypes.Block, error) {
|
||||||
|
resBlock, err := b.GetTendermintBlockByHash(hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resBlock == nil || resBlock.Block == nil {
|
||||||
|
return nil, fmt.Errorf("block not found for hash %s", hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
blockRes, err := b.GetTendermintBlockResultByNumber(&resBlock.Block.Height)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("block result not found for hash %s", hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.EthBlockFromTm(resBlock, blockRes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockNumberByHash returns the block height of given block hash
|
||||||
|
func (b *Backend) GetBlockNumberByHash(blockHash common.Hash) (*big.Int, error) {
|
||||||
|
resBlock, err := b.GetTendermintBlockByHash(blockHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resBlock == nil {
|
||||||
|
return nil, errors.Errorf("block not found for hash %s", blockHash.Hex())
|
||||||
|
}
|
||||||
|
return big.NewInt(resBlock.Block.Height), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getBlockNumber returns the BlockNumber from BlockNumberOrHash
|
||||||
|
func (b *Backend) GetBlockNumber(blockNrOrHash rpctypes.BlockNumberOrHash) (rpctypes.BlockNumber, error) {
|
||||||
|
switch {
|
||||||
|
case blockNrOrHash.BlockHash == nil && blockNrOrHash.BlockNumber == nil:
|
||||||
|
return rpctypes.EthEarliestBlockNumber, fmt.Errorf("types BlockHash and BlockNumber cannot be both nil")
|
||||||
|
case blockNrOrHash.BlockHash != nil:
|
||||||
|
blockNumber, err := b.GetBlockNumberByHash(*blockNrOrHash.BlockHash)
|
||||||
|
if err != nil {
|
||||||
|
return rpctypes.EthEarliestBlockNumber, err
|
||||||
|
}
|
||||||
|
return rpctypes.NewBlockNumber(blockNumber), nil
|
||||||
|
case blockNrOrHash.BlockNumber != nil:
|
||||||
|
return *blockNrOrHash.BlockNumber, nil
|
||||||
|
default:
|
||||||
|
return rpctypes.EthEarliestBlockNumber, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockTransactionCountByHash returns the number of transactions in the block identified by hash.
|
||||||
|
func (b *Backend) GetBlockTransactionCountByHash(hash common.Hash) *hexutil.Uint {
|
||||||
|
block, err := b.clientCtx.Client.BlockByHash(b.ctx, hash.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("block not found", "hash", hash.Hex(), "error", err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if block.Block == nil {
|
||||||
|
b.logger.Debug("block not found", "hash", hash.Hex())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
blockRes, err := b.GetTendermintBlockResultByNumber(&block.Block.Height)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ethMsgs := b.GetEthereumMsgsFromTendermintBlock(block, blockRes)
|
||||||
|
n := hexutil.Uint(len(ethMsgs))
|
||||||
|
return &n
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockTransactionCountByNumber returns the number of transactions in the block identified by number.
|
||||||
|
func (b *Backend) GetBlockTransactionCountByNumber(blockNum rpctypes.BlockNumber) *hexutil.Uint {
|
||||||
|
block, err := b.GetTendermintBlockByNumber(blockNum)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("block not found", "height", blockNum.Int64(), "error", err.Error())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if block.Block == nil {
|
||||||
|
b.logger.Debug("block not found", "height", blockNum.Int64())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
blockRes, err := b.GetTendermintBlockResultByNumber(&block.Block.Height)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ethMsgs := b.GetEthereumMsgsFromTendermintBlock(block, blockRes)
|
||||||
|
n := hexutil.Uint(len(ethMsgs))
|
||||||
|
return &n
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEthereumMsgsFromTendermintBlock returns all real MsgEthereumTxs from a
|
||||||
|
// Tendermint block. It also ensures consistency over the correct txs indexes
|
||||||
|
// across RPC endpoints
|
||||||
|
func (b *Backend) GetEthereumMsgsFromTendermintBlock(
|
||||||
|
resBlock *tmrpctypes.ResultBlock,
|
||||||
|
blockRes *tmrpctypes.ResultBlockResults,
|
||||||
|
) []*evmtypes.MsgEthereumTx {
|
||||||
|
var result []*evmtypes.MsgEthereumTx
|
||||||
|
block := resBlock.Block
|
||||||
|
|
||||||
|
txResults := blockRes.TxsResults
|
||||||
|
|
||||||
|
for i, tx := range block.Txs {
|
||||||
|
// Check if tx exists on EVM by cross checking with blockResults:
|
||||||
|
// - Include unsuccessful tx that exceeds block gas limit
|
||||||
|
// - Exclude unsuccessful tx with any other error but ExceedBlockGasLimit
|
||||||
|
if !TxSuccessOrExceedsBlockGasLimit(txResults[i]) {
|
||||||
|
b.logger.Debug("invalid tx result code", "cosmos-hash", hexutil.Encode(tx.Hash()))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
tx, err := b.clientCtx.TxConfig.TxDecoder()(tx)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("failed to decode transaction in block", "height", block.Height, "error", err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, msg := range tx.GetMsgs() {
|
||||||
|
ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ethMsg.Hash = ethMsg.AsTransaction().Hash().Hex()
|
||||||
|
result = append(result, ethMsg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeaderByNumber returns the block header identified by height.
|
||||||
|
func (b *Backend) HeaderByNumber(blockNum rpctypes.BlockNumber) (*ethtypes.Header, error) {
|
||||||
|
resBlock, err := b.GetTendermintBlockByNumber(blockNum)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resBlock == nil {
|
||||||
|
return nil, errors.Errorf("block not found for height %d", blockNum)
|
||||||
|
}
|
||||||
|
|
||||||
|
blockRes, err := b.GetTendermintBlockResultByNumber(&resBlock.Block.Height)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("block result not found for height %d", resBlock.Block.Height)
|
||||||
|
}
|
||||||
|
|
||||||
|
bloom, err := b.BlockBloom(blockRes)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("HeaderByNumber BlockBloom failed", "height", resBlock.Block.Height)
|
||||||
|
}
|
||||||
|
|
||||||
|
baseFee, err := b.BaseFee(blockRes)
|
||||||
|
if err != nil {
|
||||||
|
// handle the error for pruned node.
|
||||||
|
b.logger.Error("failed to fetch Base Fee from prunned block. Check node prunning configuration", "height", resBlock.Block.Height, "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ethHeader := rpctypes.EthHeaderFromTendermint(resBlock.Block.Header, bloom, baseFee)
|
||||||
|
return ethHeader, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeaderByHash returns the block header identified by hash.
|
||||||
|
func (b *Backend) HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error) {
|
||||||
|
resBlock, err := b.GetTendermintBlockByHash(blockHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resBlock == nil {
|
||||||
|
return nil, errors.Errorf("block not found for hash %s", blockHash.Hex())
|
||||||
|
}
|
||||||
|
|
||||||
|
blockRes, err := b.GetTendermintBlockResultByNumber(&resBlock.Block.Height)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Errorf("block result not found for height %d", resBlock.Block.Height)
|
||||||
|
}
|
||||||
|
|
||||||
|
bloom, err := b.BlockBloom(blockRes)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("HeaderByHash BlockBloom failed", "height", resBlock.Block.Height)
|
||||||
|
}
|
||||||
|
|
||||||
|
baseFee, err := b.BaseFee(blockRes)
|
||||||
|
if err != nil {
|
||||||
|
// handle the error for pruned node.
|
||||||
|
b.logger.Error("failed to fetch Base Fee from prunned block. Check node prunning configuration", "height", resBlock.Block.Height, "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ethHeader := rpctypes.EthHeaderFromTendermint(resBlock.Block.Header, bloom, baseFee)
|
||||||
|
return ethHeader, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EthBlockFromTendermint returns a JSON-RPC compatible Ethereum block from a
|
||||||
|
// given Tendermint block and its block result.
|
||||||
|
func (b *Backend) EthBlockFromTendermint(
|
||||||
|
resBlock *tmrpctypes.ResultBlock,
|
||||||
|
blockRes *tmrpctypes.ResultBlockResults,
|
||||||
|
fullTx bool,
|
||||||
|
) (map[string]interface{}, error) {
|
||||||
|
ethRPCTxs := []interface{}{}
|
||||||
|
block := resBlock.Block
|
||||||
|
|
||||||
|
baseFee, err := b.BaseFee(blockRes)
|
||||||
|
if err != nil {
|
||||||
|
// handle the error for pruned node.
|
||||||
|
b.logger.Error("failed to fetch Base Fee from prunned block. Check node prunning configuration", "height", block.Height, "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msgs := b.GetEthereumMsgsFromTendermintBlock(resBlock, blockRes)
|
||||||
|
for txIndex, ethMsg := range msgs {
|
||||||
|
if !fullTx {
|
||||||
|
hash := common.HexToHash(ethMsg.Hash)
|
||||||
|
ethRPCTxs = append(ethRPCTxs, hash)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
tx := ethMsg.AsTransaction()
|
||||||
|
rpcTx, err := rpctypes.NewRPCTransaction(
|
||||||
|
tx,
|
||||||
|
common.BytesToHash(block.Hash()),
|
||||||
|
uint64(block.Height),
|
||||||
|
uint64(txIndex),
|
||||||
|
baseFee,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("NewTransactionFromData for receipt failed", "hash", tx.Hash().Hex(), "error", err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ethRPCTxs = append(ethRPCTxs, rpcTx)
|
||||||
|
}
|
||||||
|
|
||||||
|
bloom, err := b.BlockBloom(blockRes)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("failed to query BlockBloom", "height", block.Height, "error", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &evmtypes.QueryValidatorAccountRequest{
|
||||||
|
ConsAddress: sdk.ConsAddress(block.Header.ProposerAddress).String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
var validatorAccAddr sdk.AccAddress
|
||||||
|
|
||||||
|
ctx := rpctypes.ContextWithHeight(block.Height)
|
||||||
|
res, err := b.queryClient.ValidatorAccount(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug(
|
||||||
|
"failed to query validator operator address",
|
||||||
|
"height", block.Height,
|
||||||
|
"cons-address", req.ConsAddress,
|
||||||
|
"error", err.Error(),
|
||||||
|
)
|
||||||
|
// use zero address as the validator operator address
|
||||||
|
validatorAccAddr = sdk.AccAddress(common.Address{}.Bytes())
|
||||||
|
} else {
|
||||||
|
validatorAccAddr, err = sdk.AccAddressFromBech32(res.AccountAddress)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validatorAddr := common.BytesToAddress(validatorAccAddr)
|
||||||
|
|
||||||
|
gasLimit, err := rpctypes.BlockMaxGasFromConsensusParams(ctx, b.clientCtx, block.Height)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Error("failed to query consensus params", "error", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
gasUsed := uint64(0)
|
||||||
|
|
||||||
|
for _, txsResult := range blockRes.TxsResults {
|
||||||
|
// workaround for cosmos-sdk bug. https://github.com/cosmos/cosmos-sdk/issues/10832
|
||||||
|
if ShouldIgnoreGasUsed(txsResult) {
|
||||||
|
// block gas limit has exceeded, other txs must have failed with same reason.
|
||||||
|
break
|
||||||
|
}
|
||||||
|
gasUsed += uint64(txsResult.GetGasUsed())
|
||||||
|
}
|
||||||
|
|
||||||
|
formattedBlock := rpctypes.FormatBlock(
|
||||||
|
block.Header, block.Size(),
|
||||||
|
gasLimit, new(big.Int).SetUint64(gasUsed),
|
||||||
|
ethRPCTxs, bloom, validatorAddr, baseFee,
|
||||||
|
)
|
||||||
|
return formattedBlock, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns and Ethereum Block type from Tendermint block
|
||||||
|
func (b *Backend) EthBlockFromTm(resBlock *tmrpctypes.ResultBlock, blockRes *tmrpctypes.ResultBlockResults) (*ethtypes.Block, error) {
|
||||||
|
block := resBlock.Block
|
||||||
|
height := block.Height
|
||||||
|
bloom, err := b.BlockBloom(blockRes)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("HeaderByNumber BlockBloom failed", "height", height)
|
||||||
|
}
|
||||||
|
|
||||||
|
baseFee, err := b.BaseFee(blockRes)
|
||||||
|
if err != nil {
|
||||||
|
// handle error for pruned node and log
|
||||||
|
b.logger.Error("failed to fetch Base Fee from prunned block. Check node prunning configuration", "height", height, "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ethHeader := rpctypes.EthHeaderFromTendermint(block.Header, bloom, baseFee)
|
||||||
|
|
||||||
|
resBlockResult, err := b.GetTendermintBlockResultByNumber(&block.Height)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
msgs := b.GetEthereumMsgsFromTendermintBlock(resBlock, resBlockResult)
|
||||||
|
|
||||||
|
txs := make([]*ethtypes.Transaction, len(msgs))
|
||||||
|
for i, ethMsg := range msgs {
|
||||||
|
txs[i] = ethMsg.AsTransaction()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: add tx receipts
|
||||||
|
ethBlock := ethtypes.NewBlock(ethHeader, txs, nil, nil, nil)
|
||||||
|
return ethBlock, nil
|
||||||
|
}
|
364
rpc/backend/call_tx.go
Normal file
364
rpc/backend/call_tx.go
Normal file
@ -0,0 +1,364 @@
|
|||||||
|
package backend
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||||
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
|
"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/core/vm"
|
||||||
|
rpctypes "github.com/evmos/ethermint/rpc/types"
|
||||||
|
ethermint "github.com/evmos/ethermint/types"
|
||||||
|
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Resend accepts an existing transaction and a new gas price and limit. It will remove
|
||||||
|
// the given transaction from the pool and reinsert it with the new gas price and limit.
|
||||||
|
func (b *Backend) Resend(args evmtypes.TransactionArgs, gasPrice *hexutil.Big, gasLimit *hexutil.Uint64) (common.Hash, error) {
|
||||||
|
if args.Nonce == nil {
|
||||||
|
return common.Hash{}, fmt.Errorf("missing transaction nonce in transaction spec")
|
||||||
|
}
|
||||||
|
|
||||||
|
args, err := b.SetTxDefaults(args)
|
||||||
|
if err != nil {
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The signer used should always be the 'latest' known one because we expect
|
||||||
|
// signers to be backwards-compatible with old transactions.
|
||||||
|
eip155ChainID, err := ethermint.ParseChainID(b.clientCtx.ChainID)
|
||||||
|
if err != nil {
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := b.ChainConfig()
|
||||||
|
if cfg == nil {
|
||||||
|
cfg = evmtypes.DefaultChainConfig().EthereumConfig(eip155ChainID)
|
||||||
|
}
|
||||||
|
|
||||||
|
signer := ethtypes.LatestSigner(cfg)
|
||||||
|
|
||||||
|
matchTx := args.ToTransaction().AsTransaction()
|
||||||
|
|
||||||
|
// Before replacing the old transaction, ensure the _new_ transaction fee is reasonable.
|
||||||
|
price := matchTx.GasPrice()
|
||||||
|
if gasPrice != nil {
|
||||||
|
price = gasPrice.ToInt()
|
||||||
|
}
|
||||||
|
gas := matchTx.Gas()
|
||||||
|
if gasLimit != nil {
|
||||||
|
gas = uint64(*gasLimit)
|
||||||
|
}
|
||||||
|
if err := rpctypes.CheckTxFee(price, gas, b.RPCTxFeeCap()); err != nil {
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pending, err := b.PendingTransactions()
|
||||||
|
if err != nil {
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tx := range pending {
|
||||||
|
// FIXME does Resend api possible at all? https://github.com/evmos/ethermint/issues/905
|
||||||
|
p, err := evmtypes.UnwrapEthereumMsg(tx, common.Hash{})
|
||||||
|
if err != nil {
|
||||||
|
// not valid ethereum tx
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pTx := p.AsTransaction()
|
||||||
|
|
||||||
|
wantSigHash := signer.Hash(matchTx)
|
||||||
|
pFrom, err := ethtypes.Sender(signer, pTx)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if pFrom == *args.From && signer.Hash(pTx) == wantSigHash {
|
||||||
|
// Match. Re-sign and send the transaction.
|
||||||
|
if gasPrice != nil && (*big.Int)(gasPrice).Sign() != 0 {
|
||||||
|
args.GasPrice = gasPrice
|
||||||
|
}
|
||||||
|
if gasLimit != nil && *gasLimit != 0 {
|
||||||
|
args.Gas = gasLimit
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.SendTransaction(args) // TODO: this calls SetTxDefaults again, refactor to avoid calling it twice
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return common.Hash{}, fmt.Errorf("transaction %#x not found", matchTx.Hash())
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendRawTransaction send a raw Ethereum transaction.
|
||||||
|
func (b *Backend) SendRawTransaction(data hexutil.Bytes) (common.Hash, error) {
|
||||||
|
// RLP decode raw transaction bytes
|
||||||
|
tx := ðtypes.Transaction{}
|
||||||
|
if err := tx.UnmarshalBinary(data); err != nil {
|
||||||
|
b.logger.Error("transaction decoding failed", "error", err.Error())
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the local node config in case unprotected txs are disabled
|
||||||
|
if !b.UnprotectedAllowed() && !tx.Protected() {
|
||||||
|
// Ensure only eip155 signed transactions are submitted if EIP155Required is set.
|
||||||
|
return common.Hash{}, errors.New("only replay-protected (EIP-155) transactions allowed over RPC")
|
||||||
|
}
|
||||||
|
|
||||||
|
ethereumTx := &evmtypes.MsgEthereumTx{}
|
||||||
|
if err := ethereumTx.FromEthereumTx(tx); err != nil {
|
||||||
|
b.logger.Error("transaction converting failed", "error", err.Error())
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ethereumTx.ValidateBasic(); err != nil {
|
||||||
|
b.logger.Debug("tx failed basic validation", "error", err.Error())
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query params to use the EVM denomination
|
||||||
|
res, err := b.queryClient.QueryClient.Params(b.ctx, &evmtypes.QueryParamsRequest{})
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Error("failed to query evm params", "error", err.Error())
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cosmosTx, err := ethereumTx.BuildTx(b.clientCtx.TxConfig.NewTxBuilder(), res.Params.EvmDenom)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Error("failed to build cosmos tx", "error", err.Error())
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode transaction by default Tx encoder
|
||||||
|
txBytes, err := b.clientCtx.TxConfig.TxEncoder()(cosmosTx)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Error("failed to encode eth tx using default encoder", "error", err.Error())
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
txHash := ethereumTx.AsTransaction().Hash()
|
||||||
|
|
||||||
|
syncCtx := b.clientCtx.WithBroadcastMode(flags.BroadcastSync)
|
||||||
|
rsp, err := syncCtx.BroadcastTx(txBytes)
|
||||||
|
if rsp != nil && rsp.Code != 0 {
|
||||||
|
err = sdkerrors.ABCIError(rsp.Codespace, rsp.Code, rsp.RawLog)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Error("failed to broadcast tx", "error", err.Error())
|
||||||
|
return txHash, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return txHash, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 := rpctypes.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
|
||||||
|
}
|
||||||
|
|
||||||
|
// EstimateGas returns an estimate of gas usage for the given smart contract call.
|
||||||
|
func (b *Backend) EstimateGas(args evmtypes.TransactionArgs, blockNrOptional *rpctypes.BlockNumber) (hexutil.Uint64, error) {
|
||||||
|
blockNr := rpctypes.EthPendingBlockNumber
|
||||||
|
if blockNrOptional != nil {
|
||||||
|
blockNr = *blockNrOptional
|
||||||
|
}
|
||||||
|
|
||||||
|
bz, err := json.Marshal(&args)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := evmtypes.EthCallRequest{
|
||||||
|
Args: bz,
|
||||||
|
GasCap: b.RPCGasCap(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// From ContextWithHeight: if the provided height is 0,
|
||||||
|
// it will return an empty context and the gRPC query will use
|
||||||
|
// the latest block height for querying.
|
||||||
|
res, err := b.queryClient.EstimateGas(rpctypes.ContextWithHeight(blockNr.Int64()), &req)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return hexutil.Uint64(res.Gas), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DoCall performs a simulated call operation through the evmtypes. It returns the
|
||||||
|
// estimated gas used on the operation or an error if fails.
|
||||||
|
func (b *Backend) DoCall(
|
||||||
|
args evmtypes.TransactionArgs, blockNr rpctypes.BlockNumber,
|
||||||
|
) (*evmtypes.MsgEthereumTxResponse, error) {
|
||||||
|
bz, err := json.Marshal(&args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := evmtypes.EthCallRequest{
|
||||||
|
Args: bz,
|
||||||
|
GasCap: b.RPCGasCap(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// From ContextWithHeight: if the provided height is 0,
|
||||||
|
// it will return an empty context and the gRPC query will use
|
||||||
|
// the latest block height for querying.
|
||||||
|
ctx := rpctypes.ContextWithHeight(blockNr.Int64())
|
||||||
|
timeout := b.RPCEVMTimeout()
|
||||||
|
|
||||||
|
// Setup context so it may be canceled the call has completed
|
||||||
|
// or, in case of unmetered gas, setup a context with a timeout.
|
||||||
|
var cancel context.CancelFunc
|
||||||
|
if timeout > 0 {
|
||||||
|
ctx, cancel = context.WithTimeout(ctx, timeout)
|
||||||
|
} else {
|
||||||
|
ctx, cancel = context.WithCancel(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the context is canceled when the call has completed
|
||||||
|
// this makes sure resources are cleaned up.
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
res, err := b.queryClient.EthCall(ctx, &req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.Failed() {
|
||||||
|
if res.VmError != vm.ErrExecutionReverted.Error() {
|
||||||
|
return nil, status.Error(codes.Internal, res.VmError)
|
||||||
|
}
|
||||||
|
return nil, evmtypes.NewExecErrorWithReason(res.Ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
265
rpc/backend/chain_info.go
Normal file
265
rpc/backend/chain_info.go
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
package backend
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"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"
|
||||||
|
rpctypes "github.com/evmos/ethermint/rpc/types"
|
||||||
|
ethermint "github.com/evmos/ethermint/types"
|
||||||
|
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
||||||
|
feemarkettypes "github.com/evmos/ethermint/x/feemarket/types"
|
||||||
|
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ChainID is the EIP-155 replay-protection chain id for the current ethereum chain config.
|
||||||
|
func (b *Backend) ChainID() (*hexutil.Big, error) {
|
||||||
|
eip155ChainID, err := ethermint.ParseChainID(b.clientCtx.ChainID)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
// if current block is at or past the EIP-155 replay-protection fork block, return chainID from config
|
||||||
|
bn, err := b.BlockNumber()
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("failed to fetch latest block number", "error", err.Error())
|
||||||
|
return (*hexutil.Big)(eip155ChainID), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if config := b.ChainConfig(); config.IsEIP155(new(big.Int).SetUint64(uint64(bn))) {
|
||||||
|
return (*hexutil.Big)(config.ChainID), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("chain not synced beyond EIP-155 replay-protection fork block")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChainConfig returns the latest ethereum chain configuration
|
||||||
|
func (b *Backend) ChainConfig() *params.ChainConfig {
|
||||||
|
params, err := b.queryClient.Params(b.ctx, &evmtypes.QueryParamsRequest{})
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return params.Params.ChainConfig.EthereumConfig(b.chainID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GlobalMinGasPrice returns MinGasPrice param from FeeMarket
|
||||||
|
func (b *Backend) GlobalMinGasPrice() (sdk.Dec, error) {
|
||||||
|
res, err := b.queryClient.FeeMarket.Params(b.ctx, &feemarkettypes.QueryParamsRequest{})
|
||||||
|
if err != nil {
|
||||||
|
return sdk.ZeroDec(), err
|
||||||
|
}
|
||||||
|
return res.Params.MinGasPrice, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BaseFee returns the base fee tracked by the Fee Market module.
|
||||||
|
// If the base fee is not enabled globally, the query returns nil.
|
||||||
|
// If the London hard fork is not activated at the current height, the query will
|
||||||
|
// return nil.
|
||||||
|
func (b *Backend) BaseFee(blockRes *tmrpctypes.ResultBlockResults) (*big.Int, error) {
|
||||||
|
// return BaseFee if London hard fork is activated and feemarket is enabled
|
||||||
|
res, err := b.queryClient.BaseFee(rpctypes.ContextWithHeight(blockRes.Height), &evmtypes.QueryBaseFeeRequest{})
|
||||||
|
if err != nil || res.BaseFee == nil {
|
||||||
|
// we can't tell if it's london HF not enabled or the state is pruned,
|
||||||
|
// in either case, we'll fallback to parsing from begin blocker event,
|
||||||
|
// faster to iterate reversely
|
||||||
|
for i := len(blockRes.BeginBlockEvents) - 1; i >= 0; i-- {
|
||||||
|
evt := blockRes.BeginBlockEvents[i]
|
||||||
|
if evt.Type == feemarkettypes.EventTypeFeeMarket && len(evt.Attributes) > 0 {
|
||||||
|
baseFee, err := strconv.ParseInt(string(evt.Attributes[0].Value), 10, 64)
|
||||||
|
if err == nil {
|
||||||
|
return big.NewInt(baseFee), nil
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.BaseFee == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.BaseFee.BigInt(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurrentHeader returns the latest block header
|
||||||
|
func (b *Backend) CurrentHeader() *ethtypes.Header {
|
||||||
|
header, _ := b.HeaderByNumber(rpctypes.EthLatestBlockNumber)
|
||||||
|
return header
|
||||||
|
}
|
||||||
|
|
||||||
|
// PendingTransactions returns the transactions that are in the transaction pool
|
||||||
|
// and have a from address that is one of the accounts this node manages.
|
||||||
|
func (b *Backend) PendingTransactions() ([]*sdk.Tx, error) {
|
||||||
|
res, err := b.clientCtx.Client.UnconfirmedTxs(b.ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make([]*sdk.Tx, 0, len(res.Txs))
|
||||||
|
for _, txBz := range res.Txs {
|
||||||
|
tx, err := b.clientCtx.TxConfig.TxDecoder()(txBz)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result = append(result, &tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCoinbase is the address that staking rewards will be send to (alias for Etherbase).
|
||||||
|
func (b *Backend) GetCoinbase() (sdk.AccAddress, error) {
|
||||||
|
node, err := b.clientCtx.GetNode()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
status, err := node.Status(b.ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &evmtypes.QueryValidatorAccountRequest{
|
||||||
|
ConsAddress: sdk.ConsAddress(status.ValidatorInfo.Address).String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := b.queryClient.ValidatorAccount(b.ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
address, _ := sdk.AccAddressFromBech32(res.AccountAddress)
|
||||||
|
return address, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FeeHistory returns data relevant for fee estimation based on the specified range of blocks.
|
||||||
|
func (b *Backend) FeeHistory(
|
||||||
|
userBlockCount rpc.DecimalOrHex, // number blocks to fetch, maximum is 100
|
||||||
|
lastBlock rpc.BlockNumber, // the block to start search , to oldest
|
||||||
|
rewardPercentiles []float64, // percentiles to fetch reward
|
||||||
|
) (*rpctypes.FeeHistoryResult, error) {
|
||||||
|
blockEnd := int64(lastBlock)
|
||||||
|
|
||||||
|
if blockEnd <= 0 {
|
||||||
|
blockNumber, err := b.BlockNumber()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
blockEnd = int64(blockNumber)
|
||||||
|
}
|
||||||
|
userBlockCountInt := int64(userBlockCount)
|
||||||
|
maxBlockCount := int64(b.cfg.JSONRPC.FeeHistoryCap)
|
||||||
|
if userBlockCountInt > maxBlockCount {
|
||||||
|
return nil, fmt.Errorf("FeeHistory user block count %d higher than %d", userBlockCountInt, maxBlockCount)
|
||||||
|
}
|
||||||
|
blockStart := blockEnd - userBlockCountInt
|
||||||
|
if blockStart < 0 {
|
||||||
|
blockStart = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
blockCount := blockEnd - blockStart
|
||||||
|
|
||||||
|
oldestBlock := (*hexutil.Big)(big.NewInt(blockStart))
|
||||||
|
|
||||||
|
// prepare space
|
||||||
|
reward := make([][]*hexutil.Big, blockCount)
|
||||||
|
rewardCount := len(rewardPercentiles)
|
||||||
|
for i := 0; i < int(blockCount); i++ {
|
||||||
|
reward[i] = make([]*hexutil.Big, rewardCount)
|
||||||
|
}
|
||||||
|
thisBaseFee := make([]*hexutil.Big, blockCount)
|
||||||
|
thisGasUsedRatio := make([]float64, blockCount)
|
||||||
|
|
||||||
|
// rewards should only be calculated if reward percentiles were included
|
||||||
|
calculateRewards := rewardCount != 0
|
||||||
|
|
||||||
|
// fetch block
|
||||||
|
for blockID := blockStart; blockID < blockEnd; blockID++ {
|
||||||
|
index := int32(blockID - blockStart)
|
||||||
|
// tendermint block
|
||||||
|
tendermintblock, err := b.GetTendermintBlockByNumber(rpctypes.BlockNumber(blockID))
|
||||||
|
if tendermintblock == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// eth block
|
||||||
|
ethBlock, err := b.GetBlockByNumber(rpctypes.BlockNumber(blockID), true)
|
||||||
|
if ethBlock == nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// tendermint block result
|
||||||
|
tendermintBlockResult, err := b.GetTendermintBlockResultByNumber(&tendermintblock.Block.Height)
|
||||||
|
if tendermintBlockResult == nil {
|
||||||
|
b.logger.Debug("block result not found", "height", tendermintblock.Block.Height, "error", err.Error())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
oneFeeHistory := rpctypes.OneFeeHistory{}
|
||||||
|
err = b.processBlock(tendermintblock, ðBlock, rewardPercentiles, tendermintBlockResult, &oneFeeHistory)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy
|
||||||
|
thisBaseFee[index] = (*hexutil.Big)(oneFeeHistory.BaseFee)
|
||||||
|
thisGasUsedRatio[index] = oneFeeHistory.GasUsedRatio
|
||||||
|
if calculateRewards {
|
||||||
|
for j := 0; j < rewardCount; j++ {
|
||||||
|
reward[index][j] = (*hexutil.Big)(oneFeeHistory.Reward[j])
|
||||||
|
if reward[index][j] == nil {
|
||||||
|
reward[index][j] = (*hexutil.Big)(big.NewInt(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
feeHistory := rpctypes.FeeHistoryResult{
|
||||||
|
OldestBlock: oldestBlock,
|
||||||
|
BaseFee: thisBaseFee,
|
||||||
|
GasUsedRatio: thisGasUsedRatio,
|
||||||
|
}
|
||||||
|
|
||||||
|
if calculateRewards {
|
||||||
|
feeHistory.Reward = reward
|
||||||
|
}
|
||||||
|
|
||||||
|
return &feeHistory, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SuggestGasTipCap returns the suggested tip cap
|
||||||
|
// Although we don't support tx prioritization yet, but we return a positive value to help client to
|
||||||
|
// mitigate the base fee changes.
|
||||||
|
func (b *Backend) SuggestGasTipCap(baseFee *big.Int) (*big.Int, error) {
|
||||||
|
if baseFee == nil {
|
||||||
|
// london hardfork not enabled or feemarket not enabled
|
||||||
|
return big.NewInt(0), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
params, err := b.queryClient.FeeMarket.Params(b.ctx, &feemarkettypes.QueryParamsRequest{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// calculate the maximum base fee delta in current block, assuming all block gas limit is consumed
|
||||||
|
// ```
|
||||||
|
// GasTarget = GasLimit / ElasticityMultiplier
|
||||||
|
// Delta = BaseFee * (GasUsed - GasTarget) / GasTarget / Denominator
|
||||||
|
// ```
|
||||||
|
// The delta is at maximum when `GasUsed` is equal to `GasLimit`, which is:
|
||||||
|
// ```
|
||||||
|
// MaxDelta = BaseFee * (GasLimit - GasLimit / ElasticityMultiplier) / (GasLimit / ElasticityMultiplier) / Denominator
|
||||||
|
// = BaseFee * (ElasticityMultiplier - 1) / Denominator
|
||||||
|
// ```
|
||||||
|
maxDelta := baseFee.Int64() * (int64(params.Params.ElasticityMultiplier) - 1) / int64(params.Params.BaseFeeChangeDenominator)
|
||||||
|
if maxDelta < 0 {
|
||||||
|
// impossible if the parameter validation passed.
|
||||||
|
maxDelta = 0
|
||||||
|
}
|
||||||
|
return big.NewInt(maxDelta), nil
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
37
rpc/backend/filters.go
Normal file
37
rpc/backend/filters.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package backend
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetLogs returns all the logs from all the ethereum transactions in a block.
|
||||||
|
func (b *Backend) GetLogs(hash common.Hash) ([][]*ethtypes.Log, error) {
|
||||||
|
resBlock, err := b.GetTendermintBlockByHash(hash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resBlock == nil {
|
||||||
|
return nil, errors.Errorf("block not found for hash %s", hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.GetLogsByHeight(&resBlock.Block.Header.Height)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLogsByHeight returns all the logs from all the ethereum transactions in a block.
|
||||||
|
func (b *Backend) GetLogsByHeight(height *int64) ([][]*ethtypes.Log, error) {
|
||||||
|
// NOTE: we query the state in case the tx result logs are not persisted after an upgrade.
|
||||||
|
blockRes, err := b.GetTendermintBlockResultByNumber(height)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetLogsFromBlockResults(blockRes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BloomStatus returns the BloomBitsBlocks and the number of processed sections maintained
|
||||||
|
// by the chain indexer.
|
||||||
|
func (b *Backend) BloomStatus() (uint64, uint64) {
|
||||||
|
return 4096, 0
|
||||||
|
}
|
299
rpc/backend/node_info.go
Normal file
299
rpc/backend/node_info.go
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
package backend
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
sdkmath "cosmossdk.io/math"
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||||
|
sdkcrypto "github.com/cosmos/cosmos-sdk/crypto"
|
||||||
|
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
|
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
|
||||||
|
distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/evmos/ethermint/crypto/ethsecp256k1"
|
||||||
|
rpctypes "github.com/evmos/ethermint/rpc/types"
|
||||||
|
ethermint "github.com/evmos/ethermint/types"
|
||||||
|
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
||||||
|
tmtypes "github.com/tendermint/tendermint/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Accounts returns the list of accounts available to this node.
|
||||||
|
func (b *Backend) Accounts() ([]common.Address, error) {
|
||||||
|
addresses := make([]common.Address, 0) // return [] instead of nil if empty
|
||||||
|
|
||||||
|
infos, err := b.clientCtx.Keyring.List()
|
||||||
|
if err != nil {
|
||||||
|
return addresses, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, info := range infos {
|
||||||
|
pubKey, err := info.GetPubKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
addressBytes := pubKey.Address().Bytes()
|
||||||
|
addresses = append(addresses, common.BytesToAddress(addressBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
return addresses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Syncing returns false in case the node is currently not syncing with the network. It can be up to date or has not
|
||||||
|
// yet received the latest block headers from its pears. In case it is synchronizing:
|
||||||
|
// - startingBlock: block number this node started to synchronize from
|
||||||
|
// - currentBlock: block number this node is currently importing
|
||||||
|
// - highestBlock: block number of the highest block header this node has received from peers
|
||||||
|
// - pulledStates: number of state entries processed until now
|
||||||
|
// - knownStates: number of known state entries that still need to be pulled
|
||||||
|
func (b *Backend) Syncing() (interface{}, error) {
|
||||||
|
status, err := b.clientCtx.Client.Status(b.ctx)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !status.SyncInfo.CatchingUp {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]interface{}{
|
||||||
|
"startingBlock": hexutil.Uint64(status.SyncInfo.EarliestBlockHeight),
|
||||||
|
"currentBlock": hexutil.Uint64(status.SyncInfo.LatestBlockHeight),
|
||||||
|
// "highestBlock": nil, // NA
|
||||||
|
// "pulledStates": nil, // NA
|
||||||
|
// "knownStates": nil, // NA
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetEtherbase sets the etherbase of the miner
|
||||||
|
func (b *Backend) SetEtherbase(etherbase common.Address) bool {
|
||||||
|
delAddr, err := b.GetCoinbase()
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("failed to get coinbase address", "error", err.Error())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
withdrawAddr := sdk.AccAddress(etherbase.Bytes())
|
||||||
|
msg := distributiontypes.NewMsgSetWithdrawAddress(delAddr, withdrawAddr)
|
||||||
|
|
||||||
|
if err := msg.ValidateBasic(); err != nil {
|
||||||
|
b.logger.Debug("tx failed basic validation", "error", err.Error())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble transaction from fields
|
||||||
|
builder, ok := b.clientCtx.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder)
|
||||||
|
if !ok {
|
||||||
|
b.logger.Debug("clientCtx.TxConfig.NewTxBuilder returns unsupported builder", "error", err.Error())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
err = builder.SetMsgs(msg)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Error("builder.SetMsgs failed", "error", err.Error())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch minimun gas price to calculate fees using the configuration.
|
||||||
|
minGasPrices := b.cfg.GetMinGasPrices()
|
||||||
|
if len(minGasPrices) == 0 || minGasPrices.Empty() {
|
||||||
|
b.logger.Debug("the minimun fee is not set")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
minGasPriceValue := minGasPrices[0].Amount
|
||||||
|
denom := minGasPrices[0].Denom
|
||||||
|
|
||||||
|
delCommonAddr := common.BytesToAddress(delAddr.Bytes())
|
||||||
|
nonce, err := b.GetTransactionCount(delCommonAddr, rpctypes.EthPendingBlockNumber)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("failed to get nonce", "error", err.Error())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
txFactory := tx.Factory{}
|
||||||
|
txFactory = txFactory.
|
||||||
|
WithChainID(b.clientCtx.ChainID).
|
||||||
|
WithKeybase(b.clientCtx.Keyring).
|
||||||
|
WithTxConfig(b.clientCtx.TxConfig).
|
||||||
|
WithSequence(uint64(*nonce)).
|
||||||
|
WithGasAdjustment(1.25)
|
||||||
|
|
||||||
|
_, gas, err := tx.CalculateGas(b.clientCtx, txFactory, msg)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("failed to calculate gas", "error", err.Error())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
txFactory = txFactory.WithGas(gas)
|
||||||
|
|
||||||
|
value := new(big.Int).SetUint64(gas * minGasPriceValue.Ceil().TruncateInt().Uint64())
|
||||||
|
fees := sdk.Coins{sdk.NewCoin(denom, sdkmath.NewIntFromBigInt(value))}
|
||||||
|
builder.SetFeeAmount(fees)
|
||||||
|
builder.SetGasLimit(gas)
|
||||||
|
|
||||||
|
keyInfo, err := b.clientCtx.Keyring.KeyByAddress(delAddr)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("failed to get the wallet address using the keyring", "error", err.Error())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tx.Sign(txFactory, keyInfo.Name, builder, false); err != nil {
|
||||||
|
b.logger.Debug("failed to sign tx", "error", err.Error())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode transaction by default Tx encoder
|
||||||
|
txEncoder := b.clientCtx.TxConfig.TxEncoder()
|
||||||
|
txBytes, err := txEncoder(builder.GetTx())
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("failed to encode eth tx using default encoder", "error", err.Error())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
tmHash := common.BytesToHash(tmtypes.Tx(txBytes).Hash())
|
||||||
|
|
||||||
|
// Broadcast transaction in sync mode (default)
|
||||||
|
// NOTE: If error is encountered on the node, the broadcast will not return an error
|
||||||
|
syncCtx := b.clientCtx.WithBroadcastMode(flags.BroadcastSync)
|
||||||
|
rsp, err := syncCtx.BroadcastTx(txBytes)
|
||||||
|
if rsp != nil && rsp.Code != 0 {
|
||||||
|
err = sdkerrors.ABCIError(rsp.Codespace, rsp.Code, rsp.RawLog)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("failed to broadcast tx", "error", err.Error())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
b.logger.Debug("broadcasted tx to set miner withdraw address (etherbase)", "hash", tmHash.String())
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportRawKey armors and encrypts a given raw hex encoded ECDSA key and stores it into the key directory.
|
||||||
|
// The name of the key will have the format "personal_<length-keys>", where <length-keys> is the total number of
|
||||||
|
// keys stored on the keyring.
|
||||||
|
//
|
||||||
|
// NOTE: The key will be both armored and encrypted using the same passphrase.
|
||||||
|
func (b *Backend) ImportRawKey(privkey, password string) (common.Address, error) {
|
||||||
|
priv, err := crypto.HexToECDSA(privkey)
|
||||||
|
if err != nil {
|
||||||
|
return common.Address{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
privKey := ðsecp256k1.PrivKey{Key: crypto.FromECDSA(priv)}
|
||||||
|
|
||||||
|
addr := sdk.AccAddress(privKey.PubKey().Address().Bytes())
|
||||||
|
ethereumAddr := common.BytesToAddress(addr)
|
||||||
|
|
||||||
|
// return if the key has already been imported
|
||||||
|
if _, err := b.clientCtx.Keyring.KeyByAddress(addr); err == nil {
|
||||||
|
return ethereumAddr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore error as we only care about the length of the list
|
||||||
|
list, _ := b.clientCtx.Keyring.List()
|
||||||
|
privKeyName := fmt.Sprintf("personal_%d", len(list))
|
||||||
|
|
||||||
|
armor := sdkcrypto.EncryptArmorPrivKey(privKey, password, ethsecp256k1.KeyType)
|
||||||
|
|
||||||
|
if err := b.clientCtx.Keyring.ImportPrivKey(privKeyName, armor, password); err != nil {
|
||||||
|
return common.Address{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
b.logger.Info("key successfully imported", "name", privKeyName, "address", ethereumAddr.String())
|
||||||
|
|
||||||
|
return ethereumAddr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListAccounts will return a list of addresses for accounts this node manages.
|
||||||
|
func (b *Backend) ListAccounts() ([]common.Address, error) {
|
||||||
|
addrs := []common.Address{}
|
||||||
|
|
||||||
|
list, err := b.clientCtx.Keyring.List()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, info := range list {
|
||||||
|
pubKey, err := info.GetPubKey()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
addrs = append(addrs, common.BytesToAddress(pubKey.Address()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return addrs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAccount will create a new account and returns the address for the new account.
|
||||||
|
func (b *Backend) NewMnemonic(uid string, language keyring.Language, hdPath, bip39Passphrase string, algo keyring.SignatureAlgo) (*keyring.Record, error) {
|
||||||
|
info, _, err := b.clientCtx.Keyring.NewMnemonic(uid, keyring.English, bip39Passphrase, bip39Passphrase, algo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return info, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnprotectedAllowed returns the node configuration value for allowing
|
||||||
|
// unprotected transactions (i.e not replay-protected)
|
||||||
|
func (b Backend) UnprotectedAllowed() bool {
|
||||||
|
return b.allowUnprotectedTxs
|
||||||
|
}
|
||||||
|
|
||||||
|
// RPCGasCap is the global gas cap for eth-call variants.
|
||||||
|
func (b *Backend) RPCGasCap() uint64 {
|
||||||
|
return b.cfg.JSONRPC.GasCap
|
||||||
|
}
|
||||||
|
|
||||||
|
// RPCEVMTimeout is the global evm timeout for eth-call variants.
|
||||||
|
func (b *Backend) RPCEVMTimeout() time.Duration {
|
||||||
|
return b.cfg.JSONRPC.EVMTimeout
|
||||||
|
}
|
||||||
|
|
||||||
|
// RPCGasCap is the global gas cap for eth-call variants.
|
||||||
|
func (b *Backend) RPCTxFeeCap() float64 {
|
||||||
|
return b.cfg.JSONRPC.TxFeeCap
|
||||||
|
}
|
||||||
|
|
||||||
|
// RPCFilterCap is the limit for total number of filters that can be created
|
||||||
|
func (b *Backend) RPCFilterCap() int32 {
|
||||||
|
return b.cfg.JSONRPC.FilterCap
|
||||||
|
}
|
||||||
|
|
||||||
|
// RPCFeeHistoryCap is the limit for total number of blocks that can be fetched
|
||||||
|
func (b *Backend) RPCFeeHistoryCap() int32 {
|
||||||
|
return b.cfg.JSONRPC.FeeHistoryCap
|
||||||
|
}
|
||||||
|
|
||||||
|
// RPCLogsCap defines the max number of results can be returned from single `eth_getLogs` query.
|
||||||
|
func (b *Backend) RPCLogsCap() int32 {
|
||||||
|
return b.cfg.JSONRPC.LogsCap
|
||||||
|
}
|
||||||
|
|
||||||
|
// RPCBlockRangeCap defines the max block range allowed for `eth_getLogs` query.
|
||||||
|
func (b *Backend) RPCBlockRangeCap() int32 {
|
||||||
|
return b.cfg.JSONRPC.BlockRangeCap
|
||||||
|
}
|
||||||
|
|
||||||
|
// RPCMinGasPrice returns the minimum gas price for a transaction obtained from
|
||||||
|
// the node config. If set value is 0, it will default to 20.
|
||||||
|
|
||||||
|
func (b *Backend) RPCMinGasPrice() int64 {
|
||||||
|
evmParams, err := b.queryClient.Params(b.ctx, &evmtypes.QueryParamsRequest{})
|
||||||
|
if err != nil {
|
||||||
|
return ethermint.DefaultGasPrice
|
||||||
|
}
|
||||||
|
|
||||||
|
minGasPrice := b.cfg.GetMinGasPrices()
|
||||||
|
amt := minGasPrice.AmountOf(evmParams.Params.EvmDenom).TruncateInt64()
|
||||||
|
if amt == 0 {
|
||||||
|
return ethermint.DefaultGasPrice
|
||||||
|
}
|
||||||
|
|
||||||
|
return amt
|
||||||
|
}
|
148
rpc/backend/sign_tx.go
Normal file
148
rpc/backend/sign_tx.go
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
package backend
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||||
|
"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/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/signer/core/apitypes"
|
||||||
|
"github.com/evmos/ethermint/ethereum/eip712"
|
||||||
|
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SendTransaction sends transaction based on received args using Node's key to sign it
|
||||||
|
func (b *Backend) SendTransaction(args evmtypes.TransactionArgs) (common.Hash, error) {
|
||||||
|
// Look up the wallet containing the requested signer
|
||||||
|
_, err := b.clientCtx.Keyring.KeyByAddress(sdk.AccAddress(args.From.Bytes()))
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Error("failed to find key in keyring", "address", args.From, "error", err.Error())
|
||||||
|
return common.Hash{}, fmt.Errorf("%s; %s", keystore.ErrNoMatch, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
args, err = b.SetTxDefaults(args)
|
||||||
|
if err != nil {
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := args.ToTransaction()
|
||||||
|
if err := msg.ValidateBasic(); err != nil {
|
||||||
|
b.logger.Debug("tx failed basic validation", "error", err.Error())
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
bn, err := b.BlockNumber()
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("failed to fetch latest block number", "error", err.Error())
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
signer := ethtypes.MakeSigner(b.ChainConfig(), new(big.Int).SetUint64(uint64(bn)))
|
||||||
|
|
||||||
|
// Sign transaction
|
||||||
|
if err := msg.Sign(signer, b.clientCtx.Keyring); err != nil {
|
||||||
|
b.logger.Debug("failed to sign tx", "error", err.Error())
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query params to use the EVM denomination
|
||||||
|
res, err := b.queryClient.QueryClient.Params(b.ctx, &evmtypes.QueryParamsRequest{})
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Error("failed to query evm params", "error", err.Error())
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble transaction from fields
|
||||||
|
tx, err := msg.BuildTx(b.clientCtx.TxConfig.NewTxBuilder(), res.Params.EvmDenom)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Error("build cosmos tx failed", "error", err.Error())
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode transaction by default Tx encoder
|
||||||
|
txEncoder := b.clientCtx.TxConfig.TxEncoder()
|
||||||
|
txBytes, err := txEncoder(tx)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Error("failed to encode eth tx using default encoder", "error", err.Error())
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ethTx := msg.AsTransaction()
|
||||||
|
|
||||||
|
// check the local node config in case unprotected txs are disabled
|
||||||
|
if !b.UnprotectedAllowed() && !ethTx.Protected() {
|
||||||
|
// Ensure only eip155 signed transactions are submitted if EIP155Required is set.
|
||||||
|
return common.Hash{}, errors.New("only replay-protected (EIP-155) transactions allowed over RPC")
|
||||||
|
}
|
||||||
|
|
||||||
|
txHash := ethTx.Hash()
|
||||||
|
|
||||||
|
// Broadcast transaction in sync mode (default)
|
||||||
|
// NOTE: If error is encountered on the node, the broadcast will not return an error
|
||||||
|
syncCtx := b.clientCtx.WithBroadcastMode(flags.BroadcastSync)
|
||||||
|
rsp, err := syncCtx.BroadcastTx(txBytes)
|
||||||
|
if rsp != nil && rsp.Code != 0 {
|
||||||
|
err = sdkerrors.ABCIError(rsp.Codespace, rsp.Code, rsp.RawLog)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Error("failed to broadcast tx", "error", err.Error())
|
||||||
|
return txHash, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return transaction hash
|
||||||
|
return txHash, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign signs the provided data using the private key of address via Geth's signature standard.
|
||||||
|
func (b *Backend) Sign(address common.Address, data hexutil.Bytes) (hexutil.Bytes, error) {
|
||||||
|
from := sdk.AccAddress(address.Bytes())
|
||||||
|
|
||||||
|
_, err := b.clientCtx.Keyring.KeyByAddress(from)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Error("failed to find key in keyring", "address", address.String())
|
||||||
|
return nil, fmt.Errorf("%s; %s", keystore.ErrNoMatch, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign the requested hash with the wallet
|
||||||
|
signature, _, err := b.clientCtx.Keyring.SignByAddress(from, data)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Error("keyring.SignByAddress failed", "address", address.Hex())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
signature[crypto.RecoveryIDOffset] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
|
||||||
|
return signature, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignTypedData signs EIP-712 conformant typed data
|
||||||
|
func (b *Backend) SignTypedData(address common.Address, typedData apitypes.TypedData) (hexutil.Bytes, error) {
|
||||||
|
from := sdk.AccAddress(address.Bytes())
|
||||||
|
|
||||||
|
_, err := b.clientCtx.Keyring.KeyByAddress(from)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Error("failed to find key in keyring", "address", address.String())
|
||||||
|
return nil, fmt.Errorf("%s; %s", keystore.ErrNoMatch, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
sigHash, err := eip712.ComputeTypedDataHash(typedData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign the requested hash with the wallet
|
||||||
|
signature, _, err := b.clientCtx.Keyring.SignByAddress(from, sigHash)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Error("keyring.SignByAddress failed", "address", address.Hex())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
signature[crypto.RecoveryIDOffset] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
|
||||||
|
return signature, nil
|
||||||
|
}
|
181
rpc/backend/tracing.go
Normal file
181
rpc/backend/tracing.go
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
package backend
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
rpctypes "github.com/evmos/ethermint/rpc/types"
|
||||||
|
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TraceTransaction returns the structured logs created during the execution of EVM
|
||||||
|
// and returns them as a JSON object.
|
||||||
|
func (b *Backend) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfig) (interface{}, error) {
|
||||||
|
// Get transaction by hash
|
||||||
|
transaction, err := b.GetTxByEthHash(hash)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("tx not found", "hash", hash)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if block number is 0
|
||||||
|
if transaction.Height == 0 {
|
||||||
|
return nil, errors.New("genesis is not traceable")
|
||||||
|
}
|
||||||
|
|
||||||
|
blk, err := b.GetTendermintBlockByNumber(rpctypes.BlockNumber(transaction.Height))
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("block not found", "height", transaction.Height)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedTxs, err := rpctypes.ParseTxResult(&transaction.TxResult)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse tx events: %s", hash.Hex())
|
||||||
|
}
|
||||||
|
parsedTx := parsedTxs.GetTxByHash(hash)
|
||||||
|
if parsedTx == nil {
|
||||||
|
return nil, fmt.Errorf("ethereum tx not found in msgs: %s", hash.Hex())
|
||||||
|
}
|
||||||
|
|
||||||
|
// check tx index is not out of bound
|
||||||
|
if uint32(len(blk.Block.Txs)) < transaction.Index {
|
||||||
|
b.logger.Debug("tx index out of bounds", "index", transaction.Index, "hash", hash.String(), "height", blk.Block.Height)
|
||||||
|
return nil, fmt.Errorf("transaction not included in block %v", blk.Block.Height)
|
||||||
|
}
|
||||||
|
|
||||||
|
var predecessors []*evmtypes.MsgEthereumTx
|
||||||
|
for _, txBz := range blk.Block.Txs[:transaction.Index] {
|
||||||
|
tx, err := b.clientCtx.TxConfig.TxDecoder()(txBz)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("failed to decode transaction in block", "height", blk.Block.Height, "error", err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, msg := range tx.GetMsgs() {
|
||||||
|
ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
predecessors = append(predecessors, ethMsg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tx, err := b.clientCtx.TxConfig.TxDecoder()(transaction.Tx)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("tx not found", "hash", hash)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// add predecessor messages in current cosmos tx
|
||||||
|
for i := 0; i < parsedTx.MsgIndex; i++ {
|
||||||
|
ethMsg, ok := tx.GetMsgs()[i].(*evmtypes.MsgEthereumTx)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
predecessors = append(predecessors, ethMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
ethMessage, ok := tx.GetMsgs()[parsedTx.MsgIndex].(*evmtypes.MsgEthereumTx)
|
||||||
|
if !ok {
|
||||||
|
b.logger.Debug("invalid transaction type", "type", fmt.Sprintf("%T", tx))
|
||||||
|
return nil, fmt.Errorf("invalid transaction type %T", tx)
|
||||||
|
}
|
||||||
|
|
||||||
|
traceTxRequest := evmtypes.QueryTraceTxRequest{
|
||||||
|
Msg: ethMessage,
|
||||||
|
Predecessors: predecessors,
|
||||||
|
BlockNumber: blk.Block.Height,
|
||||||
|
BlockTime: blk.Block.Time,
|
||||||
|
BlockHash: common.Bytes2Hex(blk.BlockID.Hash),
|
||||||
|
}
|
||||||
|
|
||||||
|
if config != nil {
|
||||||
|
traceTxRequest.TraceConfig = config
|
||||||
|
}
|
||||||
|
|
||||||
|
// minus one to get the context of block beginning
|
||||||
|
contextHeight := transaction.Height - 1
|
||||||
|
if contextHeight < 1 {
|
||||||
|
// 0 is a special value in `ContextWithHeight`
|
||||||
|
contextHeight = 1
|
||||||
|
}
|
||||||
|
traceResult, err := b.queryClient.TraceTx(rpctypes.ContextWithHeight(contextHeight), &traceTxRequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response format is unknown due to custom tracer config param
|
||||||
|
// More information can be found here https://geth.ethereum.org/docs/dapp/tracing-filtered
|
||||||
|
var decodedResult interface{}
|
||||||
|
err = json.Unmarshal(traceResult.Data, &decodedResult)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return decodedResult, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// traceBlock configures a new tracer according to the provided configuration, and
|
||||||
|
// executes all the transactions contained within. The return value will be one item
|
||||||
|
// per transaction, dependent on the requested tracer.
|
||||||
|
func (b *Backend) TraceBlock(height rpctypes.BlockNumber, config *evmtypes.TraceConfig, block *tmrpctypes.ResultBlock) ([]*evmtypes.TxTraceResult, error) {
|
||||||
|
txs := block.Block.Txs
|
||||||
|
txsLength := len(txs)
|
||||||
|
|
||||||
|
if txsLength == 0 {
|
||||||
|
// If there are no transactions return empty array
|
||||||
|
return []*evmtypes.TxTraceResult{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
txDecoder := b.clientCtx.TxConfig.TxDecoder()
|
||||||
|
|
||||||
|
var txsMessages []*evmtypes.MsgEthereumTx
|
||||||
|
for i, tx := range txs {
|
||||||
|
decodedTx, err := txDecoder(tx)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Error("failed to decode transaction", "hash", txs[i].Hash(), "error", err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, msg := range decodedTx.GetMsgs() {
|
||||||
|
ethMessage, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||||
|
if !ok {
|
||||||
|
// Just considers Ethereum transactions
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
txsMessages = append(txsMessages, ethMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// minus one to get the context at the beginning of the block
|
||||||
|
contextHeight := height - 1
|
||||||
|
if contextHeight < 1 {
|
||||||
|
// 0 is a special value for `ContextWithHeight`.
|
||||||
|
contextHeight = 1
|
||||||
|
}
|
||||||
|
ctxWithHeight := rpctypes.ContextWithHeight(int64(contextHeight))
|
||||||
|
|
||||||
|
traceBlockRequest := &evmtypes.QueryTraceBlockRequest{
|
||||||
|
Txs: txsMessages,
|
||||||
|
TraceConfig: config,
|
||||||
|
BlockNumber: block.Block.Height,
|
||||||
|
BlockTime: block.Block.Time,
|
||||||
|
BlockHash: common.Bytes2Hex(block.BlockID.Hash),
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := b.queryClient.TraceBlock(ctxWithHeight, traceBlockRequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
decodedResults := make([]*evmtypes.TxTraceResult, txsLength)
|
||||||
|
if err := json.Unmarshal(res.Data, &decodedResults); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return decodedResults, nil
|
||||||
|
}
|
393
rpc/backend/tx_info.go
Normal file
393
rpc/backend/tx_info.go
Normal file
@ -0,0 +1,393 @@
|
|||||||
|
package backend
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"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/crypto"
|
||||||
|
rpctypes "github.com/evmos/ethermint/rpc/types"
|
||||||
|
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetTransactionByHash returns the Ethereum format transaction identified by Ethereum transaction hash
|
||||||
|
func (b *Backend) GetTransactionByHash(txHash common.Hash) (*rpctypes.RPCTransaction, error) {
|
||||||
|
res, err := b.GetTxByEthHash(txHash)
|
||||||
|
hexTx := txHash.Hex()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// try to find tx in mempool
|
||||||
|
txs, err := b.PendingTransactions()
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("tx not found", "hash", hexTx, "error", err.Error())
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tx := range txs {
|
||||||
|
msg, err := evmtypes.UnwrapEthereumMsg(tx, txHash)
|
||||||
|
if err != nil {
|
||||||
|
// not ethereum tx
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if msg.Hash == hexTx {
|
||||||
|
rpctx, err := rpctypes.NewTransactionFromMsg(
|
||||||
|
msg,
|
||||||
|
common.Hash{},
|
||||||
|
uint64(0),
|
||||||
|
uint64(0),
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return rpctx, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.logger.Debug("tx not found", "hash", hexTx)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !TxSuccessOrExceedsBlockGasLimit(&res.TxResult) {
|
||||||
|
return nil, errors.New("invalid ethereum tx")
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedTxs, err := rpctypes.ParseTxResult(&res.TxResult)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse tx events: %s", hexTx)
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedTx := parsedTxs.GetTxByHash(txHash)
|
||||||
|
if parsedTx == nil {
|
||||||
|
return nil, fmt.Errorf("ethereum tx not found in msgs: %s", hexTx)
|
||||||
|
}
|
||||||
|
|
||||||
|
tx, err := b.clientCtx.TxConfig.TxDecoder()(res.Tx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// the `msgIndex` is inferred from tx events, should be within the bound.
|
||||||
|
msg, ok := tx.GetMsgs()[parsedTx.MsgIndex].(*evmtypes.MsgEthereumTx)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("invalid ethereum tx")
|
||||||
|
}
|
||||||
|
|
||||||
|
block, err := b.clientCtx.Client.Block(b.ctx, &res.Height)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("block not found", "height", res.Height, "error", err.Error())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
blockRes, err := b.GetTendermintBlockResultByNumber(&block.Block.Height)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("block result not found", "height", block.Block.Height, "error", err.Error())
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if parsedTx.EthTxIndex == -1 {
|
||||||
|
// Fallback to find tx index by iterating all valid eth transactions
|
||||||
|
msgs := b.GetEthereumMsgsFromTendermintBlock(block, blockRes)
|
||||||
|
for i := range msgs {
|
||||||
|
if msgs[i].Hash == hexTx {
|
||||||
|
parsedTx.EthTxIndex = int64(i)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if parsedTx.EthTxIndex == -1 {
|
||||||
|
return nil, errors.New("can't find index of ethereum tx")
|
||||||
|
}
|
||||||
|
|
||||||
|
baseFee, err := b.BaseFee(blockRes)
|
||||||
|
if err != nil {
|
||||||
|
// handle the error for pruned node.
|
||||||
|
b.logger.Error("failed to fetch Base Fee from prunned block. Check node prunning configuration", "height", blockRes.Height, "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rpctypes.NewTransactionFromMsg(
|
||||||
|
msg,
|
||||||
|
common.BytesToHash(block.BlockID.Hash.Bytes()),
|
||||||
|
uint64(res.Height),
|
||||||
|
uint64(parsedTx.EthTxIndex),
|
||||||
|
baseFee,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTransactionReceipt returns the transaction receipt identified by hash.
|
||||||
|
func (b *Backend) GetTransactionReceipt(hash common.Hash) (map[string]interface{}, error) {
|
||||||
|
hexTx := hash.Hex()
|
||||||
|
b.logger.Debug("eth_getTransactionReceipt", "hash", hexTx)
|
||||||
|
|
||||||
|
res, err := b.GetTxByEthHash(hash)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("tx not found", "hash", hexTx, "error", err.Error())
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't ignore the txs which exceed block gas limit.
|
||||||
|
if !TxSuccessOrExceedsBlockGasLimit(&res.TxResult) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedTxs, err := rpctypes.ParseTxResult(&res.TxResult)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse tx events: %s, %v", hexTx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedTx := parsedTxs.GetTxByHash(hash)
|
||||||
|
if parsedTx == nil {
|
||||||
|
return nil, fmt.Errorf("ethereum tx not found in msgs: %s", hexTx)
|
||||||
|
}
|
||||||
|
|
||||||
|
resBlock, err := b.clientCtx.Client.Block(b.ctx, &res.Height)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("block not found", "height", res.Height, "error", err.Error())
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tx, err := b.clientCtx.TxConfig.TxDecoder()(res.Tx)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("decoding failed", "error", err.Error())
|
||||||
|
return nil, fmt.Errorf("failed to decode tx: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res.TxResult.Code != 0 {
|
||||||
|
// tx failed, we should return gas limit as gas used, because that's how the fee get deducted.
|
||||||
|
for i := 0; i <= parsedTx.MsgIndex; i++ {
|
||||||
|
gasLimit := tx.GetMsgs()[i].(*evmtypes.MsgEthereumTx).GetGas()
|
||||||
|
parsedTxs.Txs[i].GasUsed = gasLimit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the `msgIndex` is inferred from tx events, should be within the bound,
|
||||||
|
// and the tx is found by eth tx hash, so the msg type must be correct.
|
||||||
|
ethMsg := tx.GetMsgs()[parsedTx.MsgIndex].(*evmtypes.MsgEthereumTx)
|
||||||
|
|
||||||
|
txData, err := evmtypes.UnpackTxData(ethMsg.Data)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Error("failed to unpack tx data", "error", err.Error())
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cumulativeGasUsed := uint64(0)
|
||||||
|
blockRes, err := b.GetTendermintBlockResultByNumber(&res.Height)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("failed to retrieve block results", "height", res.Height, "error", err.Error())
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
for i := 0; i < int(res.Index) && i < len(blockRes.TxsResults); i++ {
|
||||||
|
cumulativeGasUsed += uint64(blockRes.TxsResults[i].GasUsed)
|
||||||
|
}
|
||||||
|
cumulativeGasUsed += parsedTxs.AccumulativeGasUsed(parsedTx.MsgIndex)
|
||||||
|
|
||||||
|
// Get the transaction result from the log
|
||||||
|
var status hexutil.Uint
|
||||||
|
if res.TxResult.Code != 0 || parsedTx.Failed {
|
||||||
|
status = hexutil.Uint(ethtypes.ReceiptStatusFailed)
|
||||||
|
} else {
|
||||||
|
status = hexutil.Uint(ethtypes.ReceiptStatusSuccessful)
|
||||||
|
}
|
||||||
|
|
||||||
|
from, err := ethMsg.GetSender(b.chainID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse tx logs from events
|
||||||
|
logs, err := parsedTx.ParseTxLogs()
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("failed to parse logs", "hash", hexTx, "error", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if parsedTx.EthTxIndex == -1 {
|
||||||
|
// Fallback to find tx index by iterating all valid eth transactions
|
||||||
|
msgs := b.GetEthereumMsgsFromTendermintBlock(resBlock, blockRes)
|
||||||
|
for i := range msgs {
|
||||||
|
if msgs[i].Hash == hexTx {
|
||||||
|
parsedTx.EthTxIndex = int64(i)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if parsedTx.EthTxIndex == -1 {
|
||||||
|
return nil, errors.New("can't find index of ethereum tx")
|
||||||
|
}
|
||||||
|
|
||||||
|
receipt := map[string]interface{}{
|
||||||
|
// Consensus fields: These fields are defined by the Yellow Paper
|
||||||
|
"status": status,
|
||||||
|
"cumulativeGasUsed": hexutil.Uint64(cumulativeGasUsed),
|
||||||
|
"logsBloom": ethtypes.BytesToBloom(ethtypes.LogsBloom(logs)),
|
||||||
|
"logs": logs,
|
||||||
|
|
||||||
|
// Implementation fields: These fields are added by geth when processing a transaction.
|
||||||
|
// They are stored in the chain database.
|
||||||
|
"transactionHash": hash,
|
||||||
|
"contractAddress": nil,
|
||||||
|
"gasUsed": hexutil.Uint64(parsedTx.GasUsed),
|
||||||
|
|
||||||
|
// Inclusion information: These fields provide information about the inclusion of the
|
||||||
|
// transaction corresponding to this receipt.
|
||||||
|
"blockHash": common.BytesToHash(resBlock.Block.Header.Hash()).Hex(),
|
||||||
|
"blockNumber": hexutil.Uint64(res.Height),
|
||||||
|
"transactionIndex": hexutil.Uint64(parsedTx.EthTxIndex),
|
||||||
|
|
||||||
|
// sender and receiver (contract or EOA) addreses
|
||||||
|
"from": from,
|
||||||
|
"to": txData.GetTo(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if logs == nil {
|
||||||
|
receipt["logs"] = [][]*ethtypes.Log{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the ContractAddress is 20 0x0 bytes, assume it is not a contract creation
|
||||||
|
if txData.GetTo() == nil {
|
||||||
|
receipt["contractAddress"] = crypto.CreateAddress(from, txData.GetNonce())
|
||||||
|
}
|
||||||
|
|
||||||
|
if dynamicTx, ok := txData.(*evmtypes.DynamicFeeTx); ok {
|
||||||
|
baseFee, err := b.BaseFee(blockRes)
|
||||||
|
if err != nil {
|
||||||
|
// tolerate the error for pruned node.
|
||||||
|
b.logger.Error("fetch basefee failed, node is pruned?", "height", res.Height, "error", err)
|
||||||
|
} else {
|
||||||
|
receipt["effectiveGasPrice"] = hexutil.Big(*dynamicTx.EffectiveGasPrice(baseFee))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return receipt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTransactionByBlockHashAndIndex returns the transaction identified by hash and index.
|
||||||
|
func (b *Backend) GetTransactionByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) (*rpctypes.RPCTransaction, error) {
|
||||||
|
b.logger.Debug("eth_getTransactionByBlockHashAndIndex", "hash", hash.Hex(), "index", idx)
|
||||||
|
|
||||||
|
block, err := b.clientCtx.Client.BlockByHash(b.ctx, hash.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("block not found", "hash", hash.Hex(), "error", err.Error())
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if block.Block == nil {
|
||||||
|
b.logger.Debug("block not found", "hash", hash.Hex())
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.GetTransactionByBlockAndIndex(block, idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTransactionByBlockNumberAndIndex returns the transaction identified by number and index.
|
||||||
|
func (b *Backend) GetTransactionByBlockNumberAndIndex(blockNum rpctypes.BlockNumber, idx hexutil.Uint) (*rpctypes.RPCTransaction, error) {
|
||||||
|
b.logger.Debug("eth_getTransactionByBlockNumberAndIndex", "number", blockNum, "index", idx)
|
||||||
|
|
||||||
|
block, err := b.GetTendermintBlockByNumber(blockNum)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("block not found", "height", blockNum.Int64(), "error", err.Error())
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if block.Block == nil {
|
||||||
|
b.logger.Debug("block not found", "height", blockNum.Int64())
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.GetTransactionByBlockAndIndex(block, idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTxByEthHash uses `/tx_query` to find transaction by ethereum tx hash
|
||||||
|
// TODO: Don't need to convert once hashing is fixed on Tendermint
|
||||||
|
// https://github.com/tendermint/tendermint/issues/6539
|
||||||
|
func (b *Backend) GetTxByEthHash(hash common.Hash) (*tmrpctypes.ResultTx, error) {
|
||||||
|
query := fmt.Sprintf("%s.%s='%s'", evmtypes.TypeMsgEthereumTx, evmtypes.AttributeKeyEthereumTxHash, hash.Hex())
|
||||||
|
resTxs, err := b.clientCtx.Client.TxSearch(b.ctx, query, false, nil, nil, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(resTxs.Txs) == 0 {
|
||||||
|
return nil, errors.Errorf("ethereum tx not found for hash %s", hash.Hex())
|
||||||
|
}
|
||||||
|
return resTxs.Txs[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTxByTxIndex uses `/tx_query` to find transaction by tx index of valid ethereum txs
|
||||||
|
func (b *Backend) GetTxByTxIndex(height int64, index uint) (*tmrpctypes.ResultTx, error) {
|
||||||
|
query := fmt.Sprintf("tx.height=%d AND %s.%s=%d",
|
||||||
|
height, evmtypes.TypeMsgEthereumTx,
|
||||||
|
evmtypes.AttributeKeyTxIndex, index,
|
||||||
|
)
|
||||||
|
resTxs, err := b.clientCtx.Client.TxSearch(b.ctx, query, false, nil, nil, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(resTxs.Txs) == 0 {
|
||||||
|
return nil, errors.Errorf("ethereum tx not found for block %d index %d", height, index)
|
||||||
|
}
|
||||||
|
return resTxs.Txs[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTransactionByBlockAndIndex is the common code shared by `GetTransactionByBlockNumberAndIndex` and `GetTransactionByBlockHashAndIndex`.
|
||||||
|
func (b *Backend) GetTransactionByBlockAndIndex(block *tmrpctypes.ResultBlock, idx hexutil.Uint) (*rpctypes.RPCTransaction, error) {
|
||||||
|
blockRes, err := b.GetTendermintBlockResultByNumber(&block.Block.Height)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var msg *evmtypes.MsgEthereumTx
|
||||||
|
// try /tx_search first
|
||||||
|
res, err := b.GetTxByTxIndex(block.Block.Height, uint(idx))
|
||||||
|
if err == nil {
|
||||||
|
tx, err := b.clientCtx.TxConfig.TxDecoder()(res.Tx)
|
||||||
|
if err != nil {
|
||||||
|
b.logger.Debug("invalid ethereum tx", "height", block.Block.Header, "index", idx)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedTxs, err := rpctypes.ParseTxResult(&res.TxResult)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse tx events: %d, %v", idx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedTx := parsedTxs.GetTxByTxIndex(int(idx))
|
||||||
|
if parsedTx == nil {
|
||||||
|
return nil, fmt.Errorf("ethereum tx not found in msgs: %d", idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ok bool
|
||||||
|
// msgIndex is inferred from tx events, should be within bound.
|
||||||
|
msg, ok = tx.GetMsgs()[parsedTx.MsgIndex].(*evmtypes.MsgEthereumTx)
|
||||||
|
if !ok {
|
||||||
|
b.logger.Debug("invalid ethereum tx", "height", block.Block.Header, "index", idx)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
i := int(idx)
|
||||||
|
ethMsgs := b.GetEthereumMsgsFromTendermintBlock(block, blockRes)
|
||||||
|
if i >= len(ethMsgs) {
|
||||||
|
b.logger.Debug("block txs index out of bound", "index", i)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
msg = ethMsgs[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
baseFee, err := b.BaseFee(blockRes)
|
||||||
|
if err != nil {
|
||||||
|
// handle the error for pruned node.
|
||||||
|
b.logger.Error("failed to fetch Base Fee from prunned block. Check node prunning configuration", "height", block.Block.Height, "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rpctypes.NewTransactionFromMsg(
|
||||||
|
msg,
|
||||||
|
common.BytesToHash(block.Block.Hash()),
|
||||||
|
uint64(block.Block.Height),
|
||||||
|
uint64(idx),
|
||||||
|
baseFee,
|
||||||
|
)
|
||||||
|
}
|
@ -3,7 +3,6 @@ package backend
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"sort"
|
"sort"
|
||||||
@ -44,133 +43,6 @@ func (s sortGasAndReward) Less(i, j int) bool {
|
|||||||
return s[i].reward.Cmp(s[j].reward) < 0
|
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.
|
// getAccountNonce returns the account nonce for the given account address.
|
||||||
// If the pending value is true, it will iterate over the mempool (pending)
|
// If the pending value is true, it will iterate over the mempool (pending)
|
||||||
// txs in order to compute and return the pending tx sequence.
|
// txs in order to compute and return the pending tx sequence.
|
||||||
|
@ -2,7 +2,6 @@ package debug
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@ -14,11 +13,9 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
|
|
||||||
|
|
||||||
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
stderrors "github.com/pkg/errors"
|
stderrors "github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/server"
|
"github.com/cosmos/cosmos-sdk/server"
|
||||||
@ -43,27 +40,22 @@ type HandlerT struct {
|
|||||||
|
|
||||||
// API is the collection of tracing APIs exposed over the private debugging endpoint.
|
// API is the collection of tracing APIs exposed over the private debugging endpoint.
|
||||||
type API struct {
|
type API struct {
|
||||||
ctx *server.Context
|
ctx *server.Context
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
backend backend.EVMBackend
|
backend backend.EVMBackend
|
||||||
clientCtx client.Context
|
handler *HandlerT
|
||||||
queryClient *rpctypes.QueryClient
|
|
||||||
handler *HandlerT
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAPI creates a new API definition for the tracing methods of the Ethereum service.
|
// NewAPI creates a new API definition for the tracing methods of the Ethereum service.
|
||||||
func NewAPI(
|
func NewAPI(
|
||||||
ctx *server.Context,
|
ctx *server.Context,
|
||||||
backend backend.EVMBackend,
|
backend backend.EVMBackend,
|
||||||
clientCtx client.Context,
|
|
||||||
) *API {
|
) *API {
|
||||||
return &API{
|
return &API{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
logger: ctx.Logger.With("module", "debug"),
|
logger: ctx.Logger.With("module", "debug"),
|
||||||
backend: backend,
|
backend: backend,
|
||||||
clientCtx: clientCtx,
|
handler: new(HandlerT),
|
||||||
queryClient: rpctypes.NewQueryClient(clientCtx),
|
|
||||||
handler: new(HandlerT),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,109 +63,7 @@ func NewAPI(
|
|||||||
// and returns them as a JSON object.
|
// and returns them as a JSON object.
|
||||||
func (a *API) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfig) (interface{}, error) {
|
func (a *API) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfig) (interface{}, error) {
|
||||||
a.logger.Debug("debug_traceTransaction", "hash", hash)
|
a.logger.Debug("debug_traceTransaction", "hash", hash)
|
||||||
// Get transaction by hash
|
return a.backend.TraceTransaction(hash, config)
|
||||||
transaction, err := a.backend.GetTxByEthHash(hash)
|
|
||||||
if err != nil {
|
|
||||||
a.logger.Debug("tx not found", "hash", hash)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if block number is 0
|
|
||||||
if transaction.Height == 0 {
|
|
||||||
return nil, errors.New("genesis is not traceable")
|
|
||||||
}
|
|
||||||
|
|
||||||
blk, err := a.backend.GetTendermintBlockByNumber(rpctypes.BlockNumber(transaction.Height))
|
|
||||||
if err != nil {
|
|
||||||
a.logger.Debug("block not found", "height", transaction.Height)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
parsedTxs, err := rpctypes.ParseTxResult(&transaction.TxResult)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse tx events: %s", hash.Hex())
|
|
||||||
}
|
|
||||||
parsedTx := parsedTxs.GetTxByHash(hash)
|
|
||||||
if parsedTx == nil {
|
|
||||||
return nil, fmt.Errorf("ethereum tx not found in msgs: %s", hash.Hex())
|
|
||||||
}
|
|
||||||
|
|
||||||
// check tx index is not out of bound
|
|
||||||
if uint32(len(blk.Block.Txs)) < transaction.Index {
|
|
||||||
a.logger.Debug("tx index out of bounds", "index", transaction.Index, "hash", hash.String(), "height", blk.Block.Height)
|
|
||||||
return nil, fmt.Errorf("transaction not included in block %v", blk.Block.Height)
|
|
||||||
}
|
|
||||||
|
|
||||||
var predecessors []*evmtypes.MsgEthereumTx
|
|
||||||
for _, txBz := range blk.Block.Txs[:transaction.Index] {
|
|
||||||
tx, err := a.clientCtx.TxConfig.TxDecoder()(txBz)
|
|
||||||
if err != nil {
|
|
||||||
a.logger.Debug("failed to decode transaction in block", "height", blk.Block.Height, "error", err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, msg := range tx.GetMsgs() {
|
|
||||||
ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
predecessors = append(predecessors, ethMsg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tx, err := a.clientCtx.TxConfig.TxDecoder()(transaction.Tx)
|
|
||||||
if err != nil {
|
|
||||||
a.logger.Debug("tx not found", "hash", hash)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// add predecessor messages in current cosmos tx
|
|
||||||
for i := 0; i < parsedTx.MsgIndex; i++ {
|
|
||||||
ethMsg, ok := tx.GetMsgs()[i].(*evmtypes.MsgEthereumTx)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
predecessors = append(predecessors, ethMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
ethMessage, ok := tx.GetMsgs()[parsedTx.MsgIndex].(*evmtypes.MsgEthereumTx)
|
|
||||||
if !ok {
|
|
||||||
a.logger.Debug("invalid transaction type", "type", fmt.Sprintf("%T", tx))
|
|
||||||
return nil, fmt.Errorf("invalid transaction type %T", tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
traceTxRequest := evmtypes.QueryTraceTxRequest{
|
|
||||||
Msg: ethMessage,
|
|
||||||
Predecessors: predecessors,
|
|
||||||
BlockNumber: blk.Block.Height,
|
|
||||||
BlockTime: blk.Block.Time,
|
|
||||||
BlockHash: common.Bytes2Hex(blk.BlockID.Hash),
|
|
||||||
}
|
|
||||||
|
|
||||||
if config != nil {
|
|
||||||
traceTxRequest.TraceConfig = config
|
|
||||||
}
|
|
||||||
|
|
||||||
// minus one to get the context of block beginning
|
|
||||||
contextHeight := transaction.Height - 1
|
|
||||||
if contextHeight < 1 {
|
|
||||||
// 0 is a special value in `ContextWithHeight`
|
|
||||||
contextHeight = 1
|
|
||||||
}
|
|
||||||
traceResult, err := a.queryClient.TraceTx(rpctypes.ContextWithHeight(contextHeight), &traceTxRequest)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Response format is unknown due to custom tracer config param
|
|
||||||
// More information can be found here https://geth.ethereum.org/docs/dapp/tracing-filtered
|
|
||||||
var decodedResult interface{}
|
|
||||||
err = json.Unmarshal(traceResult.Data, &decodedResult)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return decodedResult, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TraceBlockByNumber returns the structured logs created during the execution of
|
// TraceBlockByNumber returns the structured logs created during the execution of
|
||||||
@ -190,7 +80,7 @@ func (a *API) TraceBlockByNumber(height rpctypes.BlockNumber, config *evmtypes.T
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return a.traceBlock(height, config, resBlock)
|
return a.backend.TraceBlock(rpctypes.BlockNumber(resBlock.Block.Height), config, resBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TraceBlockByHash returns the structured logs created during the execution of
|
// TraceBlockByHash returns the structured logs created during the execution of
|
||||||
@ -209,68 +99,7 @@ func (a *API) TraceBlockByHash(hash common.Hash, config *evmtypes.TraceConfig) (
|
|||||||
return nil, errors.New("block not found")
|
return nil, errors.New("block not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
return a.traceBlock(rpctypes.BlockNumber(resBlock.Block.Height), config, resBlock)
|
return a.backend.TraceBlock(rpctypes.BlockNumber(resBlock.Block.Height), config, resBlock)
|
||||||
}
|
|
||||||
|
|
||||||
// traceBlock configures a new tracer according to the provided configuration, and
|
|
||||||
// executes all the transactions contained within. The return value will be one item
|
|
||||||
// per transaction, dependent on the requested tracer.
|
|
||||||
func (a *API) traceBlock(height rpctypes.BlockNumber, config *evmtypes.TraceConfig, block *tmrpctypes.ResultBlock) ([]*evmtypes.TxTraceResult, error) {
|
|
||||||
txs := block.Block.Txs
|
|
||||||
txsLength := len(txs)
|
|
||||||
|
|
||||||
if txsLength == 0 {
|
|
||||||
// If there are no transactions return empty array
|
|
||||||
return []*evmtypes.TxTraceResult{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
txDecoder := a.clientCtx.TxConfig.TxDecoder()
|
|
||||||
|
|
||||||
var txsMessages []*evmtypes.MsgEthereumTx
|
|
||||||
for i, tx := range txs {
|
|
||||||
decodedTx, err := txDecoder(tx)
|
|
||||||
if err != nil {
|
|
||||||
a.logger.Error("failed to decode transaction", "hash", txs[i].Hash(), "error", err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, msg := range decodedTx.GetMsgs() {
|
|
||||||
ethMessage, ok := msg.(*evmtypes.MsgEthereumTx)
|
|
||||||
if !ok {
|
|
||||||
// Just considers Ethereum transactions
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
txsMessages = append(txsMessages, ethMessage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// minus one to get the context at the beginning of the block
|
|
||||||
contextHeight := height - 1
|
|
||||||
if contextHeight < 1 {
|
|
||||||
// 0 is a special value for `ContextWithHeight`.
|
|
||||||
contextHeight = 1
|
|
||||||
}
|
|
||||||
ctxWithHeight := rpctypes.ContextWithHeight(int64(contextHeight))
|
|
||||||
|
|
||||||
traceBlockRequest := &evmtypes.QueryTraceBlockRequest{
|
|
||||||
Txs: txsMessages,
|
|
||||||
TraceConfig: config,
|
|
||||||
BlockNumber: block.Block.Height,
|
|
||||||
BlockTime: block.Block.Time,
|
|
||||||
BlockHash: common.Bytes2Hex(block.BlockID.Hash),
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := a.queryClient.TraceBlock(ctxWithHeight, traceBlockRequest)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
decodedResults := make([]*evmtypes.TxTraceResult, txsLength)
|
|
||||||
if err := json.Unmarshal(res.Data, &decodedResults); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return decodedResults, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BlockProfile turns on goroutine profiling for nsec seconds and writes profile data to
|
// BlockProfile turns on goroutine profiling for nsec seconds and writes profile data to
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -23,6 +23,17 @@ import (
|
|||||||
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// FilterAPI gathers
|
||||||
|
type FilterAPI interface {
|
||||||
|
GetLogs(ctx context.Context, crit filters.FilterCriteria) ([]*ethtypes.Log, error)
|
||||||
|
GetFilterChanges(id rpc.ID) (interface{}, error)
|
||||||
|
GetFilterLogs(ctx context.Context, id rpc.ID) ([]*ethtypes.Log, error)
|
||||||
|
NewBlockFilter() rpc.ID
|
||||||
|
NewFilter(criteria filters.FilterCriteria) (rpc.ID, error)
|
||||||
|
NewPendingTransactionFilter() rpc.ID
|
||||||
|
UninstallFilter(id rpc.ID) bool
|
||||||
|
}
|
||||||
|
|
||||||
// Backend defines the methods requided by the PublicFilterAPI backend
|
// Backend defines the methods requided by the PublicFilterAPI backend
|
||||||
type Backend interface {
|
type Backend interface {
|
||||||
GetBlockByNumber(blockNum types.BlockNumber, fullTx bool) (map[string]interface{}, error)
|
GetBlockByNumber(blockNum types.BlockNumber, fullTx bool) (map[string]interface{}, error)
|
||||||
|
@ -1,187 +1,36 @@
|
|||||||
package miner
|
package miner
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/big"
|
|
||||||
|
|
||||||
sdkmath "cosmossdk.io/math"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
|
||||||
"github.com/cosmos/cosmos-sdk/server"
|
"github.com/cosmos/cosmos-sdk/server"
|
||||||
sdkconfig "github.com/cosmos/cosmos-sdk/server/config"
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
|
||||||
|
|
||||||
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
|
|
||||||
distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
||||||
|
|
||||||
"github.com/tendermint/tendermint/libs/log"
|
"github.com/tendermint/tendermint/libs/log"
|
||||||
tmtypes "github.com/tendermint/tendermint/types"
|
|
||||||
|
|
||||||
"github.com/evmos/ethermint/rpc/backend"
|
"github.com/evmos/ethermint/rpc/backend"
|
||||||
rpctypes "github.com/evmos/ethermint/rpc/types"
|
|
||||||
"github.com/evmos/ethermint/server/config"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// API is the private miner prefixed set of APIs in the Miner JSON-RPC spec.
|
// API is the private miner prefixed set of APIs in the Miner JSON-RPC spec.
|
||||||
type API struct {
|
type API struct {
|
||||||
ctx *server.Context
|
ctx *server.Context
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
clientCtx client.Context
|
backend backend.EVMBackend
|
||||||
backend backend.EVMBackend
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPrivateAPI creates an instance of the Miner API.
|
// NewPrivateAPI creates an instance of the Miner API.
|
||||||
func NewPrivateAPI(
|
func NewPrivateAPI(
|
||||||
ctx *server.Context,
|
ctx *server.Context,
|
||||||
clientCtx client.Context,
|
|
||||||
backend backend.EVMBackend,
|
backend backend.EVMBackend,
|
||||||
) *API {
|
) *API {
|
||||||
return &API{
|
return &API{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
clientCtx: clientCtx,
|
logger: ctx.Logger.With("api", "miner"),
|
||||||
logger: ctx.Logger.With("api", "miner"),
|
backend: backend,
|
||||||
backend: backend,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetEtherbase sets the etherbase of the miner
|
// SetEtherbase sets the etherbase of the miner
|
||||||
func (api *API) SetEtherbase(etherbase common.Address) bool {
|
func (api *API) SetEtherbase(etherbase common.Address) bool {
|
||||||
api.logger.Debug("miner_setEtherbase")
|
api.logger.Debug("miner_setEtherbase")
|
||||||
|
return api.backend.SetEtherbase(etherbase)
|
||||||
delAddr, err := api.backend.GetCoinbase()
|
|
||||||
if err != nil {
|
|
||||||
api.logger.Debug("failed to get coinbase address", "error", err.Error())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
withdrawAddr := sdk.AccAddress(etherbase.Bytes())
|
|
||||||
msg := distributiontypes.NewMsgSetWithdrawAddress(delAddr, withdrawAddr)
|
|
||||||
|
|
||||||
if err := msg.ValidateBasic(); err != nil {
|
|
||||||
api.logger.Debug("tx failed basic validation", "error", err.Error())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assemble transaction from fields
|
|
||||||
builder, ok := api.clientCtx.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder)
|
|
||||||
if !ok {
|
|
||||||
api.logger.Debug("clientCtx.TxConfig.NewTxBuilder returns unsupported builder", "error", err.Error())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
err = builder.SetMsgs(msg)
|
|
||||||
if err != nil {
|
|
||||||
api.logger.Error("builder.SetMsgs failed", "error", err.Error())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch minimun gas price to calculate fees using the configuration.
|
|
||||||
appConf := config.GetConfig(api.ctx.Viper)
|
|
||||||
|
|
||||||
minGasPrices := appConf.GetMinGasPrices()
|
|
||||||
if len(minGasPrices) == 0 || minGasPrices.Empty() {
|
|
||||||
api.logger.Debug("the minimun fee is not set")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
minGasPriceValue := minGasPrices[0].Amount
|
|
||||||
denom := minGasPrices[0].Denom
|
|
||||||
|
|
||||||
delCommonAddr := common.BytesToAddress(delAddr.Bytes())
|
|
||||||
nonce, err := api.backend.GetTransactionCount(delCommonAddr, rpctypes.EthPendingBlockNumber)
|
|
||||||
if err != nil {
|
|
||||||
api.logger.Debug("failed to get nonce", "error", err.Error())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
txFactory := tx.Factory{}
|
|
||||||
txFactory = txFactory.
|
|
||||||
WithChainID(api.clientCtx.ChainID).
|
|
||||||
WithKeybase(api.clientCtx.Keyring).
|
|
||||||
WithTxConfig(api.clientCtx.TxConfig).
|
|
||||||
WithSequence(uint64(*nonce)).
|
|
||||||
WithGasAdjustment(1.25)
|
|
||||||
|
|
||||||
_, gas, err := tx.CalculateGas(api.clientCtx, txFactory, msg)
|
|
||||||
if err != nil {
|
|
||||||
api.logger.Debug("failed to calculate gas", "error", err.Error())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
txFactory = txFactory.WithGas(gas)
|
|
||||||
|
|
||||||
value := new(big.Int).SetUint64(gas * minGasPriceValue.Ceil().TruncateInt().Uint64())
|
|
||||||
fees := sdk.Coins{sdk.NewCoin(denom, sdkmath.NewIntFromBigInt(value))}
|
|
||||||
builder.SetFeeAmount(fees)
|
|
||||||
builder.SetGasLimit(gas)
|
|
||||||
|
|
||||||
keyInfo, err := api.clientCtx.Keyring.KeyByAddress(delAddr)
|
|
||||||
if err != nil {
|
|
||||||
api.logger.Debug("failed to get the wallet address using the keyring", "error", err.Error())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := tx.Sign(txFactory, keyInfo.Name, builder, false); err != nil {
|
|
||||||
api.logger.Debug("failed to sign tx", "error", err.Error())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode transaction by default Tx encoder
|
|
||||||
txEncoder := api.clientCtx.TxConfig.TxEncoder()
|
|
||||||
txBytes, err := txEncoder(builder.GetTx())
|
|
||||||
if err != nil {
|
|
||||||
api.logger.Debug("failed to encode eth tx using default encoder", "error", err.Error())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
tmHash := common.BytesToHash(tmtypes.Tx(txBytes).Hash())
|
|
||||||
|
|
||||||
// Broadcast transaction in sync mode (default)
|
|
||||||
// NOTE: If error is encountered on the node, the broadcast will not return an error
|
|
||||||
syncCtx := api.clientCtx.WithBroadcastMode(flags.BroadcastSync)
|
|
||||||
rsp, err := syncCtx.BroadcastTx(txBytes)
|
|
||||||
if rsp != nil && rsp.Code != 0 {
|
|
||||||
err = sdkerrors.ABCIError(rsp.Codespace, rsp.Code, rsp.RawLog)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
api.logger.Debug("failed to broadcast tx", "error", err.Error())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
api.logger.Debug("broadcasted tx to set miner withdraw address (etherbase)", "hash", tmHash.String())
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetGasPrice sets the minimum accepted gas price for the miner.
|
|
||||||
// NOTE: this function accepts only integers to have the same interface than go-eth
|
|
||||||
// to use float values, the gas prices must be configured using the configuration file
|
|
||||||
func (api *API) SetGasPrice(gasPrice hexutil.Big) bool {
|
|
||||||
api.logger.Info(api.ctx.Viper.ConfigFileUsed())
|
|
||||||
appConf := config.GetConfig(api.ctx.Viper)
|
|
||||||
|
|
||||||
var unit string
|
|
||||||
minGasPrices := appConf.GetMinGasPrices()
|
|
||||||
|
|
||||||
// fetch the base denom from the sdk Config in case it's not currently defined on the node config
|
|
||||||
if len(minGasPrices) == 0 || minGasPrices.Empty() {
|
|
||||||
var err error
|
|
||||||
unit, err = sdk.GetBaseDenom()
|
|
||||||
if err != nil {
|
|
||||||
api.logger.Debug("could not get the denom of smallest unit registered", "error", err.Error())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
unit = minGasPrices[0].Denom
|
|
||||||
}
|
|
||||||
|
|
||||||
c := sdk.NewDecCoin(unit, sdkmath.NewIntFromBigInt(gasPrice.ToInt()))
|
|
||||||
|
|
||||||
appConf.SetMinGasPrices(sdk.DecCoins{c})
|
|
||||||
sdkconfig.WriteConfigFile(api.ctx.Viper.ConfigFileUsed(), appConf)
|
|
||||||
api.logger.Info("Your configuration file was modified. Please RESTART your node.", "gas-price", c.String())
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
@ -8,14 +8,11 @@ import (
|
|||||||
|
|
||||||
"github.com/evmos/ethermint/rpc/backend"
|
"github.com/evmos/ethermint/rpc/backend"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
|
|
||||||
"github.com/evmos/ethermint/crypto/hd"
|
"github.com/evmos/ethermint/crypto/hd"
|
||||||
ethermint "github.com/evmos/ethermint/types"
|
ethermint "github.com/evmos/ethermint/types"
|
||||||
|
|
||||||
"github.com/tendermint/tendermint/libs/log"
|
"github.com/tendermint/tendermint/libs/log"
|
||||||
|
|
||||||
sdkcrypto "github.com/cosmos/cosmos-sdk/crypto"
|
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
@ -24,13 +21,11 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
|
||||||
"github.com/evmos/ethermint/crypto/ethsecp256k1"
|
|
||||||
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PrivateAccountAPI is the personal_ prefixed set of APIs in the Web3 JSON-RPC spec.
|
// PrivateAccountAPI is the personal_ prefixed set of APIs in the Web3 JSON-RPC spec.
|
||||||
type PrivateAccountAPI struct {
|
type PrivateAccountAPI struct {
|
||||||
clientCtx client.Context
|
|
||||||
backend backend.EVMBackend
|
backend backend.EVMBackend
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
hdPathIter ethermint.HDPathIterator
|
hdPathIter ethermint.HDPathIterator
|
||||||
@ -39,7 +34,6 @@ type PrivateAccountAPI struct {
|
|||||||
// NewAPI creates an instance of the public Personal Eth API.
|
// NewAPI creates an instance of the public Personal Eth API.
|
||||||
func NewAPI(
|
func NewAPI(
|
||||||
logger log.Logger,
|
logger log.Logger,
|
||||||
clientCtx client.Context,
|
|
||||||
backend backend.EVMBackend,
|
backend backend.EVMBackend,
|
||||||
) *PrivateAccountAPI {
|
) *PrivateAccountAPI {
|
||||||
cfg := sdk.GetConfig()
|
cfg := sdk.GetConfig()
|
||||||
@ -51,7 +45,6 @@ func NewAPI(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &PrivateAccountAPI{
|
return &PrivateAccountAPI{
|
||||||
clientCtx: clientCtx,
|
|
||||||
logger: logger.With("api", "personal"),
|
logger: logger.With("api", "personal"),
|
||||||
hdPathIter: iterator,
|
hdPathIter: iterator,
|
||||||
backend: backend,
|
backend: backend,
|
||||||
@ -65,55 +58,13 @@ func NewAPI(
|
|||||||
// NOTE: The key will be both armored and encrypted using the same passphrase.
|
// NOTE: The key will be both armored and encrypted using the same passphrase.
|
||||||
func (api *PrivateAccountAPI) ImportRawKey(privkey, password string) (common.Address, error) {
|
func (api *PrivateAccountAPI) ImportRawKey(privkey, password string) (common.Address, error) {
|
||||||
api.logger.Debug("personal_importRawKey")
|
api.logger.Debug("personal_importRawKey")
|
||||||
priv, err := crypto.HexToECDSA(privkey)
|
return api.backend.ImportRawKey(privkey, password)
|
||||||
if err != nil {
|
|
||||||
return common.Address{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
privKey := ðsecp256k1.PrivKey{Key: crypto.FromECDSA(priv)}
|
|
||||||
|
|
||||||
addr := sdk.AccAddress(privKey.PubKey().Address().Bytes())
|
|
||||||
ethereumAddr := common.BytesToAddress(addr)
|
|
||||||
|
|
||||||
// return if the key has already been imported
|
|
||||||
if _, err := api.clientCtx.Keyring.KeyByAddress(addr); err == nil {
|
|
||||||
return ethereumAddr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignore error as we only care about the length of the list
|
|
||||||
list, _ := api.clientCtx.Keyring.List()
|
|
||||||
privKeyName := fmt.Sprintf("personal_%d", len(list))
|
|
||||||
|
|
||||||
armor := sdkcrypto.EncryptArmorPrivKey(privKey, password, ethsecp256k1.KeyType)
|
|
||||||
|
|
||||||
if err := api.clientCtx.Keyring.ImportPrivKey(privKeyName, armor, password); err != nil {
|
|
||||||
return common.Address{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
api.logger.Info("key successfully imported", "name", privKeyName, "address", ethereumAddr.String())
|
|
||||||
|
|
||||||
return ethereumAddr, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListAccounts will return a list of addresses for accounts this node manages.
|
// ListAccounts will return a list of addresses for accounts this node manages.
|
||||||
func (api *PrivateAccountAPI) ListAccounts() ([]common.Address, error) {
|
func (api *PrivateAccountAPI) ListAccounts() ([]common.Address, error) {
|
||||||
api.logger.Debug("personal_listAccounts")
|
api.logger.Debug("personal_listAccounts")
|
||||||
addrs := []common.Address{}
|
return api.backend.ListAccounts()
|
||||||
|
|
||||||
list, err := api.clientCtx.Keyring.List()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, info := range list {
|
|
||||||
pubKey, err := info.GetPubKey()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
addrs = append(addrs, common.BytesToAddress(pubKey.Address()))
|
|
||||||
}
|
|
||||||
|
|
||||||
return addrs, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LockAccount will lock the account associated with the given address when it's unlocked.
|
// LockAccount will lock the account associated with the given address when it's unlocked.
|
||||||
@ -134,7 +85,7 @@ func (api *PrivateAccountAPI) NewAccount(password string) (common.Address, error
|
|||||||
// create the mnemonic and save the account
|
// create the mnemonic and save the account
|
||||||
hdPath := api.hdPathIter()
|
hdPath := api.hdPathIter()
|
||||||
|
|
||||||
info, _, err := api.clientCtx.Keyring.NewMnemonic(name, keyring.English, hdPath.String(), password, hd.EthSecp256k1)
|
info, err := api.backend.NewMnemonic(name, keyring.English, hdPath.String(), password, hd.EthSecp256k1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Address{}, err
|
return common.Address{}, err
|
||||||
}
|
}
|
||||||
@ -164,19 +115,6 @@ func (api *PrivateAccountAPI) UnlockAccount(_ context.Context, addr common.Addre
|
|||||||
// able to decrypt the key it fails.
|
// able to decrypt the key it fails.
|
||||||
func (api *PrivateAccountAPI) SendTransaction(_ context.Context, args evmtypes.TransactionArgs, _ string) (common.Hash, error) {
|
func (api *PrivateAccountAPI) SendTransaction(_ context.Context, args evmtypes.TransactionArgs, _ string) (common.Hash, error) {
|
||||||
api.logger.Debug("personal_sendTransaction", "address", args.To.String())
|
api.logger.Debug("personal_sendTransaction", "address", args.To.String())
|
||||||
|
|
||||||
if args.From == nil {
|
|
||||||
return common.Hash{}, fmt.Errorf("from address cannot be nil in send transaction")
|
|
||||||
}
|
|
||||||
|
|
||||||
addr := sdk.AccAddress(args.From.Bytes())
|
|
||||||
|
|
||||||
// check if the key is on the keyring
|
|
||||||
_, err := api.clientCtx.Keyring.KeyByAddress(addr)
|
|
||||||
if err != nil {
|
|
||||||
return common.Hash{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return api.backend.SendTransaction(args)
|
return api.backend.SendTransaction(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,17 +129,7 @@ func (api *PrivateAccountAPI) SendTransaction(_ context.Context, args evmtypes.T
|
|||||||
// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_sign
|
// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_sign
|
||||||
func (api *PrivateAccountAPI) Sign(_ context.Context, data hexutil.Bytes, addr common.Address, _ string) (hexutil.Bytes, error) {
|
func (api *PrivateAccountAPI) Sign(_ context.Context, data hexutil.Bytes, addr common.Address, _ string) (hexutil.Bytes, error) {
|
||||||
api.logger.Debug("personal_sign", "data", data, "address", addr.String())
|
api.logger.Debug("personal_sign", "data", data, "address", addr.String())
|
||||||
|
return api.backend.Sign(addr, data)
|
||||||
cosmosAddr := sdk.AccAddress(addr.Bytes())
|
|
||||||
|
|
||||||
sig, _, err := api.clientCtx.Keyring.SignByAddress(cosmosAddr, accounts.TextHash(data))
|
|
||||||
if err != nil {
|
|
||||||
api.logger.Error("failed to sign with key", "data", data, "address", addr.String(), "error", err.Error())
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sig[crypto.RecoveryIDOffset] += 27 // transform V from 0/1 to 27/28
|
|
||||||
return sig, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EcRecover returns the address for the account that was used to create the signature.
|
// EcRecover returns the address for the account that was used to create the signature.
|
||||||
|
@ -5,7 +5,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -301,7 +301,7 @@ func (s *websocketsServer) tcpGetAndSendResponse(wsConn *wsConn, mb []byte) erro
|
|||||||
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "could not read body from response")
|
return errors.Wrap(err, "could not read body from response")
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user