forked from cerc-io/laconicd-deprecated
rpc: refactor rpc packages and backend to support cosmos namespace (#1070)
* rpc: refactor rpc packages and backend to support cosmos namespace * changelog * typo
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
// AddrLocker is a mutex structure used to avoid querying outdated account data
|
||||
type AddrLocker struct {
|
||||
mu sync.Mutex
|
||||
locks map[common.Address]*sync.Mutex
|
||||
}
|
||||
|
||||
// lock returns the lock of the given address.
|
||||
func (l *AddrLocker) lock(address common.Address) *sync.Mutex {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
if l.locks == nil {
|
||||
l.locks = make(map[common.Address]*sync.Mutex)
|
||||
}
|
||||
if _, ok := l.locks[address]; !ok {
|
||||
l.locks[address] = new(sync.Mutex)
|
||||
}
|
||||
return l.locks[address]
|
||||
}
|
||||
|
||||
// LockAddr locks an account's mutex. This is used to prevent another tx getting the
|
||||
// same nonce until the lock is released. The mutex prevents the (an identical nonce) from
|
||||
// being read again during the time that the first transaction is being signed.
|
||||
func (l *AddrLocker) LockAddr(address common.Address) {
|
||||
l.lock(address).Lock()
|
||||
}
|
||||
|
||||
// UnlockAddr unlocks the mutex of the given account.
|
||||
func (l *AddrLocker) UnlockAddr(address common.Address) {
|
||||
l.lock(address).Unlock()
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
"google.golang.org/grpc/metadata"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
|
||||
grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
|
||||
|
||||
ethermint "github.com/tharsis/ethermint/types"
|
||||
)
|
||||
|
||||
// BlockNumber represents decoding hex string to block values
|
||||
type BlockNumber int64
|
||||
|
||||
const (
|
||||
EthPendingBlockNumber = BlockNumber(-2)
|
||||
EthLatestBlockNumber = BlockNumber(-1)
|
||||
EthEarliestBlockNumber = BlockNumber(0)
|
||||
)
|
||||
|
||||
const (
|
||||
BlockParamEarliest = "earliest"
|
||||
BlockParamLatest = "latest"
|
||||
BlockParamPending = "pending"
|
||||
)
|
||||
|
||||
// NewBlockNumber creates a new BlockNumber instance.
|
||||
func NewBlockNumber(n *big.Int) BlockNumber {
|
||||
if !n.IsInt64() {
|
||||
// default to latest block if it overflows
|
||||
return EthLatestBlockNumber
|
||||
}
|
||||
|
||||
return BlockNumber(n.Int64())
|
||||
}
|
||||
|
||||
// ContextWithHeight wraps a context with the a gRPC block height header. If the provided height is
|
||||
// 0, it will return an empty context and the gRPC query will use the latest block height for querying.
|
||||
// Note that all metadata are processed and removed by tendermint layer, so it wont be accessible at gRPC server level.
|
||||
func ContextWithHeight(height int64) context.Context {
|
||||
if height == 0 {
|
||||
return context.Background()
|
||||
}
|
||||
|
||||
return metadata.AppendToOutgoingContext(context.Background(), grpctypes.GRPCBlockHeightHeader, fmt.Sprintf("%d", height))
|
||||
}
|
||||
|
||||
// UnmarshalJSON parses the given JSON fragment into a BlockNumber. It supports:
|
||||
// - "latest", "earliest" or "pending" as string arguments
|
||||
// - the block number
|
||||
// Returned errors:
|
||||
// - an invalid block number error when the given argument isn't a known strings
|
||||
// - an out of range error when the given block number is either too little or too large
|
||||
func (bn *BlockNumber) UnmarshalJSON(data []byte) error {
|
||||
input := strings.TrimSpace(string(data))
|
||||
if len(input) >= 2 && input[0] == '"' && input[len(input)-1] == '"' {
|
||||
input = input[1 : len(input)-1]
|
||||
}
|
||||
|
||||
switch input {
|
||||
case BlockParamEarliest:
|
||||
*bn = EthEarliestBlockNumber
|
||||
return nil
|
||||
case BlockParamLatest:
|
||||
*bn = EthLatestBlockNumber
|
||||
return nil
|
||||
case BlockParamPending:
|
||||
*bn = EthPendingBlockNumber
|
||||
return nil
|
||||
}
|
||||
|
||||
blckNum, err := hexutil.DecodeUint64(input)
|
||||
if errors.Is(err, hexutil.ErrMissingPrefix) {
|
||||
blckNum = cast.ToUint64(input)
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if blckNum > math.MaxInt64 {
|
||||
return fmt.Errorf("block number larger than int64")
|
||||
}
|
||||
*bn = BlockNumber(blckNum)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Int64 converts block number to primitive type
|
||||
func (bn BlockNumber) Int64() int64 {
|
||||
if bn < 0 {
|
||||
return 0
|
||||
} else if bn == 0 {
|
||||
return 1
|
||||
}
|
||||
|
||||
return int64(bn)
|
||||
}
|
||||
|
||||
// TmHeight is a util function used for the Tendermint RPC client. It returns
|
||||
// nil if the block number is "latest". Otherwise, it returns the pointer of the
|
||||
// int64 value of the height.
|
||||
func (bn BlockNumber) TmHeight() *int64 {
|
||||
if bn < 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
height := bn.Int64()
|
||||
return &height
|
||||
}
|
||||
|
||||
// BlockNumberOrHash represents a block number or a block hash.
|
||||
type BlockNumberOrHash struct {
|
||||
BlockNumber *BlockNumber `json:"blockNumber,omitempty"`
|
||||
BlockHash *common.Hash `json:"blockHash,omitempty"`
|
||||
}
|
||||
|
||||
func (bnh *BlockNumberOrHash) UnmarshalJSON(data []byte) error {
|
||||
type erased BlockNumberOrHash
|
||||
e := erased{}
|
||||
err := json.Unmarshal(data, &e)
|
||||
if err == nil {
|
||||
return bnh.checkUnmarshal(BlockNumberOrHash(e))
|
||||
}
|
||||
var input string
|
||||
err = json.Unmarshal(data, &input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = bnh.decodeFromString(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bnh *BlockNumberOrHash) checkUnmarshal(e BlockNumberOrHash) error {
|
||||
if e.BlockNumber != nil && e.BlockHash != nil {
|
||||
return fmt.Errorf("cannot specify both BlockHash and BlockNumber, choose one or the other")
|
||||
}
|
||||
bnh.BlockNumber = e.BlockNumber
|
||||
bnh.BlockHash = e.BlockHash
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bnh *BlockNumberOrHash) decodeFromString(input string) error {
|
||||
switch input {
|
||||
case BlockParamEarliest:
|
||||
bn := EthEarliestBlockNumber
|
||||
bnh.BlockNumber = &bn
|
||||
case BlockParamLatest:
|
||||
bn := EthLatestBlockNumber
|
||||
bnh.BlockNumber = &bn
|
||||
case BlockParamPending:
|
||||
bn := EthPendingBlockNumber
|
||||
bnh.BlockNumber = &bn
|
||||
default:
|
||||
// check if the input is a block hash
|
||||
if len(input) == 66 {
|
||||
hash := common.Hash{}
|
||||
err := hash.UnmarshalText([]byte(input))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bnh.BlockHash = &hash
|
||||
break
|
||||
}
|
||||
// otherwise take the hex string has int64 value
|
||||
blockNumber, err := hexutil.DecodeUint64(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bnInt, err := ethermint.SafeInt64(blockNumber)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bn := BlockNumber(bnInt)
|
||||
bnh.BlockNumber = &bn
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestUnmarshalBlockNumberOrHash(t *testing.T) {
|
||||
bnh := new(BlockNumberOrHash)
|
||||
|
||||
testCases := []struct {
|
||||
msg string
|
||||
input []byte
|
||||
malleate func()
|
||||
expPass bool
|
||||
}{
|
||||
{
|
||||
"JSON input with block hash",
|
||||
[]byte("{\"blockHash\": \"0x579917054e325746fda5c3ee431d73d26255bc4e10b51163862368629ae19739\"}"),
|
||||
func() {
|
||||
require.Equal(t, *bnh.BlockHash, common.HexToHash("0x579917054e325746fda5c3ee431d73d26255bc4e10b51163862368629ae19739"))
|
||||
require.Nil(t, bnh.BlockNumber)
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"JSON input with block number",
|
||||
[]byte("{\"blockNumber\": \"0x35\"}"),
|
||||
func() {
|
||||
require.Equal(t, *bnh.BlockNumber, BlockNumber(0x35))
|
||||
require.Nil(t, bnh.BlockHash)
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"JSON input with block number latest",
|
||||
[]byte("{\"blockNumber\": \"latest\"}"),
|
||||
func() {
|
||||
require.Equal(t, *bnh.BlockNumber, EthLatestBlockNumber)
|
||||
require.Nil(t, bnh.BlockHash)
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"JSON input with both block hash and block number",
|
||||
[]byte("{\"blockHash\": \"0x579917054e325746fda5c3ee431d73d26255bc4e10b51163862368629ae19739\", \"blockNumber\": \"0x35\"}"),
|
||||
func() {
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"String input with block hash",
|
||||
[]byte("\"0x579917054e325746fda5c3ee431d73d26255bc4e10b51163862368629ae19739\""),
|
||||
func() {
|
||||
require.Equal(t, *bnh.BlockHash, common.HexToHash("0x579917054e325746fda5c3ee431d73d26255bc4e10b51163862368629ae19739"))
|
||||
require.Nil(t, bnh.BlockNumber)
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"String input with block number",
|
||||
[]byte("\"0x35\""),
|
||||
func() {
|
||||
require.Equal(t, *bnh.BlockNumber, BlockNumber(0x35))
|
||||
require.Nil(t, bnh.BlockHash)
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"String input with block number latest",
|
||||
[]byte("\"latest\""),
|
||||
func() {
|
||||
require.Equal(t, *bnh.BlockNumber, EthLatestBlockNumber)
|
||||
require.Nil(t, bnh.BlockHash)
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"String input with block number overflow",
|
||||
[]byte("\"0xffffffffffffffffffffffffffffffffffffff\""),
|
||||
func() {
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
fmt.Sprintf("Case %s", tc.msg)
|
||||
// reset input
|
||||
bnh = new(BlockNumberOrHash)
|
||||
err := bnh.UnmarshalJSON(tc.input)
|
||||
tc.malleate()
|
||||
if tc.expPass {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/types/tx"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/proto/tendermint/crypto"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
|
||||
evmtypes "github.com/tharsis/ethermint/x/evm/types"
|
||||
feemarkettypes "github.com/tharsis/ethermint/x/feemarket/types"
|
||||
)
|
||||
|
||||
// QueryClient defines a gRPC Client used for:
|
||||
// - Transaction simulation
|
||||
// - EVM module queries
|
||||
// - Fee market module queries
|
||||
type QueryClient struct {
|
||||
tx.ServiceClient
|
||||
evmtypes.QueryClient
|
||||
FeeMarket feemarkettypes.QueryClient
|
||||
}
|
||||
|
||||
// NewQueryClient creates a new gRPC query client
|
||||
func NewQueryClient(clientCtx client.Context) *QueryClient {
|
||||
return &QueryClient{
|
||||
ServiceClient: tx.NewServiceClient(clientCtx),
|
||||
QueryClient: evmtypes.NewQueryClient(clientCtx),
|
||||
FeeMarket: feemarkettypes.NewQueryClient(clientCtx),
|
||||
}
|
||||
}
|
||||
|
||||
// GetProof performs an ABCI query with the given key and returns a merkle proof. The desired
|
||||
// tendermint height to perform the query should be set in the client context. The query will be
|
||||
// performed at one below this height (at the IAVL version) in order to obtain the correct merkle
|
||||
// proof. Proof queries at height less than or equal to 2 are not supported.
|
||||
// Issue: https://github.com/cosmos/cosmos-sdk/issues/6567
|
||||
func (QueryClient) GetProof(clientCtx client.Context, storeKey string, key []byte) ([]byte, *crypto.ProofOps, error) {
|
||||
height := clientCtx.Height
|
||||
// ABCI queries at height less than or equal to 2 are not supported.
|
||||
// Base app does not support queries for height less than or equal to 1.
|
||||
// Therefore, a query at height 2 would be equivalent to a query at height 3
|
||||
if height <= 2 {
|
||||
return nil, nil, fmt.Errorf("proof queries at height <= 2 are not supported")
|
||||
}
|
||||
|
||||
// Use the IAVL height if a valid tendermint height is passed in.
|
||||
height--
|
||||
|
||||
abciReq := abci.RequestQuery{
|
||||
Path: fmt.Sprintf("store/%s/key", storeKey),
|
||||
Data: key,
|
||||
Height: height,
|
||||
Prove: true,
|
||||
}
|
||||
|
||||
abciRes, err := clientCtx.QueryABCI(abciReq)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return abciRes.Value, abciRes.ProofOps, nil
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
// Copied the Account and StorageResult types since they are registered under an
|
||||
// internal pkg on geth.
|
||||
|
||||
// AccountResult struct for account proof
|
||||
type AccountResult struct {
|
||||
Address common.Address `json:"address"`
|
||||
AccountProof []string `json:"accountProof"`
|
||||
Balance *hexutil.Big `json:"balance"`
|
||||
CodeHash common.Hash `json:"codeHash"`
|
||||
Nonce hexutil.Uint64 `json:"nonce"`
|
||||
StorageHash common.Hash `json:"storageHash"`
|
||||
StorageProof []StorageResult `json:"storageProof"`
|
||||
}
|
||||
|
||||
// StorageResult defines the format for storage proof return
|
||||
type StorageResult struct {
|
||||
Key string `json:"key"`
|
||||
Value *hexutil.Big `json:"value"`
|
||||
Proof []string `json:"proof"`
|
||||
}
|
||||
|
||||
// RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction
|
||||
type RPCTransaction struct {
|
||||
BlockHash *common.Hash `json:"blockHash"`
|
||||
BlockNumber *hexutil.Big `json:"blockNumber"`
|
||||
From common.Address `json:"from"`
|
||||
Gas hexutil.Uint64 `json:"gas"`
|
||||
GasPrice *hexutil.Big `json:"gasPrice"`
|
||||
GasFeeCap *hexutil.Big `json:"maxFeePerGas,omitempty"`
|
||||
GasTipCap *hexutil.Big `json:"maxPriorityFeePerGas,omitempty"`
|
||||
Hash common.Hash `json:"hash"`
|
||||
Input hexutil.Bytes `json:"input"`
|
||||
Nonce hexutil.Uint64 `json:"nonce"`
|
||||
To *common.Address `json:"to"`
|
||||
TransactionIndex *hexutil.Uint64 `json:"transactionIndex"`
|
||||
Value *hexutil.Big `json:"value"`
|
||||
Type hexutil.Uint64 `json:"type"`
|
||||
Accesses *ethtypes.AccessList `json:"accessList,omitempty"`
|
||||
ChainID *hexutil.Big `json:"chainId,omitempty"`
|
||||
V *hexutil.Big `json:"v"`
|
||||
R *hexutil.Big `json:"r"`
|
||||
S *hexutil.Big `json:"s"`
|
||||
}
|
||||
|
||||
// StateOverride is the collection of overridden accounts.
|
||||
type StateOverride map[common.Address]OverrideAccount
|
||||
|
||||
// OverrideAccount indicates the overriding fields of account during the execution of
|
||||
// a message call.
|
||||
// Note, state and stateDiff can't be specified at the same time. If state is
|
||||
// set, message execution will only use the data in the given state. Otherwise
|
||||
// if statDiff is set, all diff will be applied first and then execute the call
|
||||
// message.
|
||||
type OverrideAccount struct {
|
||||
Nonce *hexutil.Uint64 `json:"nonce"`
|
||||
Code *hexutil.Bytes `json:"code"`
|
||||
Balance **hexutil.Big `json:"balance"`
|
||||
State *map[common.Hash]common.Hash `json:"state"`
|
||||
StateDiff *map[common.Hash]common.Hash `json:"stateDiff"`
|
||||
}
|
||||
|
||||
type FeeHistoryResult struct {
|
||||
OldestBlock *hexutil.Big `json:"oldestBlock"`
|
||||
Reward [][]*hexutil.Big `json:"reward,omitempty"`
|
||||
BaseFee []*hexutil.Big `json:"baseFeePerGas,omitempty"`
|
||||
GasUsedRatio []float64 `json:"gasUsedRatio"`
|
||||
}
|
||||
|
||||
// SignTransactionResult represents a RLP encoded signed transaction.
|
||||
type SignTransactionResult struct {
|
||||
Raw hexutil.Bytes `json:"raw"`
|
||||
Tx *ethtypes.Transaction `json:"tx"`
|
||||
}
|
||||
|
||||
type OneFeeHistory struct {
|
||||
BaseFee *big.Int // base fee for each block
|
||||
Reward []*big.Int // each element of the array will have the tip provided to miners for the percentile given
|
||||
GasUsedRatio float64 // the ratio of gas used to the gas limit for each block
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strconv"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
tmtypes "github.com/tendermint/tendermint/types"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
|
||||
evmtypes "github.com/tharsis/ethermint/x/evm/types"
|
||||
feemarkettypes "github.com/tharsis/ethermint/x/feemarket/types"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
// RawTxToEthTx returns a evm MsgEthereum transaction from raw tx bytes.
|
||||
func RawTxToEthTx(clientCtx client.Context, txBz tmtypes.Tx) ([]*evmtypes.MsgEthereumTx, error) {
|
||||
tx, err := clientCtx.TxConfig.TxDecoder()(txBz)
|
||||
if err != nil {
|
||||
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
|
||||
}
|
||||
|
||||
ethTxs := make([]*evmtypes.MsgEthereumTx, len(tx.GetMsgs()))
|
||||
for i, msg := range tx.GetMsgs() {
|
||||
ethTx, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid message type %T, expected %T", msg, &evmtypes.MsgEthereumTx{})
|
||||
}
|
||||
ethTxs[i] = ethTx
|
||||
}
|
||||
return ethTxs, nil
|
||||
}
|
||||
|
||||
// EthHeaderFromTendermint is an util function that returns an Ethereum Header
|
||||
// from a tendermint Header.
|
||||
func EthHeaderFromTendermint(header tmtypes.Header, bloom ethtypes.Bloom, baseFee *big.Int) *ethtypes.Header {
|
||||
txHash := ethtypes.EmptyRootHash
|
||||
if len(header.DataHash) == 0 {
|
||||
txHash = common.BytesToHash(header.DataHash)
|
||||
}
|
||||
|
||||
return ðtypes.Header{
|
||||
ParentHash: common.BytesToHash(header.LastBlockID.Hash.Bytes()),
|
||||
UncleHash: ethtypes.EmptyUncleHash,
|
||||
Coinbase: common.BytesToAddress(header.ProposerAddress),
|
||||
Root: common.BytesToHash(header.AppHash),
|
||||
TxHash: txHash,
|
||||
ReceiptHash: ethtypes.EmptyRootHash,
|
||||
Bloom: bloom,
|
||||
Difficulty: big.NewInt(0),
|
||||
Number: big.NewInt(header.Height),
|
||||
GasLimit: 0,
|
||||
GasUsed: 0,
|
||||
Time: uint64(header.Time.UTC().Unix()),
|
||||
Extra: []byte{},
|
||||
MixDigest: common.Hash{},
|
||||
Nonce: ethtypes.BlockNonce{},
|
||||
BaseFee: baseFee,
|
||||
}
|
||||
}
|
||||
|
||||
// BlockMaxGasFromConsensusParams returns the gas limit for the current block from the chain consensus params.
|
||||
func BlockMaxGasFromConsensusParams(goCtx context.Context, clientCtx client.Context, blockHeight int64) (int64, error) {
|
||||
resConsParams, err := clientCtx.Client.ConsensusParams(goCtx, &blockHeight)
|
||||
if err != nil {
|
||||
return int64(^uint32(0)), err
|
||||
}
|
||||
|
||||
gasLimit := resConsParams.ConsensusParams.Block.MaxGas
|
||||
if gasLimit == -1 {
|
||||
// Sets gas limit to max uint32 to not error with javascript dev tooling
|
||||
// This -1 value indicating no block gas limit is set to max uint64 with geth hexutils
|
||||
// which errors certain javascript dev tooling which only supports up to 53 bits
|
||||
gasLimit = int64(^uint32(0))
|
||||
}
|
||||
|
||||
return gasLimit, nil
|
||||
}
|
||||
|
||||
// FormatBlock creates an ethereum block from a tendermint header and ethereum-formatted
|
||||
// transactions.
|
||||
func FormatBlock(
|
||||
header tmtypes.Header, size int, gasLimit int64,
|
||||
gasUsed *big.Int, transactions []interface{}, bloom ethtypes.Bloom,
|
||||
validatorAddr common.Address, baseFee *big.Int,
|
||||
) map[string]interface{} {
|
||||
var transactionsRoot common.Hash
|
||||
if len(transactions) == 0 {
|
||||
transactionsRoot = ethtypes.EmptyRootHash
|
||||
} else {
|
||||
transactionsRoot = common.BytesToHash(header.DataHash)
|
||||
}
|
||||
|
||||
result := map[string]interface{}{
|
||||
"number": hexutil.Uint64(header.Height),
|
||||
"hash": hexutil.Bytes(header.Hash()),
|
||||
"parentHash": common.BytesToHash(header.LastBlockID.Hash.Bytes()),
|
||||
"nonce": ethtypes.BlockNonce{}, // PoW specific
|
||||
"sha3Uncles": ethtypes.EmptyUncleHash, // No uncles in Tendermint
|
||||
"logsBloom": bloom,
|
||||
"stateRoot": hexutil.Bytes(header.AppHash),
|
||||
"miner": validatorAddr,
|
||||
"mixHash": common.Hash{},
|
||||
"difficulty": (*hexutil.Big)(big.NewInt(0)),
|
||||
"extraData": "0x",
|
||||
"size": hexutil.Uint64(size),
|
||||
"gasLimit": hexutil.Uint64(gasLimit), // Static gas limit
|
||||
"gasUsed": (*hexutil.Big)(gasUsed),
|
||||
"timestamp": hexutil.Uint64(header.Time.Unix()),
|
||||
"transactionsRoot": transactionsRoot,
|
||||
"receiptsRoot": ethtypes.EmptyRootHash,
|
||||
|
||||
"uncles": []common.Hash{},
|
||||
"transactions": transactions,
|
||||
"totalDifficulty": (*hexutil.Big)(big.NewInt(0)),
|
||||
}
|
||||
|
||||
if baseFee != nil {
|
||||
result["baseFeePerGas"] = (*hexutil.Big)(baseFee)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
type DataError interface {
|
||||
Error() string // returns the message
|
||||
ErrorData() interface{} // returns the error data
|
||||
}
|
||||
|
||||
type dataError struct {
|
||||
msg string
|
||||
data string
|
||||
}
|
||||
|
||||
func (d *dataError) Error() string {
|
||||
return d.msg
|
||||
}
|
||||
|
||||
func (d *dataError) ErrorData() interface{} {
|
||||
return d.data
|
||||
}
|
||||
|
||||
type SDKTxLogs struct {
|
||||
Log string `json:"log"`
|
||||
}
|
||||
|
||||
const LogRevertedFlag = "transaction reverted"
|
||||
|
||||
func ErrRevertedWith(data []byte) DataError {
|
||||
return &dataError{
|
||||
msg: "VM execution error.",
|
||||
data: fmt.Sprintf("0x%s", hex.EncodeToString(data)),
|
||||
}
|
||||
}
|
||||
|
||||
// NewTransactionFromMsg returns a transaction that will serialize to the RPC
|
||||
// representation, with the given location metadata set (if available).
|
||||
func NewTransactionFromMsg(
|
||||
msg *evmtypes.MsgEthereumTx,
|
||||
blockHash common.Hash,
|
||||
blockNumber, index uint64,
|
||||
baseFee *big.Int,
|
||||
) (*RPCTransaction, error) {
|
||||
tx := msg.AsTransaction()
|
||||
return NewRPCTransaction(tx, blockHash, blockNumber, index, baseFee)
|
||||
}
|
||||
|
||||
// NewTransactionFromData returns a transaction that will serialize to the RPC
|
||||
// representation, with the given location metadata set (if available).
|
||||
func NewRPCTransaction(
|
||||
tx *ethtypes.Transaction, blockHash common.Hash, blockNumber, index uint64, baseFee *big.Int,
|
||||
) (*RPCTransaction, error) {
|
||||
// Determine the signer. For replay-protected transactions, use the most permissive
|
||||
// signer, because we assume that signers are backwards-compatible with old
|
||||
// transactions. For non-protected transactions, the homestead signer signer is used
|
||||
// because the return value of ChainId is zero for those transactions.
|
||||
var signer ethtypes.Signer
|
||||
if tx.Protected() {
|
||||
signer = ethtypes.LatestSignerForChainID(tx.ChainId())
|
||||
} else {
|
||||
signer = ethtypes.HomesteadSigner{}
|
||||
}
|
||||
from, _ := ethtypes.Sender(signer, tx)
|
||||
v, r, s := tx.RawSignatureValues()
|
||||
result := &RPCTransaction{
|
||||
Type: hexutil.Uint64(tx.Type()),
|
||||
From: from,
|
||||
Gas: hexutil.Uint64(tx.Gas()),
|
||||
GasPrice: (*hexutil.Big)(tx.GasPrice()),
|
||||
Hash: tx.Hash(),
|
||||
Input: hexutil.Bytes(tx.Data()),
|
||||
Nonce: hexutil.Uint64(tx.Nonce()),
|
||||
To: tx.To(),
|
||||
Value: (*hexutil.Big)(tx.Value()),
|
||||
V: (*hexutil.Big)(v),
|
||||
R: (*hexutil.Big)(r),
|
||||
S: (*hexutil.Big)(s),
|
||||
}
|
||||
if blockHash != (common.Hash{}) {
|
||||
result.BlockHash = &blockHash
|
||||
result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber))
|
||||
result.TransactionIndex = (*hexutil.Uint64)(&index)
|
||||
}
|
||||
switch tx.Type() {
|
||||
case ethtypes.AccessListTxType:
|
||||
al := tx.AccessList()
|
||||
result.Accesses = &al
|
||||
result.ChainID = (*hexutil.Big)(tx.ChainId())
|
||||
case ethtypes.DynamicFeeTxType:
|
||||
al := tx.AccessList()
|
||||
result.Accesses = &al
|
||||
result.ChainID = (*hexutil.Big)(tx.ChainId())
|
||||
result.GasFeeCap = (*hexutil.Big)(tx.GasFeeCap())
|
||||
result.GasTipCap = (*hexutil.Big)(tx.GasTipCap())
|
||||
// if the transaction has been mined, compute the effective gas price
|
||||
if baseFee != nil && blockHash != (common.Hash{}) {
|
||||
// price = min(tip, gasFeeCap - baseFee) + baseFee
|
||||
price := math.BigMin(new(big.Int).Add(tx.GasTipCap(), baseFee), tx.GasFeeCap())
|
||||
result.GasPrice = (*hexutil.Big)(price)
|
||||
} else {
|
||||
result.GasPrice = (*hexutil.Big)(tx.GasFeeCap())
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// BaseFeeFromEvents parses the feemarket basefee from cosmos events
|
||||
func BaseFeeFromEvents(events []abci.Event) *big.Int {
|
||||
for _, event := range events {
|
||||
if event.Type != feemarkettypes.EventTypeFeeMarket {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, attr := range event.Attributes {
|
||||
if bytes.Equal(attr.Key, []byte(feemarkettypes.AttributeKeyBaseFee)) {
|
||||
result, success := new(big.Int).SetString(string(attr.Value), 10)
|
||||
if success {
|
||||
return result
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindTxAttributes returns the msg index of the eth tx in cosmos tx, and the attributes,
|
||||
// returns -1 and nil if not found.
|
||||
func FindTxAttributes(events []abci.Event, txHash string) (int, map[string]string) {
|
||||
msgIndex := -1
|
||||
for _, event := range events {
|
||||
if event.Type != evmtypes.EventTypeEthereumTx {
|
||||
continue
|
||||
}
|
||||
|
||||
msgIndex++
|
||||
|
||||
value := FindAttribute(event.Attributes, []byte(evmtypes.AttributeKeyEthereumTxHash))
|
||||
if !bytes.Equal(value, []byte(txHash)) {
|
||||
continue
|
||||
}
|
||||
|
||||
// found, convert attributes to map for later lookup
|
||||
attrs := make(map[string]string, len(event.Attributes))
|
||||
for _, attr := range event.Attributes {
|
||||
attrs[string(attr.Key)] = string(attr.Value)
|
||||
}
|
||||
return msgIndex, attrs
|
||||
}
|
||||
// not found
|
||||
return -1, nil
|
||||
}
|
||||
|
||||
// FindTxAttributesByIndex search the msg in tx events by txIndex
|
||||
// returns the msgIndex, returns -1 if not found.
|
||||
func FindTxAttributesByIndex(events []abci.Event, txIndex uint64) int {
|
||||
strIndex := []byte(strconv.FormatUint(txIndex, 10))
|
||||
txIndexKey := []byte(evmtypes.AttributeKeyTxIndex)
|
||||
msgIndex := -1
|
||||
for _, event := range events {
|
||||
if event.Type != evmtypes.EventTypeEthereumTx {
|
||||
continue
|
||||
}
|
||||
|
||||
msgIndex++
|
||||
|
||||
value := FindAttribute(event.Attributes, txIndexKey)
|
||||
if !bytes.Equal(value, strIndex) {
|
||||
continue
|
||||
}
|
||||
|
||||
// found, convert attributes to map for later lookup
|
||||
return msgIndex
|
||||
}
|
||||
// not found
|
||||
return -1
|
||||
}
|
||||
|
||||
// FindAttribute find event attribute with specified key, if not found returns nil.
|
||||
func FindAttribute(attrs []abci.EventAttribute, key []byte) []byte {
|
||||
for _, attr := range attrs {
|
||||
if !bytes.Equal(attr.Key, key) {
|
||||
continue
|
||||
}
|
||||
return attr.Value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUint64Attribute parses the uint64 value from event attributes
|
||||
func GetUint64Attribute(attrs map[string]string, key string) (uint64, error) {
|
||||
value, found := attrs[key]
|
||||
if !found {
|
||||
return 0, fmt.Errorf("tx index attribute not found: %s", key)
|
||||
}
|
||||
var result int64
|
||||
result, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if result < 0 {
|
||||
return 0, fmt.Errorf("negative tx index: %d", result)
|
||||
}
|
||||
return uint64(result), nil
|
||||
}
|
||||
|
||||
// AccumulativeGasUsedOfMsg accumulate the gas used by msgs before `msgIndex`.
|
||||
func AccumulativeGasUsedOfMsg(events []abci.Event, msgIndex int) (gasUsed uint64) {
|
||||
for _, event := range events {
|
||||
if event.Type != evmtypes.EventTypeEthereumTx {
|
||||
continue
|
||||
}
|
||||
|
||||
if msgIndex < 0 {
|
||||
break
|
||||
}
|
||||
msgIndex--
|
||||
|
||||
value := FindAttribute(event.Attributes, []byte(evmtypes.AttributeKeyTxGasUsed))
|
||||
var result int64
|
||||
result, err := strconv.ParseInt(string(value), 10, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
gasUsed += uint64(result)
|
||||
}
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user