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 10:21:57 +09:00 committed by GitHub
parent c0ca43fbf1
commit 4ea3cc190e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 278 additions and 23 deletions

View File

@ -99,6 +99,7 @@ the Tracer type used to collect execution traces from the EVM transaction execut
* (rpc) [#313](https://github.com/tharsis/ethermint/pull/313) Implement internal debug namespace (Not including logger functions nor traces).
* (rpc) [#349](https://github.com/tharsis/ethermint/pull/349) Implement configurable JSON-RPC APIs to manage enabled namespaces.
* (rpc) [#377](https://github.com/tharsis/ethermint/pull/377) Implement `miner_` namespace. `miner_setEtherbase` and `miner_setGasPrice` are working as intended. All the other calls are not applicable and return `unsupported`.
* (eth) [tharsis#460](https://github.com/tharsis/ethermint/issues/460) Add support for EIP-1898.
### Bug Fixes

View File

@ -63,6 +63,7 @@ Check the JSON-RPC methods supported on Ethermint. {synopsis}
| `eth_getUncleCountByBlockNumber` | Eth | N/A | Not relevant to Ethermint |
| `eth_getUncleByBlockHashAndIndex` | Eth | N/A | Not relevant to Ethermint |
| `eth_getUncleByBlockNumberAndIndex` | Eth | N/A | Not relevant to Ethermint |
| [`eth_getProof`](#eth-getProof) | Eth | ✔ | |
| [`eth_subscribe`](#eth-subscribe) | Websocket | ✔ | |
| [`eth_unsubscribe`](#eth-unsubscribe) | Websocket | ✔ | |
| [`personal_importRawKey`](#personal-importrawkey) | Personal | ✔ | |
@ -299,7 +300,7 @@ Returns the account balance for a given account address and Block Number.
- Account Address
- Block Number
- Block Number or Block Hash [(eip-1898)](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1898.md)
```json
// Request
@ -319,7 +320,7 @@ Returns the storage address for a given account address.
- Integer of the position in the storage
- Block Number
- Block Number or Block Hash [(eip-1898)](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1898.md)
```json
// Request
@ -337,7 +338,7 @@ Returns the total transaction for a given account address and Block Number.
- Account Address
- Block Number
- Block Number or Block Hash [(eip-1898)](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1898.md)
```json
// Request
@ -387,7 +388,7 @@ Returns the code for a given account address and Block Number.
- Account Address
- Block Number
- Block Number or Block Hash [(eip-1898)](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1898.md)
```json
// Request
@ -489,7 +490,7 @@ Executes a new message call immediately without creating a transaction on the bl
data: DATA - (optional) Hash of the method signature and encoded parameters. For details see Ethereum Contract ABI in the Solidity documentation
- Block number
- Block number or Block Hash [(eip-1898)](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1898.md)
```json
// Request
@ -778,6 +779,26 @@ curl -X POST --data '{"jsonrpc":"2.0","method":"eth_coinbase","params":[],"id":1
{"jsonrpc":"2.0","id":1,"result":"0x7cB61D4117AE31a12E393a1Cfa3BaC666481D02E"}
```
### eth_getProof
Returns the account- and storage-values of the specified account including the Merkle-proof.
#### Parameters
- Address of account or contract
- Integer of the position in the storage
- Block Number or Block Hash [(eip-1898)](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1898.md)
```json
// Request
curl -X POST --data '{"jsonrpc":"2.0","method":"eth_getProof","params":["0x1234567890123456789012345678901234567890",["0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000001"],"latest"],"id":1}' -H "Content-type:application/json" http://localhost:8545
// Result
{"jsonrpc": "2.0", "id": 1, "result": {"address": "0x1234567890123456789012345678901234567890", "accountProof": ["0xf90211a090dcaf88c40c7bbc95a912cbdde67c175767b31173df9ee4b0d733bfdd511c43a0babe369f6b12092f49181ae04ca173fb68d1a5456f18d20fa32cba73954052bda0473ecf8a7e36a829e75039a3b055e51b8332cbf03324ab4af2066bbd6fbf0021a0bbda34753d7aa6c38e603f360244e8f59611921d9e1f128372fec0d586d4f9e0a04e44caecff45c9891f74f6a2156735886eedf6f1a733628ebc802ec79d844648a0a5f3f2f7542148c973977c8a1e154c4300fec92f755f7846f1b734d3ab1d90e7a0e823850f50bf72baae9d1733a36a444ab65d0a6faaba404f0583ce0ca4dad92da0f7a00cbe7d4b30b11faea3ae61b7f1f2b315b61d9f6bd68bfe587ad0eeceb721a07117ef9fc932f1a88e908eaead8565c19b5645dc9e5b1b6e841c5edbdfd71681a069eb2de283f32c11f859d7bcf93da23990d3e662935ed4d6b39ce3673ec84472a0203d26456312bbc4da5cd293b75b840fc5045e493d6f904d180823ec22bfed8ea09287b5c21f2254af4e64fca76acc5cd87399c7f1ede818db4326c98ce2dc2208a06fc2d754e304c48ce6a517753c62b1a9c1d5925b89707486d7fc08919e0a94eca07b1c54f15e299bd58bdfef9741538c7828b5d7d11a489f9c20d052b3471df475a051f9dd3739a927c89e357580a4c97b40234aa01ed3d5e0390dc982a7975880a0a089d613f26159af43616fd9455bb461f4869bfede26f2130835ed067a8b967bfb80", "0xf90211a0395d87a95873cd98c21cf1df9421af03f7247880a2554e20738eec2c7507a494a0bcf6546339a1e7e14eb8fb572a968d217d2a0d1f3bc4257b22ef5333e9e4433ca012ae12498af8b2752c99efce07f3feef8ec910493be749acd63822c3558e6671a0dbf51303afdc36fc0c2d68a9bb05dab4f4917e7531e4a37ab0a153472d1b86e2a0ae90b50f067d9a2244e3d975233c0a0558c39ee152969f6678790abf773a9621a01d65cd682cc1be7c5e38d8da5c942e0a73eeaef10f387340a40a106699d494c3a06163b53d956c55544390c13634ea9aa75309f4fd866f312586942daf0f60fb37a058a52c1e858b1382a8893eb9c1f111f266eb9e21e6137aff0dddea243a567000a037b4b100761e02de63ea5f1fcfcf43e81a372dafb4419d126342136d329b7a7ba032472415864b08f808ba4374092003c8d7c40a9f7f9fe9cc8291f62538e1cc14a074e238ff5ec96b810364515551344100138916594d6af966170ff326a092fab0a0d31ac4eef14a79845200a496662e92186ca8b55e29ed0f9f59dbc6b521b116fea090607784fe738458b63c1942bba7c0321ae77e18df4961b2bc66727ea996464ea078f757653c1b63f72aff3dcc3f2a2e4c8cb4a9d36d1117c742833c84e20de994a0f78407de07f4b4cb4f899dfb95eedeb4049aeb5fc1635d65cf2f2f4dfd25d1d7a0862037513ba9d45354dd3e36264aceb2b862ac79d2050f14c95657e43a51b85c80", "0xf90171a04ad705ea7bf04339fa36b124fa221379bd5a38ffe9a6112cb2d94be3a437b879a08e45b5f72e8149c01efcb71429841d6a8879d4bbe27335604a5bff8dfdf85dcea00313d9b2f7c03733d6549ea3b810e5262ed844ea12f70993d87d3e0f04e3979ea0b59e3cdd6750fa8b15164612a5cb6567cdfb386d4e0137fccee5f35ab55d0efda0fe6db56e42f2057a071c980a778d9a0b61038f269dd74a0e90155b3f40f14364a08538587f2378a0849f9608942cf481da4120c360f8391bbcc225d811823c6432a026eac94e755534e16f9552e73025d6d9c30d1d7682a4cb5bd7741ddabfd48c50a041557da9a74ca68da793e743e81e2029b2835e1cc16e9e25bd0c1e89d4ccad6980a041dda0a40a21ade3a20fcd1a4abb2a42b74e9a32b02424ff8db4ea708a5e0fb9a09aaf8326a51f613607a8685f57458329b41e938bb761131a5747e066b81a0a16808080a022e6cef138e16d2272ef58434ddf49260dc1de1f8ad6dfca3da5d2a92aaaadc58080", "0xf851808080a009833150c367df138f1538689984b8a84fc55692d3d41fe4d1e5720ff5483a6980808080808080808080a0a319c1c415b271afc0adcb664e67738d103ac168e0bc0b7bd2da7966165cb9518080"], "balance": "0x0", "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "nonce": "0x0", "storageHash": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "storageProof": [{"key": "0x0000000000000000000000000000000000000000000000000000000000000000", "value": "0x0", "proof": []}, {"key": "0x0000000000000000000000000000000000000000000000000000000000000001", "value": "0x0", "proof": []}]}}
```
## WebSocket Methods
Read about websockets in [events](./../quickstart/events.md) {hide}

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)

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
}
}

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
}

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)
}
}
}