laconicd/rpc/backend.go
Federico Kunze 988ee53a59
rpc: implement eth_coinbase (#325)
* rpc: implement eth_coinbase

* changelog

* address comments from review

* changelog
2020-06-08 12:43:37 -04:00

203 lines
5.8 KiB
Go

package rpc
import (
"fmt"
"math/big"
"strconv"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/ethermint/x/evm"
"github.com/cosmos/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"
)
// Backend implements the functionality needed to filter changes.
// Implemented by EthermintBackend.
type Backend interface {
// Used by block filter; also used for polling
BlockNumber() (hexutil.Uint64, error)
GetBlockByNumber(blockNum BlockNumber, fullTx bool) (map[string]interface{}, error)
GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error)
getEthBlockByNumber(height int64, fullTx bool) (map[string]interface{}, error)
getGasLimit() (int64, error)
// Used by pending transaction filter
PendingTransactions() ([]*Transaction, error)
// Used by log filter
GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error)
// TODO: Bloom methods
}
// EmintBackend implements Backend
type EthermintBackend struct {
cliCtx context.CLIContext
gasLimit int64
}
func NewEthermintBackend(cliCtx context.CLIContext) *EthermintBackend {
return &EthermintBackend{
cliCtx: cliCtx,
gasLimit: int64(^uint32(0)),
}
}
// BlockNumber returns the current block number.
func (e *EthermintBackend) BlockNumber() (hexutil.Uint64, error) {
res, height, err := e.cliCtx.QueryWithData(fmt.Sprintf("custom/%s/blockNumber", types.ModuleName), nil)
if err != nil {
return hexutil.Uint64(0), err
}
var out types.QueryResBlockNumber
e.cliCtx.Codec.MustUnmarshalJSON(res, &out)
e.cliCtx.WithHeight(height)
return hexutil.Uint64(out.Number), nil
}
// GetBlockByNumber returns the block identified by number.
func (e *EthermintBackend) GetBlockByNumber(blockNum BlockNumber, fullTx bool) (map[string]interface{}, error) {
value := blockNum.Int64()
return e.getEthBlockByNumber(value, fullTx)
}
// GetBlockByHash returns the block identified by hash.
func (e *EthermintBackend) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) {
res, _, err := e.cliCtx.Query(fmt.Sprintf("custom/%s/%s/%s", types.ModuleName, evm.QueryHashToHeight, hash.Hex()))
if err != nil {
return nil, err
}
var out types.QueryResBlockNumber
if err := e.cliCtx.Codec.UnmarshalJSON(res, &out); err != nil {
return nil, err
}
return e.getEthBlockByNumber(out.Number, fullTx)
}
func (e *EthermintBackend) getEthBlockByNumber(height int64, fullTx bool) (map[string]interface{}, error) {
// Remove this check when 0 query is fixed ref: (https://github.com/tendermint/tendermint/issues/4014)
var blkNumPtr *int64
if height != 0 {
blkNumPtr = &height
}
block, err := e.cliCtx.Client.Block(blkNumPtr)
if err != nil {
return nil, err
}
header := block.Block.Header
gasLimit, err := e.getGasLimit()
if err != nil {
return nil, err
}
var (
gasUsed *big.Int
transactions []common.Hash
)
if fullTx {
// Populate full transaction data
transactions, gasUsed, err = convertTransactionsToRPC(
e.cliCtx, block.Block.Txs, common.BytesToHash(header.Hash()), uint64(header.Height),
)
if err != nil {
return nil, err
}
} else {
// TODO: Gas used not saved and cannot be calculated by hashes
// Return slice of transaction hashes
transactions = make([]common.Hash, len(block.Block.Txs))
for i, tx := range block.Block.Txs {
transactions[i] = common.BytesToHash(tx.Hash())
}
}
res, _, err := e.cliCtx.Query(fmt.Sprintf("custom/%s/%s/%s", types.ModuleName, evm.QueryBloom, strconv.FormatInt(block.Block.Height, 10)))
if err != nil {
return nil, err
}
var out types.QueryBloomFilter
e.cliCtx.Codec.MustUnmarshalJSON(res, &out)
return formatBlock(header, block.Block.Size(), gasLimit, gasUsed, transactions, out.Bloom), nil
}
// getGasLimit returns the gas limit per block set in genesis
func (e *EthermintBackend) getGasLimit() (int64, error) {
// Retrieve from gasLimit variable cache
if e.gasLimit != -1 {
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
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))
}
e.gasLimit = gasLimit
return gasLimit, nil
}
// GetTransactionLogs returns the logs given a transaction hash.
func (e *EthermintBackend) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error) {
// do we need to use the block height somewhere?
ctx := e.cliCtx
res, _, err := ctx.QueryWithData(fmt.Sprintf("custom/%s/%s/%s", types.ModuleName, evm.QueryTransactionLogs, txHash.Hex()), nil)
if err != nil {
return nil, err
}
out := new(types.QueryETHLogs)
if err := e.cliCtx.Codec.UnmarshalJSON(res, &out); err != nil {
return nil, err
}
return out.Logs, nil
}
// PendingTransactions returns the transactions that are in the transaction pool
// and have a from address that is one of the accounts this node manages.
func (e *EthermintBackend) PendingTransactions() ([]*Transaction, error) {
pendingTxs, err := e.cliCtx.Client.UnconfirmedTxs(100)
if err != nil {
return nil, err
}
transactions := make([]*Transaction, 0, 100)
for _, tx := range pendingTxs.Txs {
ethTx, err := bytesToEthTx(e.cliCtx, tx)
if err != nil {
return nil, err
}
// * Should check signer and reference against accounts the node manages in future
rpcTx, err := newRPCTransaction(*ethTx, common.BytesToHash(tx.Hash()), common.Hash{}, nil, 0)
if err != nil {
return nil, err
}
transactions = append(transactions, rpcTx)
}
return transactions, nil
}