package types import ( "context" "encoding/hex" "fmt" "math/big" tmbytes "github.com/tendermint/tendermint/libs/bytes" 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" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" 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()) } ethTx, ok := tx.(*evmtypes.MsgEthereumTx) if !ok { return nil, fmt.Errorf("invalid transaction type %T, expected %T", tx, evmtypes.MsgEthereumTx{}) } return ethTx, nil } // EthBlockFromTendermint returns a JSON-RPC compatible Ethereum blockfrom a given Tendermint block. func EthBlockFromTendermint(clientCtx client.Context, queryClient *QueryClient, block *tmtypes.Block) (map[string]interface{}, error) { gasLimit, err := BlockMaxGasFromConsensusParams(context.Background(), clientCtx) if err != nil { return nil, err } transactions, gasUsed, err := EthTransactionsFromTendermint(clientCtx, block.Txs) if err != nil { return nil, err } req := &evmtypes.QueryBlockBloomRequest{} res, err := queryClient.BlockBloom(ContextWithHeight(block.Height), req) if err != nil { return nil, err } bloom := ethtypes.BytesToBloom(res.Bloom) return FormatBlock(block.Header, block.Size(), gasLimit, gasUsed, transactions, bloom), nil } // NewTransaction returns a transaction that will serialize to the RPC // representation, with the given location metadata set (if available). func NewTransaction(tx *ethtypes.Transaction, blockHash common.Hash, blockNumber uint64, index uint64) *RPCTransaction { // 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(), // NOTE: transaction hash here uses the ethereum format for compatibility 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) } if tx.Type() == ethtypes.AccessListTxType { al := tx.AccessList() result.Accesses = &al result.ChainID = (*hexutil.Big)(tx.ChainId()) } return result } // EthHeaderFromTendermint is an util function that returns an Ethereum Header // from a tendermint Header. func EthHeaderFromTendermint(header tmtypes.Header) *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.Address{}, Root: common.BytesToHash(header.AppHash), TxHash: txHash, ReceiptHash: ethtypes.EmptyRootHash, Bloom: ethtypes.Bloom{}, Difficulty: big.NewInt(0), Number: big.NewInt(header.Height), GasLimit: 0, GasUsed: 0, Time: uint64(header.Time.Unix()), Extra: nil, MixDigest: common.Hash{}, Nonce: ethtypes.BlockNonce{}, } } // EthTransactionsFromTendermint returns a slice of ethereum transaction hashes and the total gas usage from a set of // tendermint block transactions. func EthTransactionsFromTendermint(clientCtx client.Context, txs []tmtypes.Tx) ([]common.Hash, *big.Int, error) { transactionHashes := []common.Hash{} gasUsed := big.NewInt(0) for _, tx := range txs { ethTx, err := RawTxToEthTx(clientCtx, tx) if err != nil { // continue to next transaction in case it's not a MsgEthereumTx continue } data, err := evmtypes.UnpackTxData(ethTx.Data) if err != nil { return nil, nil, fmt.Errorf("failed to unpack tx data: %w", err) } // TODO: Remove gas usage calculation if saving gasUsed per block gasUsed.Add(gasUsed, data.Fee()) transactionHashes = append(transactionHashes, common.BytesToHash(tx.Hash())) } return transactionHashes, gasUsed, nil } // BlockMaxGasFromConsensusParams returns the gas limit for the latest block from the chain consensus params. func BlockMaxGasFromConsensusParams(ctx context.Context, clientCtx client.Context) (int64, error) { resConsParams, err := clientCtx.Client.ConsensusParams(ctx, nil) if err != nil { return 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, ) map[string]interface{} { if len(header.DataHash) == 0 { header.DataHash = tmbytes.HexBytes(common.Hash{}.Bytes()) } return 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": common.Address{}, "mixHash": common.Hash{}, "difficulty": (*hexutil.Big)(big.NewInt(0)), "extraData": "", "size": hexutil.Uint64(size), "gasLimit": hexutil.Uint64(gasLimit), // Static gas limit "gasUsed": (*hexutil.Big)(gasUsed), "timestamp": hexutil.Uint64(header.Time.Unix()), "transactionsRoot": hexutil.Bytes(header.DataHash), "receiptsRoot": ethtypes.EmptyRootHash, "uncles": []common.Hash{}, "transactions": transactions, "totalDifficulty": (*hexutil.Big)(big.NewInt(0)), } } 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, chainID *big.Int, ) (*RPCTransaction, error) { from, err := msg.GetSender(chainID) if err != nil { return nil, err } data, err := evmtypes.UnpackTxData(msg.Data) if err != nil { return nil, fmt.Errorf("failed to unpack tx data: %w", err) } return NewTransactionFromData(data, from, msg.AsTransaction().Hash(), blockHash, blockNumber, index) } // NewTransactionFromData returns a transaction that will serialize to the RPC // representation, with the given location metadata set (if available). func NewTransactionFromData( txData evmtypes.TxData, from common.Address, txHash, blockHash common.Hash, blockNumber, index uint64, ) (*RPCTransaction, error) { if txHash == (common.Hash{}) { txHash = ethtypes.EmptyRootHash } v, r, s := txData.GetRawSignatureValues() rpcTx := &RPCTransaction{ Type: hexutil.Uint64(txData.TxType()), From: from, Gas: hexutil.Uint64(txData.GetGas()), GasPrice: (*hexutil.Big)(txData.GetGasPrice()), Hash: txHash, Input: hexutil.Bytes(txData.GetData()), Nonce: hexutil.Uint64(txData.GetNonce()), To: txData.GetTo(), Value: (*hexutil.Big)(txData.GetValue()), V: (*hexutil.Big)(v), R: (*hexutil.Big)(r), S: (*hexutil.Big)(s), } if rpcTx.To == nil { addr := common.Address{} rpcTx.To = &addr } if blockHash != (common.Hash{}) { rpcTx.BlockHash = &blockHash rpcTx.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber)) rpcTx.TransactionIndex = (*hexutil.Uint64)(&index) } if txData.TxType() == ethtypes.AccessListTxType { accesses := txData.GetAccessList() rpcTx.Accesses = &accesses rpcTx.ChainID = (*hexutil.Big)(txData.GetChainID()) } return rpcTx, nil }