Skip to content
This repository has been archived by the owner on Apr 4, 2024. It is now read-only.

Commit

Permalink
feat: add raw ethereum tx CLI (#712)
Browse files Browse the repository at this point in the history
Closes #709

fix index

Apply suggestions from code review

Co-authored-by: Federico Kunze Küllmer <[email protected]>

fix lint

transaction decoding unit test

test BuildTx

fix lint

changelog
  • Loading branch information
yihuang authored Nov 2, 2021
1 parent d1446fc commit 10c49f7
Show file tree
Hide file tree
Showing 6 changed files with 334 additions and 81 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (rpc, evm) [tharsis#673](https://github.com/tharsis/ethermint/pull/673) Use tendermint events to store fee market basefee.
* (rpc) [tharsis#624](https://github.com/tharsis/ethermint/pull/624) Implement new JSON-RPC endpoints from latest geth version
* (evm) [tharsis#662](https://github.com/tharsis/ethermint/pull/662) Disable basefee for non london blocks
* (cmd) [tharsis#712](https://github.com/tharsis/ethermint/pull/712) add tx cli to build evm transaction

### Bug Fixes

Expand Down
33 changes: 3 additions & 30 deletions rpc/ethereum/namespaces/eth/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,8 @@ import (

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
sdk "github.com/cosmos/cosmos-sdk/types"
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"

"github.com/ethereum/go-ethereum/accounts/keystore"
Expand Down Expand Up @@ -452,46 +450,21 @@ func (e *PublicAPI) SendRawTransaction(data hexutil.Bytes) (common.Hash, error)
return common.Hash{}, err
}

builder, ok := e.clientCtx.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder)
if !ok {
e.logger.Error("clientCtx.TxConfig.NewTxBuilder returns unsupported builder")
}

option, err := codectypes.NewAnyWithValue(&evmtypes.ExtensionOptionsEthereumTx{})
if err != nil {
e.logger.Error("codectypes.NewAnyWithValue failed to pack an obvious value", "error", err.Error())
}

builder.SetExtensionOptions(option)
err = builder.SetMsgs(tx.GetMsgs()...)
if err != nil {
e.logger.Error("builder.SetMsgs failed", "error", err.Error())
}

// Query params to use the EVM denomination
res, err := e.queryClient.QueryClient.Params(e.ctx, &evmtypes.QueryParamsRequest{})
if err != nil {
e.logger.Error("failed to query evm params", "error", err.Error())
return common.Hash{}, err
}

txData, err := evmtypes.UnpackTxData(ethereumTx.Data)
cosmosTx, err := ethereumTx.BuildTx(e.clientCtx.TxConfig.NewTxBuilder(), res.Params.EvmDenom)
if err != nil {
e.logger.Error("failed to unpack tx data", "error", err.Error())
e.logger.Error("failed to build cosmos tx", "error", err.Error())
return common.Hash{}, err
}

fees := sdk.Coins{
{
Denom: res.Params.EvmDenom,
Amount: sdk.NewIntFromBigInt(txData.Fee()),
},
}
builder.SetFeeAmount(fees)
builder.SetGasLimit(ethereumTx.GetGas())

// Encode transaction by default Tx encoder
txBytes, err := e.clientCtx.TxConfig.TxEncoder()(builder.GetTx())
txBytes, err := e.clientCtx.TxConfig.TxEncoder()(cosmosTx)
if err != nil {
e.logger.Error("failed to encode eth tx using default encoder", "error", err.Error())
return common.Hash{}, err
Expand Down
111 changes: 111 additions & 0 deletions x/evm/client/cli/tx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package cli

import (
"bufio"
"fmt"
"os"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/input"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/pkg/errors"
"github.com/spf13/cobra"

rpctypes "github.com/tharsis/ethermint/rpc/ethereum/types"
"github.com/tharsis/ethermint/x/evm/types"
)

// GetTxCmd returns the transaction commands for this module
func GetTxCmd() *cobra.Command {
cmd := &cobra.Command{
Use: types.ModuleName,
Short: fmt.Sprintf("%s transactions subcommands", types.ModuleName),
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
RunE: client.ValidateCmd,
}
cmd.AddCommand(NewRawTxCmd())
return cmd
}

// NewRawTxCmd command build cosmos transaction from raw ethereum transaction
func NewRawTxCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "raw [tx-hex]",
Short: "Build cosmos transaction from raw ethereum transaction",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
data, err := hexutil.Decode(args[0])
if err != nil {
return errors.Wrap(err, "failed to decode ethereum tx hex bytes")
}

msg := &types.MsgEthereumTx{}
if err := msg.UnmarshalBinary(data); err != nil {
return err
}

if err := msg.ValidateBasic(); err != nil {
return err
}

clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}

rsp, err := rpctypes.NewQueryClient(clientCtx).Params(cmd.Context(), &types.QueryParamsRequest{})
if err != nil {
return err
}

tx, err := msg.BuildTx(clientCtx.TxConfig.NewTxBuilder(), rsp.Params.EvmDenom)
if err != nil {
return err
}

if clientCtx.GenerateOnly {
json, err := clientCtx.TxConfig.TxJSONEncoder()(tx)
if err != nil {
return err
}

return clientCtx.PrintString(fmt.Sprintf("%s\n", json))
}

if !clientCtx.SkipConfirm {
out, err := clientCtx.TxConfig.TxJSONEncoder()(tx)
if err != nil {
return err
}

_, _ = fmt.Fprintf(os.Stderr, "%s\n\n", out)

buf := bufio.NewReader(os.Stdin)
ok, err := input.GetConfirmation("confirm transaction before signing and broadcasting", buf, os.Stderr)

if err != nil || !ok {
_, _ = fmt.Fprintf(os.Stderr, "%s\n", "canceled transaction")
return err
}
}

txBytes, err := clientCtx.TxConfig.TxEncoder()(tx)
if err != nil {
return err
}

// broadcast to a Tendermint node
res, err := clientCtx.BroadcastTx(txBytes)
if err != nil {
return err
}

return clientCtx.PrintProto(res)
},
}

flags.AddTxFlagsToCmd(cmd)
return cmd
}
2 changes: 1 addition & 1 deletion x/evm/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func (b AppModuleBasic) RegisterGRPCGatewayRoutes(c client.Context, serveMux *ru

// GetTxCmd returns the root tx command for the evm module.
func (AppModuleBasic) GetTxCmd() *cobra.Command {
return nil
return cli.GetTxCmd()
}

// GetQueryCmd returns no root query command for the evm module.
Expand Down
47 changes: 47 additions & 0 deletions x/evm/types/msg.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
package types

import (
"errors"
"fmt"
"math/big"

"github.com/cosmos/cosmos-sdk/client"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/auth/ante"
"github.com/cosmos/cosmos-sdk/x/auth/signing"
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"

"github.com/tharsis/ethermint/types"

Expand Down Expand Up @@ -282,3 +286,46 @@ func (msg *MsgEthereumTx) GetSender(chainID *big.Int) (common.Address, error) {
func (msg MsgEthereumTx) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error {
return unpacker.UnpackAny(msg.Data, new(TxData))
}

// UnmarshalBinary decodes the canonical encoding of transactions.
func (msg *MsgEthereumTx) UnmarshalBinary(b []byte) error {
tx := &ethtypes.Transaction{}
if err := tx.UnmarshalBinary(b); err != nil {
return err
}
return msg.FromEthereumTx(tx)
}

// BuildTx builds the canonical cosmos tx from ethereum msg
func (msg *MsgEthereumTx) BuildTx(b client.TxBuilder, evmDenom string) (signing.Tx, error) {
builder, ok := b.(authtx.ExtensionOptionsTxBuilder)
if !ok {
return nil, errors.New("unsupported builder")
}

option, err := codectypes.NewAnyWithValue(&ExtensionOptionsEthereumTx{})
if err != nil {
return nil, err
}

txData, err := UnpackTxData(msg.Data)
if err != nil {
return nil, err
}
fees := sdk.Coins{
{
Denom: evmDenom,
Amount: sdk.NewIntFromBigInt(txData.Fee()),
},
}

builder.SetExtensionOptions(option)
err = builder.SetMsgs(msg)
if err != nil {
return nil, err
}
builder.SetFeeAmount(fees)
builder.SetGasLimit(msg.GetGas())
tx := builder.GetTx()
return tx, nil
}
Loading

0 comments on commit 10c49f7

Please sign in to comment.