diff --git a/go.mod b/go.mod index 77993556..bfb4587e 100644 --- a/go.mod +++ b/go.mod @@ -53,7 +53,6 @@ require ( golang.org/x/net v0.0.0-20190628185345-da137c7871d7 golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb // indirect golang.org/x/text v0.3.2 // indirect - google.golang.org/appengine v1.4.0 // indirect google.golang.org/genproto v0.0.0-20190708153700-3bdd9d9f5532 // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/yaml.v2 v2.2.2 diff --git a/rpc/eth_api.go b/rpc/eth_api.go index d7c2de5b..e2b1a788 100644 --- a/rpc/eth_api.go +++ b/rpc/eth_api.go @@ -14,6 +14,7 @@ import ( "github.com/cosmos/ethermint/x/evm" "github.com/cosmos/ethermint/x/evm/types" + "github.com/tendermint/tendermint/rpc/client" tmtypes "github.com/tendermint/tendermint/types" "github.com/ethereum/go-ethereum/accounts/keystore" @@ -599,7 +600,7 @@ func (e *PublicEthAPI) GetTransactionReceipt(hash common.Hash) (map[string]inter return nil, err } - var logs types.QueryTxLogs + var logs types.QueryETHLogs e.cliCtx.Codec.MustUnmarshalJSON(res, &logs) // TODO: change hard coded indexing of bytes @@ -639,6 +640,68 @@ func (e *PublicEthAPI) GetUncleByBlockNumberAndIndex(number hexutil.Uint, idx he return nil } +// AccountResult struct for account proof +type AccountResult struct { + Address common.Address `json:"address"` + AccountProof []string `json:"accountProof"` + Balance *hexutil.Big `json:"balance"` + CodeHash common.Hash `json:"codeHash"` + Nonce hexutil.Uint64 `json:"nonce"` + StorageHash common.Hash `json:"storageHash"` + StorageProof []StorageResult `json:"storageProof"` +} + +// StorageResult defines the format for storage proof return +type StorageResult struct { + Key string `json:"key"` + Value *hexutil.Big `json:"value"` + Proof []string `json:"proof"` +} + +// GetProof returns an account object with proof and any storage proofs +func (e *PublicEthAPI) GetProof(address common.Address, storageKeys []string, block BlockNumber) (*AccountResult, error) { + opts := client.ABCIQueryOptions{Height: int64(block), Prove: true} + path := fmt.Sprintf("custom/%s/%s/%s", types.ModuleName, evm.QueryAccount, address.Hex()) + pRes, err := e.cliCtx.Client.ABCIQueryWithOptions(path, nil, opts) + if err != nil { + return nil, err + } + + // TODO: convert TM merkle proof to []string if needed in future + // proof := pRes.Response.GetProof() + + account := new(types.QueryAccount) + e.cliCtx.Codec.MustUnmarshalJSON(pRes.Response.GetValue(), &account) + + storageProofs := make([]StorageResult, len(storageKeys)) + for i, k := range storageKeys { + // Get value for key + vPath := fmt.Sprintf("custom/%s/%s/%s/%s", types.ModuleName, evm.QueryStorage, address, k) + vRes, err := e.cliCtx.Client.ABCIQueryWithOptions(vPath, nil, opts) + if err != nil { + return nil, err + } + value := new(types.QueryResStorage) + e.cliCtx.Codec.MustUnmarshalJSON(vRes.Response.GetValue(), &value) + + storageProofs[i] = StorageResult{ + Key: k, + Value: (*hexutil.Big)(common.BytesToHash(value.Value).Big()), + Proof: []string{""}, + } + } + + return &AccountResult{ + Address: address, + AccountProof: []string{""}, // This shouldn't be necessary (different proof formats) + Balance: (*hexutil.Big)(utils.MustUnmarshalBigInt(account.Balance)), + CodeHash: common.BytesToHash(account.CodeHash), + Nonce: hexutil.Uint64(account.Nonce), + StorageHash: common.Hash{}, // Ethermint doesn't have a storage hash + StorageProof: storageProofs, + }, nil +} + // getGasLimit returns the gas limit per block set in genesis func (e *PublicEthAPI) getGasLimit() (int64, error) { // Retrieve from gasLimit variable cache diff --git a/utils/int.go b/utils/int.go index 6c820045..c059bc8b 100644 --- a/utils/int.go +++ b/utils/int.go @@ -17,3 +17,13 @@ func UnmarshalBigInt(s string) (*big.Int, error) { err := ret.UnmarshalText([]byte(s)) return ret, err } + +// MustUnmarshalBigInt unmarshalls string from *big.Int +func MustUnmarshalBigInt(s string) *big.Int { + ret := new(big.Int) + err := ret.UnmarshalText([]byte(s)) + if err != nil { + panic(err) + } + return ret +} diff --git a/x/evm/querier.go b/x/evm/querier.go index 9f077e9e..c151e20f 100644 --- a/x/evm/querier.go +++ b/x/evm/querier.go @@ -25,6 +25,7 @@ const ( QueryTxLogs = "txLogs" QueryLogsBloom = "logsBloom" QueryLogs = "logs" + QueryAccount = "account" ) // NewQuerier is the module level router for state queries @@ -50,7 +51,9 @@ func NewQuerier(keeper Keeper) sdk.Querier { case QueryLogsBloom: return queryBlockLogsBloom(ctx, path, keeper) case QueryLogs: - return queryLogs(ctx, path, keeper) + return queryLogs(ctx, keeper) + case QueryAccount: + return queryAccount(ctx, path, keeper) default: return nil, sdk.ErrUnknownRequest("unknown query endpoint") } @@ -162,7 +165,7 @@ func queryTxLogs(ctx sdk.Context, path []string, keeper Keeper) ([]byte, sdk.Err txHash := ethcmn.HexToHash(path[1]) logs := keeper.GetLogs(ctx, txHash) - bRes := types.QueryTxLogs{Logs: logs} + bRes := types.QueryETHLogs{Logs: logs} res, err := codec.MarshalJSONIndent(keeper.cdc, bRes) if err != nil { panic("could not marshal result to JSON: " + err.Error()) @@ -171,10 +174,27 @@ func queryTxLogs(ctx sdk.Context, path []string, keeper Keeper) ([]byte, sdk.Err return res, nil } -func queryLogs(ctx sdk.Context, path []string, keeper Keeper) ([]byte, sdk.Error) { +func queryLogs(ctx sdk.Context, keeper Keeper) ([]byte, sdk.Error) { logs := keeper.Logs(ctx) - l, err := codec.MarshalJSONIndent(keeper.cdc, logs) + lRes := types.QueryETHLogs{Logs: logs} + l, err := codec.MarshalJSONIndent(keeper.cdc, lRes) + if err != nil { + panic("could not marshal result to JSON: " + err.Error()) + } + return l, nil +} + +func queryAccount(ctx sdk.Context, path []string, keeper Keeper) ([]byte, sdk.Error) { + addr := ethcmn.HexToAddress(path[1]) + so := keeper.GetOrNewStateObject(ctx, addr) + + lRes := types.QueryAccount{ + Balance: utils.MarshalBigInt(so.Balance()), + CodeHash: so.CodeHash(), + Nonce: so.Nonce(), + } + l, err := codec.MarshalJSONIndent(keeper.cdc, lRes) if err != nil { panic("could not marshal result to JSON: " + err.Error()) } diff --git a/x/evm/types/querier.go b/x/evm/types/querier.go index a8d48ed9..77ad945f 100644 --- a/x/evm/types/querier.go +++ b/x/evm/types/querier.go @@ -60,12 +60,12 @@ func (q QueryResNonce) String() string { return string(q.Nonce) } -// QueryTxLogs is response type for tx logs query -type QueryTxLogs struct { +// QueryETHLogs is response type for tx logs query +type QueryETHLogs struct { Logs []*ethtypes.Log `json:"logs"` } -func (q QueryTxLogs) String() string { +func (q QueryETHLogs) String() string { return string(fmt.Sprintf("%+v", q.Logs)) } @@ -77,3 +77,10 @@ type QueryBloomFilter struct { func (q QueryBloomFilter) String() string { return string(q.Bloom.Bytes()) } + +// QueryAccount is response type for querying Ethereum state objects +type QueryAccount struct { + Balance string `json:"balance"` + CodeHash []byte `json:"codeHash"` + Nonce uint64 `json:"nonce"` +}