eth_getBlockByNumber impl (#87)

* WIP implement eth_getBlockByNumber

* Implemented some missing fields

* Added gasLimit and updated previous fields

* Implement remaining pieces for eth_getBlock including decoding txs

* Add converting transaction objects into function for usability

* Clean up code

* format block in function for usability

* Fixed formatting and cached gasLimit value
This commit is contained in:
Austin Abell 2019-09-24 14:39:17 -04:00 committed by GitHub
parent 1cac4feb4d
commit 4a030335e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 147 additions and 7 deletions

View File

@ -5,21 +5,25 @@ import (
"fmt" "fmt"
"math/big" "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" emintcrypto "github.com/cosmos/ethermint/crypto"
emintkeys "github.com/cosmos/ethermint/keys" emintkeys "github.com/cosmos/ethermint/keys"
"github.com/cosmos/ethermint/rpc/args" "github.com/cosmos/ethermint/rpc/args"
"github.com/cosmos/ethermint/version" "github.com/cosmos/ethermint/version"
"github.com/cosmos/ethermint/x/evm/types" "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/accounts/keystore"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/flags" "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" "github.com/spf13/viper"
) )
@ -28,6 +32,7 @@ type PublicEthAPI struct {
cliCtx context.CLIContext cliCtx context.CLIContext
key emintcrypto.PrivKeySecp256k1 key emintcrypto.PrivKeySecp256k1
nonceLock *AddrLocker nonceLock *AddrLocker
gasLimit *int64
} }
// NewPublicEthAPI creates an instance of the public ETH Web3 API. // 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. // GetBlockByNumber returns the block identified by number.
func (e *PublicEthAPI) GetBlockByNumber(blockNum rpc.BlockNumber, fullTx bool) map[string]interface{} { func (e *PublicEthAPI) GetBlockByNumber(blockNum rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) {
return nil 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. // Transaction represents a transaction returned to RPC clients.
type Transaction struct { type Transaction struct {
BlockHash common.Hash `json:"blockHash"` BlockHash *common.Hash `json:"blockHash"`
BlockNumber *hexutil.Big `json:"blockNumber"` BlockNumber *hexutil.Big `json:"blockNumber"`
From common.Address `json:"from"` From common.Address `json:"from"`
Gas hexutil.Uint64 `json:"gas"` Gas hexutil.Uint64 `json:"gas"`
@ -329,13 +405,40 @@ type Transaction struct {
Input hexutil.Bytes `json:"input"` Input hexutil.Bytes `json:"input"`
Nonce hexutil.Uint64 `json:"nonce"` Nonce hexutil.Uint64 `json:"nonce"`
To *common.Address `json:"to"` To *common.Address `json:"to"`
TransactionIndex hexutil.Uint `json:"transactionIndex"` TransactionIndex *hexutil.Uint64 `json:"transactionIndex"`
Value *hexutil.Big `json:"value"` Value *hexutil.Big `json:"value"`
V *hexutil.Big `json:"v"` V *hexutil.Big `json:"v"`
R *hexutil.Big `json:"r"` R *hexutil.Big `json:"r"`
S *hexutil.Big `json:"s"` 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. // GetTransactionByHash returns the transaction identified by hash.
func (e *PublicEthAPI) GetTransactionByHash(hash common.Hash) *Transaction { func (e *PublicEthAPI) GetTransactionByHash(hash common.Hash) *Transaction {
return nil 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{} { func (e *PublicEthAPI) GetUncleByBlockNumberAndIndex(number hexutil.Uint, idx hexutil.Uint) map[string]interface{} {
return nil 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
}

View File

@ -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)) 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 // Auxiliary
@ -360,7 +378,7 @@ func recoverEthSig(R, S, Vb *big.Int, sigHash ethcmn.Hash) (ethcmn.Address, erro
return addr, nil 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) { func GenerateFromArgs(args args.SendTxArgs, ctx context.CLIContext) (msg *EthereumTxMsg, err error) {
var nonce uint64 var nonce uint64