rpc: add EIP1898 support (#462)

* support for eip1898

* update changelog

* fix linter

* fix linter

* refactor code

* add test

* support for eip1898

* update changelog

* fix linter

* fix linter

* refactor code

* add test

* cleanup code

* add comment

* create const for block param

* change default to be earliest to be consistant with geth

* correct comment

* update doc

* update doc

Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>
This commit is contained in:
Thomas Nguy
2021-08-25 01:21:57 +00:00
committed by GitHub
co-authored by Federico Kunze Küllmer
parent c0ca43fbf1
commit 4ea3cc190e
6 changed files with 278 additions and 23 deletions
+4
View File
@@ -318,6 +318,10 @@ func (e *EVMBackend) HeaderByHash(blockHash common.Hash) (*ethtypes.Header, erro
return nil, err
}
if resBlock.Block == nil {
return nil, errors.Errorf("block not found for hash %s", blockHash.Hex())
}
req := &evmtypes.QueryBlockBloomRequest{Height: resBlock.Block.Height}
blockBloomResp, err := e.queryClient.BlockBloom(types.ContextWithHeight(resBlock.Block.Height), req)
+62 -15
View File
@@ -197,8 +197,13 @@ func (e *PublicAPI) BlockNumber() (hexutil.Uint64, error) {
}
// GetBalance returns the provided account's balance up to the provided block number.
func (e *PublicAPI) GetBalance(address common.Address, blockNum rpctypes.BlockNumber) (*hexutil.Big, error) { // nolint: interfacer
e.logger.Debug("eth_getBalance", "address", address.String(), "block number", blockNum)
func (e *PublicAPI) GetBalance(address common.Address, blockNrOrHash rpctypes.BlockNumberOrHash) (*hexutil.Big, error) { // nolint: interfacer
e.logger.Debug("eth_getBalance", "address", address.String(), "block number or hash", blockNrOrHash)
blockNum, err := e.getBlockNumber(blockNrOrHash)
if err != nil {
return nil, err
}
req := &evmtypes.QueryBalanceRequest{
Address: address.String(),
@@ -218,8 +223,13 @@ func (e *PublicAPI) GetBalance(address common.Address, blockNum rpctypes.BlockNu
}
// GetStorageAt returns the contract storage at the given address, block number, and key.
func (e *PublicAPI) GetStorageAt(address common.Address, key string, blockNum rpctypes.BlockNumber) (hexutil.Bytes, error) { // nolint: interfacer
e.logger.Debug("eth_getStorageAt", "address", address.Hex(), "key", key, "block number", blockNum)
func (e *PublicAPI) GetStorageAt(address common.Address, key string, blockNrOrHash rpctypes.BlockNumberOrHash) (hexutil.Bytes, error) { // nolint: interfacer
e.logger.Debug("eth_getStorageAt", "address", address.Hex(), "key", key, "block number or hash", blockNrOrHash)
blockNum, err := e.getBlockNumber(blockNrOrHash)
if err != nil {
return nil, err
}
req := &evmtypes.QueryStorageRequest{
Address: address.String(),
@@ -236,8 +246,12 @@ func (e *PublicAPI) GetStorageAt(address common.Address, key string, blockNum rp
}
// GetTransactionCount returns the number of transactions at the given address up to the given block number.
func (e *PublicAPI) GetTransactionCount(address common.Address, blockNum rpctypes.BlockNumber) (*hexutil.Uint64, error) {
e.logger.Debug("eth_getTransactionCount", "address", address.Hex(), "block number", blockNum)
func (e *PublicAPI) GetTransactionCount(address common.Address, blockNrOrHash rpctypes.BlockNumberOrHash) (*hexutil.Uint64, error) {
e.logger.Debug("eth_getTransactionCount", "address", address.Hex(), "block number or hash", blockNrOrHash)
blockNum, err := e.getBlockNumber(blockNrOrHash)
if err != nil {
return nil, err
}
return e.backend.GetTransactionCount(address, blockNum)
}
@@ -289,14 +303,19 @@ func (e *PublicAPI) GetUncleCountByBlockNumber(blockNum rpctypes.BlockNumber) he
}
// GetCode returns the contract code at the given address and block number.
func (e *PublicAPI) GetCode(address common.Address, blockNumber rpctypes.BlockNumber) (hexutil.Bytes, error) { // nolint: interfacer
e.logger.Debug("eth_getCode", "address", address.Hex(), "block number", blockNumber)
func (e *PublicAPI) GetCode(address common.Address, blockNrOrHash rpctypes.BlockNumberOrHash) (hexutil.Bytes, error) { // nolint: interfacer
e.logger.Debug("eth_getCode", "address", address.Hex(), "block number or hash", blockNrOrHash)
blockNum, err := e.getBlockNumber(blockNrOrHash)
if err != nil {
return nil, err
}
req := &evmtypes.QueryCodeRequest{
Address: address.String(),
}
res, err := e.queryClient.Code(rpctypes.ContextWithHeight(blockNumber.Int64()), req)
res, err := e.queryClient.Code(rpctypes.ContextWithHeight(blockNum.Int64()), req)
if err != nil {
return nil, err
}
@@ -418,9 +437,14 @@ func (e *PublicAPI) SendRawTransaction(data hexutil.Bytes) (common.Hash, error)
}
// Call performs a raw contract call.
func (e *PublicAPI) Call(args evmtypes.CallArgs, blockNr rpctypes.BlockNumber, _ *rpctypes.StateOverride) (hexutil.Bytes, error) {
e.logger.Debug("eth_call", "args", args.String(), "block number", blockNr)
data, err := e.doCall(args, blockNr)
func (e *PublicAPI) Call(args evmtypes.CallArgs, blockNrOrHash rpctypes.BlockNumberOrHash, _ *rpctypes.StateOverride) (hexutil.Bytes, error) {
e.logger.Debug("eth_call", "args", args.String(), "block number or hash", blockNrOrHash)
blockNum, err := e.getBlockNumber(blockNrOrHash)
if err != nil {
return nil, err
}
data, err := e.doCall(args, blockNum)
if err != nil {
return []byte{}, err
}
@@ -791,9 +815,14 @@ func (e *PublicAPI) GetUncleByBlockNumberAndIndex(number hexutil.Uint, idx hexut
}
// GetProof returns an account object with proof and any storage proofs
func (e *PublicAPI) GetProof(address common.Address, storageKeys []string, blockNumber rpctypes.BlockNumber) (*rpctypes.AccountResult, error) {
height := blockNumber.Int64()
e.logger.Debug("eth_getProof", "address", address.Hex(), "keys", storageKeys, "number", height)
func (e *PublicAPI) GetProof(address common.Address, storageKeys []string, blockNrOrHash rpctypes.BlockNumberOrHash) (*rpctypes.AccountResult, error) {
e.logger.Debug("eth_getProof", "address", address.Hex(), "keys", storageKeys, "block number or hash", blockNrOrHash)
blockNum, err := e.getBlockNumber(blockNrOrHash)
if err != nil {
return nil, err
}
height := blockNum.Int64()
ctx := rpctypes.ContextWithHeight(height)
clientCtx := e.clientCtx.WithHeight(height)
@@ -859,3 +888,21 @@ func (e *PublicAPI) GetProof(address common.Address, storageKeys []string, block
StorageProof: storageProofs,
}, nil
}
// getBlockNumber returns the BlockNumber from BlockNumberOrHash
func (e *PublicAPI) getBlockNumber(blockNrOrHash rpctypes.BlockNumberOrHash) (rpctypes.BlockNumber, error) {
switch {
case blockNrOrHash.BlockHash == nil && blockNrOrHash.BlockNumber == nil:
return rpctypes.EthEarliestBlockNumber, fmt.Errorf("BlockHash and BlockNumber cannot be both nil")
case blockNrOrHash.BlockHash != nil:
blockHeader, err := e.backend.HeaderByHash(*blockNrOrHash.BlockHash)
if err != nil {
return rpctypes.EthEarliestBlockNumber, err
}
return rpctypes.NewBlockNumber(blockHeader.Number), nil
case blockNrOrHash.BlockNumber != nil:
return *blockNrOrHash.BlockNumber, nil
default:
return rpctypes.EthEarliestBlockNumber, nil
}
}
+83 -3
View File
@@ -2,11 +2,14 @@ package types
import (
"context"
"encoding/json"
"fmt"
"math"
"math/big"
"strings"
"github.com/ethereum/go-ethereum/common"
grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
"github.com/spf13/cast"
"google.golang.org/grpc/metadata"
@@ -23,6 +26,12 @@ const (
EthEarliestBlockNumber = BlockNumber(0)
)
const (
BlockParamEarliest = "earliest"
BlockParamLatest = "latest"
BlockParamPending = "pending"
)
// NewBlockNumber creates a new BlockNumber instance.
func NewBlockNumber(n *big.Int) BlockNumber {
return BlockNumber(n.Int64())
@@ -52,13 +61,13 @@ func (bn *BlockNumber) UnmarshalJSON(data []byte) error {
}
switch input {
case "earliest":
case BlockParamEarliest:
*bn = EthEarliestBlockNumber
return nil
case "latest":
case BlockParamLatest:
*bn = EthLatestBlockNumber
return nil
case "pending":
case BlockParamPending:
*bn = EthPendingBlockNumber
return nil
}
@@ -103,3 +112,74 @@ func (bn BlockNumber) TmHeight() *int64 {
height := bn.Int64()
return &height
}
// BlockNumberOrHash represents a block number or a block hash.
type BlockNumberOrHash struct {
BlockNumber *BlockNumber `json:"blockNumber,omitempty"`
BlockHash *common.Hash `json:"blockHash,omitempty"`
}
func (bnh *BlockNumberOrHash) UnmarshalJSON(data []byte) error {
type erased BlockNumberOrHash
e := erased{}
err := json.Unmarshal(data, &e)
if err == nil {
return bnh.checkUnmarshal(BlockNumberOrHash(e))
}
var input string
err = json.Unmarshal(data, &input)
if err != nil {
return err
}
err = bnh.decodeFromString(input)
if err != nil {
return err
}
return nil
}
func (bnh *BlockNumberOrHash) checkUnmarshal(e BlockNumberOrHash) error {
if e.BlockNumber != nil && e.BlockHash != nil {
return fmt.Errorf("cannot specify both BlockHash and BlockNumber, choose one or the other")
}
bnh.BlockNumber = e.BlockNumber
bnh.BlockHash = e.BlockHash
return nil
}
func (bnh *BlockNumberOrHash) decodeFromString(input string) error {
switch input {
case BlockParamEarliest:
bn := EthEarliestBlockNumber
bnh.BlockNumber = &bn
case BlockParamLatest:
bn := EthLatestBlockNumber
bnh.BlockNumber = &bn
case BlockParamPending:
bn := EthPendingBlockNumber
bnh.BlockNumber = &bn
default:
// check if the input is a block hash
if len(input) == 66 {
hash := common.Hash{}
err := hash.UnmarshalText([]byte(input))
if err != nil {
return err
}
bnh.BlockHash = &hash
break
}
// otherwise take the hex string has int64 value
blockNumber, err := hexutil.DecodeUint64(input)
if err != nil {
return err
}
if blockNumber > math.MaxInt64 {
return fmt.Errorf("blocknumber %d is too high", blockNumber)
}
bn := BlockNumber(blockNumber)
bnh.BlockNumber = &bn
}
return nil
}
+102
View File
@@ -0,0 +1,102 @@
package types
import (
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
"testing"
)
func TestUnmarshalBlockNumberOrHash(t *testing.T) {
bnh := new(BlockNumberOrHash)
testCases := []struct {
msg string
input []byte
malleate func()
expPass bool
}{
{
"JSON input with block hash",
[]byte("{\"blockHash\": \"0x579917054e325746fda5c3ee431d73d26255bc4e10b51163862368629ae19739\"}"),
func() {
require.Equal(t, *bnh.BlockHash, common.HexToHash("0x579917054e325746fda5c3ee431d73d26255bc4e10b51163862368629ae19739"))
require.Nil(t, bnh.BlockNumber)
},
true,
},
{
"JSON input with block number",
[]byte("{\"blockNumber\": \"0x35\"}"),
func() {
require.Equal(t, *bnh.BlockNumber, BlockNumber(0x35))
require.Nil(t, bnh.BlockHash)
},
true,
},
{
"JSON input with block number latest",
[]byte("{\"blockNumber\": \"latest\"}"),
func() {
require.Equal(t, *bnh.BlockNumber, EthLatestBlockNumber)
require.Nil(t, bnh.BlockHash)
},
true,
},
{
"JSON input with both block hash and block number",
[]byte("{\"blockHash\": \"0x579917054e325746fda5c3ee431d73d26255bc4e10b51163862368629ae19739\", \"blockNumber\": \"0x35\"}"),
func() {
},
false,
},
{
"String input with block hash",
[]byte("\"0x579917054e325746fda5c3ee431d73d26255bc4e10b51163862368629ae19739\""),
func() {
require.Equal(t, *bnh.BlockHash, common.HexToHash("0x579917054e325746fda5c3ee431d73d26255bc4e10b51163862368629ae19739"))
require.Nil(t, bnh.BlockNumber)
},
true,
},
{
"String input with block number",
[]byte("\"0x35\""),
func() {
require.Equal(t, *bnh.BlockNumber, BlockNumber(0x35))
require.Nil(t, bnh.BlockHash)
},
true,
},
{
"String input with block number latest",
[]byte("\"latest\""),
func() {
require.Equal(t, *bnh.BlockNumber, EthLatestBlockNumber)
require.Nil(t, bnh.BlockHash)
},
true,
},
{
"String input with block number overflow",
[]byte("\"0xffffffffffffffffffffffffffffffffffffff\""),
func() {
},
false,
},
}
for _, tc := range testCases {
fmt.Sprintf("Case %s", tc.msg)
// reset input
bnh = new(BlockNumberOrHash)
err := bnh.UnmarshalJSON(tc.input)
tc.malleate()
if tc.expPass {
require.NoError(t, err)
} else {
require.Error(t, err)
}
}
}