diff --git a/rpc/eth_api.go b/rpc/eth_api.go index 293d5542..764eff58 100644 --- a/rpc/eth_api.go +++ b/rpc/eth_api.go @@ -5,21 +5,25 @@ import ( "fmt" "math/big" - "github.com/cosmos/cosmos-sdk/client/context" - authutils "github.com/cosmos/cosmos-sdk/x/auth/client/utils" emintcrypto "github.com/cosmos/ethermint/crypto" emintkeys "github.com/cosmos/ethermint/keys" "github.com/cosmos/ethermint/rpc/args" "github.com/cosmos/ethermint/version" "github.com/cosmos/ethermint/x/evm/types" + tmtypes "github.com/tendermint/tendermint/types" + "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" + "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/flags" + sdk "github.com/cosmos/cosmos-sdk/types" + authutils "github.com/cosmos/cosmos-sdk/x/auth/client/utils" + "github.com/spf13/viper" ) @@ -28,6 +32,7 @@ type PublicEthAPI struct { cliCtx context.CLIContext key emintcrypto.PrivKeySecp256k1 nonceLock *AddrLocker + gasLimit *int64 } // NewPublicEthAPI creates an instance of the public ETH Web3 API. @@ -314,13 +319,84 @@ func (e *PublicEthAPI) GetBlockByHash(hash common.Hash, fullTx bool) map[string] } // GetBlockByNumber returns the block identified by number. -func (e *PublicEthAPI) GetBlockByNumber(blockNum rpc.BlockNumber, fullTx bool) map[string]interface{} { - return nil +func (e *PublicEthAPI) GetBlockByNumber(blockNum rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) { + value := blockNum.Int64() + block, err := e.cliCtx.Client.Block(&value) + if err != nil { + return nil, err + } + header := block.BlockMeta.Header + + gasLimit, err := e.getGasLimit() + if err != nil { + return nil, err + } + + var gasUsed *big.Int + var transactions []interface{} + + if fullTx { + // Populate full transaction data + transactions, gasUsed = convertTransactionsToRPC(e.cliCtx, block.Block.Txs, + common.BytesToHash(header.ConsensusHash.Bytes()), uint64(header.Height)) + } else { + // TODO: Gas used not saved and cannot be calculated by hashes + // Return slice of transaction hashes + transactions = make([]interface{}, len(block.Block.Txs)) + for i, tx := range block.Block.Txs { + transactions[i] = common.BytesToHash(tx.Hash()) + } + } + + return formatBlock(header, block.Block.Size(), gasLimit, gasUsed, transactions), nil +} + +func formatBlock( + header tmtypes.Header, size int, gasLimit int64, + gasUsed *big.Int, transactions []interface{}, +) map[string]interface{} { + return map[string]interface{}{ + "number": hexutil.Uint64(header.Height), + "hash": hexutil.Bytes(header.ConsensusHash), + "parentHash": hexutil.Bytes(header.LastBlockID.Hash), + "nonce": nil, // PoW specific + "sha3Uncles": nil, // No uncles in Tendermint + "logsBloom": "", // TODO: Complete with #55 + "transactionsRoot": hexutil.Bytes(header.DataHash), + "stateRoot": hexutil.Bytes(header.AppHash), + "miner": hexutil.Bytes(header.ValidatorsHash), + "difficulty": nil, + "totalDifficulty": nil, + "extraData": nil, + "size": hexutil.Uint64(size), + "gasLimit": hexutil.Uint64(gasLimit), // Static gas limit + "gasUsed": (*hexutil.Big)(gasUsed), + "timestamp": hexutil.Uint64(header.Time.Unix()), + "transactions": transactions, + "uncles": nil, + } +} + +func convertTransactionsToRPC(cliCtx context.CLIContext, txs []tmtypes.Tx, blockHash common.Hash, height uint64) ([]interface{}, *big.Int) { + transactions := make([]interface{}, len(txs)) + gasUsed := big.NewInt(0) + for i, tx := range txs { + var stdTx sdk.Tx + err := cliCtx.Codec.UnmarshalBinaryLengthPrefixed(tx, &stdTx) + ethTx, ok := stdTx.(*types.EthereumTxMsg) + if !ok || err != nil { + continue + } + // TODO: Remove gas usage calculation if saving gasUsed per block + gasUsed.Add(gasUsed, ethTx.Fee()) + transactions[i] = newRPCTransaction(ethTx, blockHash, height, uint64(i)) + } + return transactions, gasUsed } // Transaction represents a transaction returned to RPC clients. type Transaction struct { - BlockHash common.Hash `json:"blockHash"` + BlockHash *common.Hash `json:"blockHash"` BlockNumber *hexutil.Big `json:"blockNumber"` From common.Address `json:"from"` Gas hexutil.Uint64 `json:"gas"` @@ -329,13 +405,40 @@ type Transaction struct { Input hexutil.Bytes `json:"input"` Nonce hexutil.Uint64 `json:"nonce"` To *common.Address `json:"to"` - TransactionIndex hexutil.Uint `json:"transactionIndex"` + TransactionIndex *hexutil.Uint64 `json:"transactionIndex"` Value *hexutil.Big `json:"value"` V *hexutil.Big `json:"v"` R *hexutil.Big `json:"r"` S *hexutil.Big `json:"s"` } +// newRPCTransaction returns a transaction that will serialize to the RPC +// representation, with the given location metadata set (if available). +func newRPCTransaction(tx *types.EthereumTxMsg, blockHash common.Hash, blockNumber uint64, index uint64) *Transaction { + // Verify signature and retrieve sender address + from, _ := tx.VerifySig(tx.ChainID()) + + result := &Transaction{ + From: from, + Gas: hexutil.Uint64(tx.Data.GasLimit), + GasPrice: (*hexutil.Big)(tx.Data.Price), + Hash: tx.Hash(), + Input: hexutil.Bytes(tx.Data.Payload), + Nonce: hexutil.Uint64(tx.Data.AccountNonce), + To: tx.To(), + Value: (*hexutil.Big)(tx.Data.Amount), + V: (*hexutil.Big)(tx.Data.V), + R: (*hexutil.Big)(tx.Data.R), + S: (*hexutil.Big)(tx.Data.S), + } + if blockHash != (common.Hash{}) { + result.BlockHash = &blockHash + result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber)) + result.TransactionIndex = (*hexutil.Uint64)(&index) + } + return result +} + // GetTransactionByHash returns the transaction identified by hash. func (e *PublicEthAPI) GetTransactionByHash(hash common.Hash) *Transaction { return nil @@ -365,3 +468,22 @@ func (e *PublicEthAPI) GetUncleByBlockHashAndIndex(hash common.Hash, idx hexutil func (e *PublicEthAPI) GetUncleByBlockNumberAndIndex(number hexutil.Uint, idx hexutil.Uint) map[string]interface{} { return nil } + +// getGasLimit returns the gas limit per block set in genesis +func (e *PublicEthAPI) getGasLimit() (int64, error) { + // Retrieve from gasLimit variable cache + if e.gasLimit != nil { + return *e.gasLimit, nil + } + + // Query genesis block if hasn't been retrieved yet + genesis, err := e.cliCtx.Client.Genesis() + if err != nil { + return 0, err + } + + // Save value to gasLimit cached value + gasLimit := genesis.Genesis.ConsensusParams.Block.MaxGas + e.gasLimit = &gasLimit + return gasLimit, nil +} diff --git a/x/evm/types/msg.go b/x/evm/types/msg.go index 6135f9f9..cc235a9b 100644 --- a/x/evm/types/msg.go +++ b/x/evm/types/msg.go @@ -299,6 +299,24 @@ func (msg EthereumTxMsg) Fee() *big.Int { return new(big.Int).Mul(msg.Data.Price, new(big.Int).SetUint64(msg.Data.GasLimit)) } +// ChainID returns which chain id this transaction was signed for (if at all) +func (msg *EthereumTxMsg) ChainID() *big.Int { + return deriveChainID(msg.Data.V) +} + +// deriveChainID derives the chain id from the given v parameter +func deriveChainID(v *big.Int) *big.Int { + if v.BitLen() <= 64 { + v := v.Uint64() + if v == 27 || v == 28 { + return new(big.Int) + } + return new(big.Int).SetUint64((v - 35) / 2) + } + v = new(big.Int).Sub(v, big.NewInt(35)) + return v.Div(v, big.NewInt(2)) +} + // ---------------------------------------------------------------------------- // Auxiliary @@ -360,7 +378,7 @@ func recoverEthSig(R, S, Vb *big.Int, sigHash ethcmn.Hash) (ethcmn.Address, erro return addr, nil } -// PopulateFromArgs populates tx message with args (used in RPC API) +// GenerateFromArgs populates tx message with args (used in RPC API) func GenerateFromArgs(args args.SendTxArgs, ctx context.CLIContext) (msg *EthereumTxMsg, err error) { var nonce uint64