impr: support batch eth txs (#901)

* support batch eth tx

Closes: 896

Allow multiple MsgEthereumTx in single tx

* fix transaction receipt api

* fix tx receipt api and accumulate tx gas used

* fix lint

* fix test

* fix rpc test

* cleanup

* fix cumulativeGasUsed and gasUsed

* fix lint

* Update app/ante/eth.go

Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>

* Update app/ante/eth.go

Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>

* Update rpc/ethereum/backend/utils.go

Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>

* pr suggestions

* typo

* fix lint

Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>
This commit is contained in:
yihuang
2022-01-14 10:37:33 +01:00
committed by GitHub
co-authored by Federico Kunze Küllmer
parent aeb6aeb715
commit 7d8664043e
22 changed files with 466 additions and 320 deletions
+11 -5
View File
@@ -4,6 +4,7 @@ import (
"context"
"encoding/hex"
"encoding/json"
"errors"
"math/big"
"net/http"
"strings"
@@ -94,17 +95,22 @@ func getEthTransactionByHash(clientCtx client.Context, hashHex string) ([]byte,
blockHash := common.BytesToHash(block.Block.Header.Hash())
ethTx, err := rpctypes.RawTxToEthTx(clientCtx, tx.Tx)
ethTxs, err := rpctypes.RawTxToEthTx(clientCtx, tx.Tx)
if err != nil {
return nil, err
}
height := uint64(tx.Height)
rpcTx, err := rpctypes.NewRPCTransaction(ethTx.AsTransaction(), blockHash, height, uint64(tx.Index), baseFee)
if err != nil {
return nil, err
for _, ethTx := range ethTxs {
if common.HexToHash(ethTx.Hash) == common.BytesToHash(hash) {
rpcTx, err := rpctypes.NewRPCTransaction(ethTx.AsTransaction(), blockHash, height, uint64(tx.Index), baseFee)
if err != nil {
return nil, err
}
return json.Marshal(rpcTx)
}
}
return json.Marshal(rpcTx)
return nil, errors.New("eth tx not found")
}
+34
View File
@@ -6,6 +6,7 @@ import (
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
@@ -301,3 +302,36 @@ func (k Keeper) BaseFee(ctx sdk.Context, ethCfg *params.ChainConfig) *big.Int {
}
return baseFee
}
// ResetTransientGasUsed reset gas used to prepare for execution of current cosmos tx, called in ante handler.
func (k Keeper) ResetTransientGasUsed(ctx sdk.Context) {
store := ctx.TransientStore(k.transientKey)
store.Delete(types.KeyPrefixTransientGasUsed)
}
// GetTransientGasUsed returns the gas used by current cosmos tx.
func (k Keeper) GetTransientGasUsed(ctx sdk.Context) uint64 {
store := ctx.TransientStore(k.transientKey)
bz := store.Get(types.KeyPrefixTransientGasUsed)
if len(bz) == 0 {
return 0
}
return sdk.BigEndianToUint64(bz)
}
// SetTransientGasUsed sets the gas used by current cosmos tx.
func (k Keeper) SetTransientGasUsed(ctx sdk.Context, gasUsed uint64) {
store := ctx.TransientStore(k.transientKey)
bz := sdk.Uint64ToBigEndian(gasUsed)
store.Set(types.KeyPrefixTransientGasUsed, bz)
}
// AddTransientGasUsed accumulate gas used by each eth msgs included in current cosmos tx.
func (k Keeper) AddTransientGasUsed(ctx sdk.Context, gasUsed uint64) (uint64, error) {
result := k.GetTransientGasUsed(ctx) + gasUsed
if result < gasUsed {
return 0, sdkerrors.Wrap(types.ErrGasOverflow, "transient gas used")
}
k.SetTransientGasUsed(ctx, result)
return result, nil
}
+3 -1
View File
@@ -38,7 +38,9 @@ func (k *Keeper) EthereumTx(goCtx context.Context, msg *types.MsgEthereumTx) (*t
// add event for ethereum transaction hash format
sdk.NewAttribute(types.AttributeKeyEthereumTxHash, response.Hash),
// add event for index of valid ethereum tx
sdk.NewAttribute(types.AttributeKeyTxIndex, strconv.FormatInt(int64(txIndex), 10)),
sdk.NewAttribute(types.AttributeKeyTxIndex, strconv.FormatUint(txIndex, 10)),
// add event for eth tx gas used, we can't get it from cosmos tx result when it contains multiple eth tx msgs.
sdk.NewAttribute(types.AttributeKeyTxGasUsed, strconv.FormatUint(response.GasUsed, 10)),
}
if len(ctx.TxBytes()) > 0 {
+7 -2
View File
@@ -287,8 +287,13 @@ func (k *Keeper) ApplyTransaction(ctx sdk.Context, tx *ethtypes.Transaction) (*t
k.SetTxIndexTransient(ctx, uint64(txConfig.TxIndex)+1)
// update the gas used after refund
k.ResetGasMeterAndConsumeGas(ctx, res.GasUsed)
totalGasUsed, err := k.AddTransientGasUsed(ctx, res.GasUsed)
if err != nil {
return nil, sdkerrors.Wrap(err, "failed to add transient gas used")
}
// reset the gas meter for current cosmos transaction
k.ResetGasMeterAndConsumeGas(ctx, totalGasUsed)
return res, nil
}
+2 -1
View File
@@ -19,6 +19,7 @@ The `x/evm` module keeps the following objects in state:
| Block Bloom | Block bloom filter, used to accumulate the bloom filter of current block, emitted to events at end blocker. | `[]byte{1} + []byte(tx.Hash)` | `protobuf([]Log)` | Transient |
| Tx Index | Index of current transaction in current block. | `[]byte{2}` | `BigEndian(uint64)` | Transient |
| Log Size | Number of the logs emitted so far in current block. Used to decide the log index of following logs. | `[]byte{3}` | `BigEndian(uint64)` | Transient |
| Gas Used | Amount of gas used by ethereum messages of current cosmos-sdk tx, it's necessary when cosmos-sdk tx contains multiple ethereum messages. | `[]byte{4}` | `BigEndian(uint64)` | Transient |
## StateDB
@@ -161,7 +162,7 @@ With `AddLog()` you can append the given ethereum `Log` to the list of Logs asso
## Keeper
The EVM module `Keeper` grants access to the EVM module state and implements `statedb.Keeper` interface to support the `StateDB` implementation. The Keeper contains a store key that allows the DB to write to a concrete subtree of the multistore that is only accessible to the EVM module. Instead of using a trie and database for querying and persistence (the `StateDB` implementation on Ethermint), use the Cosmos `KVStore` (key-value store) and Cosmos SDK `Keeper` to facilitate state transitions.
The EVM module `Keeper` grants access to the EVM module state and implements `statedb.Keeper` interface to support the `StateDB` implementation. The Keeper contains a store key that allows the DB to write to a concrete subtree of the multistore that is only accessible to the EVM module. Instead of using a trie and database for querying and persistence (the `StateDB` implementation on Ethermint), use the Cosmos `KVStore` (key-value store) and Cosmos SDK `Keeper` to facilitate state transitions.
To support the interface functionality, it imports 4 module Keepers:
+1
View File
@@ -16,6 +16,7 @@ The `x/evm` module emits the Cosmos SDK events after a state execution. The EVM
| ethereum_tx | `"txHash"` | `{tendermint_hex_hash}` |
| ethereum_tx | `"ethereumTxHash"` | `{hex_hash}` |
| ethereum_tx | `"txIndex"` | `{tx_index}` |
| ethereum_tx | `"txGasUsed"` | `{gas_used}` |
| tx_log | `"txLog"` | `{tx_log}` |
| message | `"sender"` | `{eth_address}` |
| message | `"action"` | `"ethereum"` |
+1
View File
@@ -11,6 +11,7 @@ const (
AttributeKeyTxHash = "txHash"
AttributeKeyEthereumTxHash = "ethereumTxHash"
AttributeKeyTxIndex = "txIndex"
AttributeKeyTxGasUsed = "txGasUsed"
AttributeKeyTxType = "txType"
AttributeKeyTxLog = "txLog"
// tx failed in eth vm execution
+2
View File
@@ -32,6 +32,7 @@ const (
prefixTransientBloom = iota + 1
prefixTransientTxIndex
prefixTransientLogSize
prefixTransientGasUsed
)
// KVStore key prefixes
@@ -45,6 +46,7 @@ var (
KeyPrefixTransientBloom = []byte{prefixTransientBloom}
KeyPrefixTransientTxIndex = []byte{prefixTransientTxIndex}
KeyPrefixTransientLogSize = []byte{prefixTransientLogSize}
KeyPrefixTransientGasUsed = []byte{prefixTransientGasUsed}
)
// AddressStoragePrefix returns a prefix to iterate over a given account storage.
+11 -8
View File
@@ -9,6 +9,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
@@ -54,20 +55,22 @@ func DecodeTransactionLogs(data []byte) (TransactionLogs, error) {
}
// UnwrapEthereumMsg extract MsgEthereumTx from wrapping sdk.Tx
func UnwrapEthereumMsg(tx *sdk.Tx) (*MsgEthereumTx, error) {
func UnwrapEthereumMsg(tx *sdk.Tx, ethHash common.Hash) (*MsgEthereumTx, error) {
if tx == nil {
return nil, fmt.Errorf("invalid tx: nil")
}
if len((*tx).GetMsgs()) != 1 {
return nil, fmt.Errorf("invalid tx type: %T", tx)
}
msg, ok := (*tx).GetMsgs()[0].(*MsgEthereumTx)
if !ok {
return nil, fmt.Errorf("invalid tx type: %T", tx)
for _, msg := range (*tx).GetMsgs() {
ethMsg, ok := msg.(*MsgEthereumTx)
if !ok {
return nil, fmt.Errorf("invalid tx type: %T", tx)
}
if ethMsg.AsTransaction().Hash() == ethHash {
return ethMsg, nil
}
}
return msg, nil
return nil, fmt.Errorf("eth tx not found: %s", ethHash)
}
// BinSearch execute the binary search and hone in on an executable gas limit
+3 -3
View File
@@ -48,7 +48,7 @@ func TestEvmDataEncoding(t *testing.T) {
}
func TestUnwrapEthererumMsg(t *testing.T) {
_, err := evmtypes.UnwrapEthereumMsg(nil)
_, err := evmtypes.UnwrapEthereumMsg(nil, common.Hash{})
require.NotNil(t, err)
encodingConfig := encoding.MakeConfig(app.ModuleBasics)
@@ -56,14 +56,14 @@ func TestUnwrapEthererumMsg(t *testing.T) {
builder, _ := clientCtx.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder)
tx := builder.GetTx().(sdk.Tx)
_, err = evmtypes.UnwrapEthereumMsg(&tx)
_, err = evmtypes.UnwrapEthereumMsg(&tx, common.Hash{})
require.NotNil(t, err)
msg := evmtypes.NewTx(big.NewInt(1), 0, &common.Address{}, big.NewInt(0), 0, big.NewInt(0), nil, nil, []byte{}, nil)
err = builder.SetMsgs(msg)
tx = builder.GetTx().(sdk.Tx)
msg_, err := evmtypes.UnwrapEthereumMsg(&tx)
msg_, err := evmtypes.UnwrapEthereumMsg(&tx, msg.AsTransaction().Hash())
require.Nil(t, err)
require.Equal(t, msg_, msg)
}