diff --git a/ethereum/rpc/apis.go b/ethereum/rpc/apis.go index 91eb10ee..224cdfc5 100644 --- a/ethereum/rpc/apis.go +++ b/ethereum/rpc/apis.go @@ -5,6 +5,13 @@ package rpc import ( "github.com/cosmos/cosmos-sdk/client" "github.com/ethereum/go-ethereum/rpc" + "github.com/tharsis/ethermint/ethereum/rpc/backend" + "github.com/tharsis/ethermint/ethereum/rpc/namespaces/eth" + "github.com/tharsis/ethermint/ethereum/rpc/namespaces/eth/filters" + "github.com/tharsis/ethermint/ethereum/rpc/namespaces/net" + "github.com/tharsis/ethermint/ethereum/rpc/namespaces/personal" + "github.com/tharsis/ethermint/ethereum/rpc/namespaces/txpool" + "github.com/tharsis/ethermint/ethereum/rpc/namespaces/web3" "github.com/tharsis/ethermint/ethereum/rpc/types" rpcclient "github.com/tendermint/tendermint/rpc/jsonrpc/client" @@ -24,14 +31,14 @@ const ( // GetRPCAPIs returns the list of all APIs func GetRPCAPIs(clientCtx client.Context, tmWSClient *rpcclient.WSClient) []rpc.API { nonceLock := new(types.AddrLocker) - backend := NewEVMBackend(clientCtx) - ethAPI := NewPublicEthAPI(clientCtx, backend, nonceLock) + backend := backend.NewEVMBackend(clientCtx) + ethAPI := eth.NewPublicAPI(clientCtx, backend, nonceLock) return []rpc.API{ { Namespace: Web3Namespace, Version: apiVersion, - Service: NewPublicWeb3API(), + Service: web3.NewPublicAPI(), Public: true, }, { @@ -43,25 +50,25 @@ func GetRPCAPIs(clientCtx client.Context, tmWSClient *rpcclient.WSClient) []rpc. { Namespace: EthNamespace, Version: apiVersion, - Service: NewPublicFilterAPI(tmWSClient, backend), + Service: filters.NewPublicAPI(tmWSClient, backend), Public: true, }, { Namespace: NetNamespace, Version: apiVersion, - Service: NewPublicNetAPI(clientCtx), + Service: net.NewPublicAPI(clientCtx), Public: true, }, { Namespace: PersonalNamespace, Version: apiVersion, - Service: NewPersonalAPI(ethAPI), + Service: personal.NewAPI(ethAPI), Public: true, }, { Namespace: TxPoolNamespace, Version: apiVersion, - Service: NewPublicTxPoolAPI(), + Service: txpool.NewPublicAPI(), Public: true, }, } diff --git a/ethereum/rpc/backend.go b/ethereum/rpc/backend/backend.go similarity index 99% rename from ethereum/rpc/backend.go rename to ethereum/rpc/backend/backend.go index f5cc2174..075ed581 100644 --- a/ethereum/rpc/backend.go +++ b/ethereum/rpc/backend/backend.go @@ -1,4 +1,4 @@ -package rpc +package backend import ( "context" diff --git a/ethereum/rpc/eth_api.go b/ethereum/rpc/namespaces/eth/api.go similarity index 89% rename from ethereum/rpc/eth_api.go rename to ethereum/rpc/namespaces/eth/api.go index 9c4245d3..af45189b 100644 --- a/ethereum/rpc/eth_api.go +++ b/ethereum/rpc/namespaces/eth/api.go @@ -1,4 +1,4 @@ -package rpc +package eth import ( "bytes" @@ -30,28 +30,29 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/tharsis/ethermint/crypto/hd" + "github.com/tharsis/ethermint/ethereum/rpc/backend" rpctypes "github.com/tharsis/ethermint/ethereum/rpc/types" ethermint "github.com/tharsis/ethermint/types" evmtypes "github.com/tharsis/ethermint/x/evm/types" ) -// PublicEthAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec. -type PublicEthAPI struct { +// PublicAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec. +type PublicAPI struct { ctx context.Context clientCtx client.Context queryClient *rpctypes.QueryClient chainIDEpoch *big.Int logger log.Logger - backend Backend + backend backend.Backend nonceLock *rpctypes.AddrLocker } -// NewPublicEthAPI creates an instance of the public ETH Web3 API. -func NewPublicEthAPI( +// NewPublicAPI creates an instance of the public ETH Web3 API. +func NewPublicAPI( clientCtx client.Context, - backend Backend, + backend backend.Backend, nonceLock *rpctypes.AddrLocker, -) *PublicEthAPI { +) *PublicAPI { epoch, err := ethermint.ParseChainID(clientCtx.ChainID) if err != nil { panic(err) @@ -75,7 +76,7 @@ func NewPublicEthAPI( clientCtx = clientCtx.WithKeyring(kr) } - api := &PublicEthAPI{ + api := &PublicAPI{ ctx: context.Background(), clientCtx: clientCtx, queryClient: rpctypes.NewQueryClient(clientCtx), @@ -89,25 +90,25 @@ func NewPublicEthAPI( } // ClientCtx returns client context -func (e *PublicEthAPI) ClientCtx() client.Context { +func (e *PublicAPI) ClientCtx() client.Context { return e.clientCtx } // ProtocolVersion returns the supported Ethereum protocol version. -func (e *PublicEthAPI) ProtocolVersion() hexutil.Uint { +func (e *PublicAPI) ProtocolVersion() hexutil.Uint { e.logger.Debugln("eth_protocolVersion") return hexutil.Uint(ethermint.ProtocolVersion) } // ChainId returns the chain's identifier in hex format -func (e *PublicEthAPI) ChainId() (hexutil.Uint, error) { // nolint +func (e *PublicAPI) ChainId() (hexutil.Uint, error) { // nolint e.logger.Debugln("eth_chainId") return hexutil.Uint(uint(e.chainIDEpoch.Uint64())), nil } // Syncing returns whether or not the current node is syncing with other peers. Returns false if not, or a struct // outlining the state of the sync if it is. -func (e *PublicEthAPI) Syncing() (interface{}, error) { +func (e *PublicAPI) Syncing() (interface{}, error) { e.logger.Debugln("eth_syncing") status, err := e.clientCtx.Client.Status(e.ctx) @@ -129,7 +130,7 @@ func (e *PublicEthAPI) Syncing() (interface{}, error) { } // Coinbase is the address that staking rewards will be send to (alias for Etherbase). -func (e *PublicEthAPI) Coinbase() (string, error) { +func (e *PublicAPI) Coinbase() (string, error) { e.logger.Debugln("eth_coinbase") node, err := e.clientCtx.GetNode() @@ -157,19 +158,19 @@ func (e *PublicEthAPI) Coinbase() (string, error) { } // Mining returns whether or not this node is currently mining. Always false. -func (e *PublicEthAPI) Mining() bool { +func (e *PublicAPI) Mining() bool { e.logger.Debugln("eth_mining") return false } // Hashrate returns the current node's hashrate. Always 0. -func (e *PublicEthAPI) Hashrate() hexutil.Uint64 { +func (e *PublicAPI) Hashrate() hexutil.Uint64 { e.logger.Debugln("eth_hashrate") return 0 } // GasPrice returns the current gas price based on Ethermint's gas price oracle. -func (e *PublicEthAPI) GasPrice() *hexutil.Big { +func (e *PublicAPI) GasPrice() *hexutil.Big { e.logger.Debugln("eth_gasPrice") // TODO: use minimum value defined in config instead of default or implement oracle out := big.NewInt(ethermint.DefaultGasPrice) @@ -177,7 +178,7 @@ func (e *PublicEthAPI) GasPrice() *hexutil.Big { } // Accounts returns the list of accounts available to this node. -func (e *PublicEthAPI) Accounts() ([]common.Address, error) { +func (e *PublicAPI) Accounts() ([]common.Address, error) { e.logger.Debugln("eth_accounts") addresses := make([]common.Address, 0) // return [] instead of nil if empty @@ -196,13 +197,13 @@ func (e *PublicEthAPI) Accounts() ([]common.Address, error) { } // BlockNumber returns the current block number. -func (e *PublicEthAPI) BlockNumber() (hexutil.Uint64, error) { +func (e *PublicAPI) BlockNumber() (hexutil.Uint64, error) { //e.logger.Debugln("eth_blockNumber") return e.backend.BlockNumber() } // GetBalance returns the provided account's balance up to the provided block number. -func (e *PublicEthAPI) GetBalance(address common.Address, blockNum rpctypes.BlockNumber) (*hexutil.Big, error) { // nolint: interfacer +func (e *PublicAPI) GetBalance(address common.Address, blockNum rpctypes.BlockNumber) (*hexutil.Big, error) { // nolint: interfacer e.logger.Debugln("eth_getBalance", "address", address.String(), "block number", blockNum) req := &evmtypes.QueryBalanceRequest{ @@ -223,7 +224,7 @@ func (e *PublicEthAPI) GetBalance(address common.Address, blockNum rpctypes.Bloc } // GetStorageAt returns the contract storage at the given address, block number, and key. -func (e *PublicEthAPI) GetStorageAt(address common.Address, key string, blockNum rpctypes.BlockNumber) (hexutil.Bytes, error) { // nolint: interfacer +func (e *PublicAPI) GetStorageAt(address common.Address, key string, blockNum rpctypes.BlockNumber) (hexutil.Bytes, error) { // nolint: interfacer e.logger.Debugln("eth_getStorageAt", "address", address.Hex(), "key", key, "block number", blockNum) req := &evmtypes.QueryStorageRequest{ @@ -241,7 +242,7 @@ func (e *PublicEthAPI) GetStorageAt(address common.Address, key string, blockNum } // GetTransactionCount returns the number of transactions at the given address up to the given block number. -func (e *PublicEthAPI) GetTransactionCount(address common.Address, blockNum rpctypes.BlockNumber) (*hexutil.Uint64, error) { +func (e *PublicAPI) GetTransactionCount(address common.Address, blockNum rpctypes.BlockNumber) (*hexutil.Uint64, error) { e.logger.Debugln("eth_getTransactionCount", "address", address.Hex(), "block number", blockNum) // Get nonce (sequence) from account @@ -265,7 +266,7 @@ func (e *PublicEthAPI) GetTransactionCount(address common.Address, blockNum rpct } // GetBlockTransactionCountByHash returns the number of transactions in the block identified by hash. -func (e *PublicEthAPI) GetBlockTransactionCountByHash(hash common.Hash) *hexutil.Uint { +func (e *PublicAPI) GetBlockTransactionCountByHash(hash common.Hash) *hexutil.Uint { e.logger.Debugln("eth_getBlockTransactionCountByHash", "hash", hash.Hex()) resBlock, err := e.clientCtx.Client.BlockByHash(e.ctx, hash.Bytes()) @@ -278,7 +279,7 @@ func (e *PublicEthAPI) GetBlockTransactionCountByHash(hash common.Hash) *hexutil } // GetBlockTransactionCountByNumber returns the number of transactions in the block identified by number. -func (e *PublicEthAPI) GetBlockTransactionCountByNumber(blockNum rpctypes.BlockNumber) *hexutil.Uint { +func (e *PublicAPI) GetBlockTransactionCountByNumber(blockNum rpctypes.BlockNumber) *hexutil.Uint { e.logger.Debugln("eth_getBlockTransactionCountByNumber", "block number", blockNum) resBlock, err := e.clientCtx.Client.Block(e.ctx, blockNum.TmHeight()) if err != nil { @@ -290,17 +291,17 @@ func (e *PublicEthAPI) GetBlockTransactionCountByNumber(blockNum rpctypes.BlockN } // GetUncleCountByBlockHash returns the number of uncles in the block identified by hash. Always zero. -func (e *PublicEthAPI) GetUncleCountByBlockHash(hash common.Hash) hexutil.Uint { +func (e *PublicAPI) GetUncleCountByBlockHash(hash common.Hash) hexutil.Uint { return 0 } // GetUncleCountByBlockNumber returns the number of uncles in the block identified by number. Always zero. -func (e *PublicEthAPI) GetUncleCountByBlockNumber(blockNum rpctypes.BlockNumber) hexutil.Uint { +func (e *PublicAPI) GetUncleCountByBlockNumber(blockNum rpctypes.BlockNumber) hexutil.Uint { return 0 } // GetCode returns the contract code at the given address and block number. -func (e *PublicEthAPI) GetCode(address common.Address, blockNumber rpctypes.BlockNumber) (hexutil.Bytes, error) { // nolint: interfacer +func (e *PublicAPI) GetCode(address common.Address, blockNumber rpctypes.BlockNumber) (hexutil.Bytes, error) { // nolint: interfacer e.logger.Debugln("eth_getCode", "address", address.Hex(), "block number", blockNumber) req := &evmtypes.QueryCodeRequest{ @@ -316,13 +317,13 @@ func (e *PublicEthAPI) GetCode(address common.Address, blockNumber rpctypes.Bloc } // GetTransactionLogs returns the logs given a transaction hash. -func (e *PublicEthAPI) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error) { +func (e *PublicAPI) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error) { e.logger.Debugln("eth_getTransactionLogs", "hash", txHash) return e.backend.GetTransactionLogs(txHash) } // Sign signs the provided data using the private key of address via Geth's signature standard. -func (e *PublicEthAPI) Sign(address common.Address, data hexutil.Bytes) (hexutil.Bytes, error) { +func (e *PublicAPI) Sign(address common.Address, data hexutil.Bytes) (hexutil.Bytes, error) { e.logger.Debugln("eth_sign", "address", address.Hex(), "data", common.Bytes2Hex(data)) from := sdk.AccAddress(address.Bytes()) @@ -345,7 +346,7 @@ func (e *PublicEthAPI) Sign(address common.Address, data hexutil.Bytes) (hexutil } // SendTransaction sends an Ethereum transaction. -func (e *PublicEthAPI) SendTransaction(args rpctypes.SendTxArgs) (common.Hash, error) { +func (e *PublicAPI) SendTransaction(args rpctypes.SendTxArgs) (common.Hash, error) { e.logger.Debugln("eth_sendTransaction", "args", args) // Look up the wallet containing the requested signer @@ -428,7 +429,7 @@ func (e *PublicEthAPI) SendTransaction(args rpctypes.SendTxArgs) (common.Hash, e } // SendRawTransaction send a raw Ethereum transaction. -func (e *PublicEthAPI) SendRawTransaction(data hexutil.Bytes) (common.Hash, error) { +func (e *PublicAPI) SendRawTransaction(data hexutil.Bytes) (common.Hash, error) { e.logger.Debugln("eth_sendRawTransaction", "data_len", len(data)) // RLP decode raw transaction bytes @@ -491,7 +492,7 @@ func (e *PublicEthAPI) SendRawTransaction(data hexutil.Bytes) (common.Hash, erro } // Call performs a raw contract call. -func (e *PublicEthAPI) Call(args rpctypes.CallArgs, blockNr rpctypes.BlockNumber, _ *rpctypes.StateOverride) (hexutil.Bytes, error) { +func (e *PublicAPI) Call(args rpctypes.CallArgs, blockNr rpctypes.BlockNumber, _ *rpctypes.StateOverride) (hexutil.Bytes, error) { e.logger.Debugln("eth_call", "args", args, "block number", blockNr) simRes, err := e.doCall(args, blockNr, big.NewInt(ethermint.DefaultRPCGasLimit)) if err != nil { @@ -513,7 +514,7 @@ func (e *PublicEthAPI) Call(args rpctypes.CallArgs, blockNr rpctypes.BlockNumber // DoCall performs a simulated call operation through the evmtypes. It returns the // estimated gas used on the operation or an error if fails. -func (e *PublicEthAPI) doCall( +func (e *PublicAPI) doCall( args rpctypes.CallArgs, blockNr rpctypes.BlockNumber, globalGasCap *big.Int, ) (*sdk.SimulationResponse, error) { // Set default gas & gas price if none were set @@ -627,7 +628,7 @@ func (e *PublicEthAPI) doCall( // EstimateGas returns an estimate of gas usage for the given smart contract call. // It adds 1,000 gas to the returned value instead of using the gas adjustment // param from the SDK. -func (e *PublicEthAPI) EstimateGas(args rpctypes.CallArgs) (hexutil.Uint64, error) { +func (e *PublicAPI) EstimateGas(args rpctypes.CallArgs) (hexutil.Uint64, error) { e.logger.Debugln("eth_estimateGas") // From ContextWithHeight: if the provided height is 0, @@ -656,19 +657,19 @@ func (e *PublicEthAPI) EstimateGas(args rpctypes.CallArgs) (hexutil.Uint64, erro } // GetBlockByHash returns the block identified by hash. -func (e *PublicEthAPI) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) { +func (e *PublicAPI) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) { e.logger.Debugln("eth_getBlockByHash", "hash", hash.Hex(), "full", fullTx) return e.backend.GetBlockByHash(hash, fullTx) } // GetBlockByNumber returns the block identified by number. -func (e *PublicEthAPI) GetBlockByNumber(ethBlockNum rpctypes.BlockNumber, fullTx bool) (map[string]interface{}, error) { +func (e *PublicAPI) GetBlockByNumber(ethBlockNum rpctypes.BlockNumber, fullTx bool) (map[string]interface{}, error) { e.logger.Debugln("eth_getBlockByNumber", "number", ethBlockNum, "full", fullTx) return e.backend.GetBlockByNumber(ethBlockNum, fullTx) } // GetTransactionByHash returns the transaction identified by hash. -func (e *PublicEthAPI) GetTransactionByHash(hash common.Hash) (*rpctypes.RPCTransaction, error) { +func (e *PublicAPI) GetTransactionByHash(hash common.Hash) (*rpctypes.RPCTransaction, error) { e.logger.Debugln("eth_getTransactionByHash", "hash", hash.Hex()) res, err := e.clientCtx.Client.Tx(e.ctx, hash.Bytes(), false) @@ -713,7 +714,7 @@ func (e *PublicEthAPI) GetTransactionByHash(hash common.Hash) (*rpctypes.RPCTran } // GetTransactionByBlockHashAndIndex returns the transaction identified by hash and index. -func (e *PublicEthAPI) GetTransactionByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) (*rpctypes.RPCTransaction, error) { +func (e *PublicAPI) GetTransactionByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) (*rpctypes.RPCTransaction, error) { e.logger.Debugln("eth_getTransactionByBlockHashAndIndex", "hash", hash.Hex(), "index", idx) resBlock, err := e.clientCtx.Client.BlockByHash(e.ctx, hash.Bytes()) @@ -756,7 +757,7 @@ func (e *PublicEthAPI) GetTransactionByBlockHashAndIndex(hash common.Hash, idx h } // GetTransactionByBlockNumberAndIndex returns the transaction identified by number and index. -func (e *PublicEthAPI) GetTransactionByBlockNumberAndIndex(blockNum rpctypes.BlockNumber, idx hexutil.Uint) (*rpctypes.RPCTransaction, error) { +func (e *PublicAPI) GetTransactionByBlockNumberAndIndex(blockNum rpctypes.BlockNumber, idx hexutil.Uint) (*rpctypes.RPCTransaction, error) { e.logger.Debugln("eth_getTransactionByBlockNumberAndIndex", "number", blockNum, "index", idx) resBlock, err := e.clientCtx.Client.Block(e.ctx, blockNum.TmHeight()) @@ -803,7 +804,7 @@ func (e *PublicEthAPI) GetTransactionByBlockNumberAndIndex(blockNum rpctypes.Blo } // GetTransactionReceipt returns the transaction receipt identified by hash. -func (e *PublicEthAPI) GetTransactionReceipt(hash common.Hash) (map[string]interface{}, error) { +func (e *PublicAPI) GetTransactionReceipt(hash common.Hash) (map[string]interface{}, error) { e.logger.Debugln("eth_getTransactionReceipt", "hash", hash.Hex()) res, err := e.clientCtx.Client.Tx(e.ctx, hash.Bytes(), false) @@ -908,23 +909,23 @@ func (e *PublicEthAPI) GetTransactionReceipt(hash common.Hash) (map[string]inter // 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 *PublicEthAPI) PendingTransactions() ([]*rpctypes.RPCTransaction, error) { +func (e *PublicAPI) PendingTransactions() ([]*rpctypes.RPCTransaction, error) { e.logger.Debugln("eth_getPendingTransactions") return e.backend.PendingTransactions() } // GetUncleByBlockHashAndIndex returns the uncle identified by hash and index. Always returns nil. -func (e *PublicEthAPI) GetUncleByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) map[string]interface{} { +func (e *PublicAPI) GetUncleByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) map[string]interface{} { return nil } // GetUncleByBlockNumberAndIndex returns the uncle identified by number and index. Always returns nil. -func (e *PublicEthAPI) GetUncleByBlockNumberAndIndex(number hexutil.Uint, idx hexutil.Uint) map[string]interface{} { +func (e *PublicAPI) GetUncleByBlockNumberAndIndex(number hexutil.Uint, idx hexutil.Uint) map[string]interface{} { return nil } // GetProof returns an account object with proof and any storage proofs -func (e *PublicEthAPI) GetProof(address common.Address, storageKeys []string, blockNumber rpctypes.BlockNumber) (*rpctypes.AccountResult, error) { +func (e *PublicAPI) GetProof(address common.Address, storageKeys []string, blockNumber rpctypes.BlockNumber) (*rpctypes.AccountResult, error) { height := blockNumber.Int64() e.logger.Debugln("eth_getProof", "address", address.Hex(), "keys", storageKeys, "number", height) @@ -994,7 +995,7 @@ func (e *PublicEthAPI) GetProof(address common.Address, storageKeys []string, bl // setTxDefaults populates tx message with default values in case they are not // provided on the args -func (e *PublicEthAPI) setTxDefaults(args rpctypes.SendTxArgs) (rpctypes.SendTxArgs, error) { +func (e *PublicAPI) setTxDefaults(args rpctypes.SendTxArgs) (rpctypes.SendTxArgs, error) { // Get nonce (sequence) from sender account from := sdk.AccAddress(args.From.Bytes()) diff --git a/ethereum/rpc/filter_api.go b/ethereum/rpc/namespaces/eth/filters/api.go similarity index 95% rename from ethereum/rpc/filter_api.go rename to ethereum/rpc/namespaces/eth/filters/api.go index 194be41b..7cd29675 100644 --- a/ethereum/rpc/filter_api.go +++ b/ethereum/rpc/namespaces/eth/filters/api.go @@ -1,4 +1,4 @@ -package rpc +package filters import ( "context" @@ -57,8 +57,8 @@ type PublicFilterAPI struct { filters map[rpc.ID]*filter } -// NewPublicFilterAPI returns a new PublicFilterAPI instance. -func NewPublicFilterAPI(tmWSClient *rpcclient.WSClient, backend FiltersBackend) *PublicFilterAPI { +// NewPublicAPI returns a new PublicFilterAPI instance. +func NewPublicAPI(tmWSClient *rpcclient.WSClient, backend FiltersBackend) *PublicFilterAPI { api := &PublicFilterAPI{ backend: backend, filters: make(map[rpc.ID]*filter), @@ -371,7 +371,7 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri return } - logs := filterLogs(evmtypes.LogsToEthereum(txResponse.Logs), crit.FromBlock, crit.ToBlock, crit.Addresses, crit.Topics) + logs := FilterLogs(evmtypes.LogsToEthereum(txResponse.Logs), crit.FromBlock, crit.ToBlock, crit.Addresses, crit.Topics) for _, log := range logs { err = notifier.Notify(rpcSub.ID, log) @@ -448,7 +448,7 @@ func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID, return } - logs := filterLogs(evmtypes.LogsToEthereum(txResponse.Logs), criteria.FromBlock, criteria.ToBlock, criteria.Addresses, criteria.Topics) + logs := FilterLogs(evmtypes.LogsToEthereum(txResponse.Logs), criteria.FromBlock, criteria.ToBlock, criteria.Addresses, criteria.Topics) api.filtersMu.Lock() if f, found := api.filters[filterID]; found { @@ -595,21 +595,3 @@ func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) { return nil, fmt.Errorf("invalid filter %s type %d", id, f.typ) } } - -// returnHashes is a helper that will return an empty hash array case the given hash array is nil, -// otherwise the given hashes array is returned. -func returnHashes(hashes []common.Hash) []common.Hash { - if hashes == nil { - return []common.Hash{} - } - return hashes -} - -// returnLogs is a helper that will return an empty log array in case the given logs array is nil, -// otherwise the given logs array is returned. -func returnLogs(logs []*ethtypes.Log) []*ethtypes.Log { - if logs == nil { - return []*ethtypes.Log{} - } - return logs -} diff --git a/ethereum/rpc/filter_system.go b/ethereum/rpc/namespaces/eth/filters/filter_system.go similarity index 87% rename from ethereum/rpc/filter_system.go rename to ethereum/rpc/namespaces/eth/filters/filter_system.go index 5cf0dfae..2dd50abb 100644 --- a/ethereum/rpc/filter_system.go +++ b/ethereum/rpc/namespaces/eth/filters/filter_system.go @@ -1,4 +1,4 @@ -package rpc +package filters import ( "context" @@ -297,49 +297,3 @@ func (es *EventSystem) consumeEvents() { time.Sleep(time.Second) } } - -// Subscription defines a wrapper for the private subscription -type Subscription struct { - id rpc.ID - typ filters.Type - event string - created time.Time - logsCrit filters.FilterCriteria - logs chan []*ethtypes.Log - hashes chan []common.Hash - headers chan *ethtypes.Header - installed chan struct{} // closed when the filter is installed - eventCh <-chan coretypes.ResultEvent - err chan error -} - -// ID returns the underlying subscription RPC identifier. -func (s Subscription) ID() rpc.ID { - return s.id -} - -// Unsubscribe from the current subscription to Tendermint Websocket. It sends an error to the -// subscription error channel if unsubscription fails. -func (s *Subscription) Unsubscribe(es *EventSystem) { - go func() { - uninstallLoop: - for { - // write uninstall request and consume logs/hashes. This prevents - // the eventLoop broadcast method to deadlock when writing to the - // filter event channel while the subscription loop is waiting for - // this method to return (and thus not reading these events). - select { - case es.uninstall <- s: - break uninstallLoop - case <-s.logs: - case <-s.hashes: - case <-s.headers: - } - } - }() -} - -// Err returns the error channel -func (s *Subscription) Err() <-chan error { - return s.err -} diff --git a/ethereum/rpc/filters.go b/ethereum/rpc/namespaces/eth/filters/filters.go similarity index 76% rename from ethereum/rpc/filters.go rename to ethereum/rpc/namespaces/eth/filters/filters.go index 2ffcabd5..9e0c507b 100644 --- a/ethereum/rpc/filters.go +++ b/ethereum/rpc/namespaces/eth/filters/filters.go @@ -1,4 +1,4 @@ -package rpc +package filters import ( "context" @@ -198,7 +198,7 @@ func (f *Filter) blockLogs(header *ethtypes.Header) ([]*ethtypes.Log, error) { unfiltered = append(unfiltered, logs...) } - logs := filterLogs(unfiltered, nil, nil, f.criteria.Addresses, f.criteria.Topics) + logs := FilterLogs(unfiltered, nil, nil, f.criteria.Addresses, f.criteria.Topics) if len(logs) == 0 { return []*ethtypes.Log{}, nil } @@ -222,81 +222,5 @@ func (f *Filter) checkMatches(transactions []common.Hash) []*ethtypes.Log { unfiltered = append(unfiltered, logs...) } - return filterLogs(unfiltered, f.criteria.FromBlock, f.criteria.ToBlock, f.criteria.Addresses, f.criteria.Topics) -} - -// filterLogs creates a slice of logs matching the given criteria. -// [] -> anything -// [A] -> A in first position of log topics, anything after -// [null, B] -> anything in first position, B in second position -// [A, B] -> A in first position and B in second position -// [[A, B], [A, B]] -> A or B in first position, A or B in second position -func filterLogs(logs []*ethtypes.Log, fromBlock, toBlock *big.Int, addresses []common.Address, topics [][]common.Hash) []*ethtypes.Log { - var ret []*ethtypes.Log -Logs: - for _, log := range logs { - if fromBlock != nil && fromBlock.Int64() >= 0 && fromBlock.Uint64() > log.BlockNumber { - continue - } - if toBlock != nil && toBlock.Int64() >= 0 && toBlock.Uint64() < log.BlockNumber { - continue - } - if len(addresses) > 0 && !includes(addresses, log.Address) { - continue - } - // If the to filtered topics is greater than the amount of topics in logs, skip. - if len(topics) > len(log.Topics) { - continue - } - for i, sub := range topics { - match := len(sub) == 0 // empty rule set == wildcard - for _, topic := range sub { - if log.Topics[i] == topic { - match = true - break - } - } - if !match { - continue Logs - } - } - ret = append(ret, log) - } - return ret -} - -func includes(addresses []common.Address, a common.Address) bool { - for _, addr := range addresses { - if addr == a { - return true - } - } - - return false -} - -func bloomFilter(bloom ethtypes.Bloom, addresses []common.Address, topics [][]common.Hash) bool { - var included bool - if len(addresses) > 0 { - for _, addr := range addresses { - if ethtypes.BloomLookup(bloom, addr) { - included = true - break - } - } - if !included { - return false - } - } - - for _, sub := range topics { - included = len(sub) == 0 // empty rule set == wildcard - for _, topic := range sub { - if ethtypes.BloomLookup(bloom, topic) { - included = true - break - } - } - } - return included + return FilterLogs(unfiltered, f.criteria.FromBlock, f.criteria.ToBlock, f.criteria.Addresses, f.criteria.Topics) } diff --git a/ethereum/rpc/namespaces/eth/filters/subscription.go b/ethereum/rpc/namespaces/eth/filters/subscription.go new file mode 100644 index 00000000..87237740 --- /dev/null +++ b/ethereum/rpc/namespaces/eth/filters/subscription.go @@ -0,0 +1,62 @@ +package filters + +import ( + "time" + + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/filters" + "github.com/ethereum/go-ethereum/rpc" + coretypes "github.com/tendermint/tendermint/rpc/core/types" +) + +// Subscription defines a wrapper for the private subscription +type Subscription struct { + id rpc.ID + typ filters.Type + event string + created time.Time + logsCrit filters.FilterCriteria + logs chan []*ethtypes.Log + hashes chan []common.Hash + headers chan *ethtypes.Header + installed chan struct{} // closed when the filter is installed + eventCh <-chan coretypes.ResultEvent + err chan error +} + +// ID returns the underlying subscription RPC identifier. +func (s Subscription) ID() rpc.ID { + return s.id +} + +// Unsubscribe from the current subscription to Tendermint Websocket. It sends an error to the +// subscription error channel if unsubscription fails. +func (s *Subscription) Unsubscribe(es *EventSystem) { + go func() { + uninstallLoop: + for { + // write uninstall request and consume logs/hashes. This prevents + // the eventLoop broadcast method to deadlock when writing to the + // filter event channel while the subscription loop is waiting for + // this method to return (and thus not reading these events). + select { + case es.uninstall <- s: + break uninstallLoop + case <-s.logs: + case <-s.hashes: + case <-s.headers: + } + } + }() +} + +// Err returns the error channel +func (s *Subscription) Err() <-chan error { + return s.err +} + +// Event returns the tendermint result event channel +func (s *Subscription) Event() <-chan coretypes.ResultEvent { + return s.eventCh +} diff --git a/ethereum/rpc/namespaces/eth/filters/utils.go b/ethereum/rpc/namespaces/eth/filters/utils.go new file mode 100644 index 00000000..b7362607 --- /dev/null +++ b/ethereum/rpc/namespaces/eth/filters/utils.go @@ -0,0 +1,102 @@ +package filters + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" +) + +// FilterLogs creates a slice of logs matching the given criteria. +// [] -> anything +// [A] -> A in first position of log topics, anything after +// [null, B] -> anything in first position, B in second position +// [A, B] -> A in first position and B in second position +// [[A, B], [A, B]] -> A or B in first position, A or B in second position +func FilterLogs(logs []*ethtypes.Log, fromBlock, toBlock *big.Int, addresses []common.Address, topics [][]common.Hash) []*ethtypes.Log { + var ret []*ethtypes.Log +Logs: + for _, log := range logs { + if fromBlock != nil && fromBlock.Int64() >= 0 && fromBlock.Uint64() > log.BlockNumber { + continue + } + if toBlock != nil && toBlock.Int64() >= 0 && toBlock.Uint64() < log.BlockNumber { + continue + } + if len(addresses) > 0 && !includes(addresses, log.Address) { + continue + } + // If the to filtered topics is greater than the amount of topics in logs, skip. + if len(topics) > len(log.Topics) { + continue + } + for i, sub := range topics { + match := len(sub) == 0 // empty rule set == wildcard + for _, topic := range sub { + if log.Topics[i] == topic { + match = true + break + } + } + if !match { + continue Logs + } + } + ret = append(ret, log) + } + return ret +} + +func includes(addresses []common.Address, a common.Address) bool { + for _, addr := range addresses { + if addr == a { + return true + } + } + + return false +} + +func bloomFilter(bloom ethtypes.Bloom, addresses []common.Address, topics [][]common.Hash) bool { + var included bool + if len(addresses) > 0 { + for _, addr := range addresses { + if ethtypes.BloomLookup(bloom, addr) { + included = true + break + } + } + if !included { + return false + } + } + + for _, sub := range topics { + included = len(sub) == 0 // empty rule set == wildcard + for _, topic := range sub { + if ethtypes.BloomLookup(bloom, topic) { + included = true + break + } + } + } + return included +} + +// returnHashes is a helper that will return an empty hash array case the given hash array is nil, +// otherwise the given hashes array is returned. +func returnHashes(hashes []common.Hash) []common.Hash { + if hashes == nil { + return []common.Hash{} + } + return hashes +} + +// returnLogs is a helper that will return an empty log array in case the given logs array is nil, +// otherwise the given logs array is returned. +func returnLogs(logs []*ethtypes.Log) []*ethtypes.Log { + if logs == nil { + return []*ethtypes.Log{} + } + return logs +} diff --git a/ethereum/rpc/net_api.go b/ethereum/rpc/namespaces/net/api.go similarity index 57% rename from ethereum/rpc/net_api.go rename to ethereum/rpc/namespaces/net/api.go index 9a33d867..741c18df 100644 --- a/ethereum/rpc/net_api.go +++ b/ethereum/rpc/namespaces/net/api.go @@ -1,4 +1,4 @@ -package rpc +package net import ( "fmt" @@ -8,25 +8,25 @@ import ( "github.com/cosmos/cosmos-sdk/client" ) -// PublicNetAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec. -type PublicNetAPI struct { +// PublicAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec. +type PublicAPI struct { networkVersion uint64 } -// NewPublicNetAPI creates an instance of the public Net Web3 API. -func NewPublicNetAPI(clientCtx client.Context) *PublicNetAPI { +// NewPublicAPI creates an instance of the public Net Web3 API. +func NewPublicAPI(clientCtx client.Context) *PublicAPI { // parse the chainID from a integer string chainIDEpoch, err := ethermint.ParseChainID(clientCtx.ChainID) if err != nil { panic(err) } - return &PublicNetAPI{ + return &PublicAPI{ networkVersion: chainIDEpoch.Uint64(), } } // Version returns the current ethereum protocol version. -func (s *PublicNetAPI) Version() string { +func (s *PublicAPI) Version() string { return fmt.Sprintf("%d", s.networkVersion) } diff --git a/ethereum/rpc/personal.go b/ethereum/rpc/namespaces/personal/api.go similarity index 97% rename from ethereum/rpc/personal.go rename to ethereum/rpc/namespaces/personal/api.go index e6330ef6..b149ee5c 100644 --- a/ethereum/rpc/personal.go +++ b/ethereum/rpc/namespaces/personal/api.go @@ -1,4 +1,4 @@ -package rpc +package personal import ( "context" @@ -21,17 +21,18 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/tharsis/ethermint/crypto/ethsecp256k1" + "github.com/tharsis/ethermint/ethereum/rpc/namespaces/eth" rpctypes "github.com/tharsis/ethermint/ethereum/rpc/types" ) // PrivateAccountAPI is the personal_ prefixed set of APIs in the Web3 JSON-RPC spec. type PrivateAccountAPI struct { - ethAPI *PublicEthAPI + ethAPI *eth.PublicAPI logger log.Logger } -// NewPersonalAPI creates an instance of the public Personal Eth API. -func NewPersonalAPI(ethAPI *PublicEthAPI) *PrivateAccountAPI { +// NewAPI creates an instance of the public Personal Eth API. +func NewAPI(ethAPI *eth.PublicAPI) *PrivateAccountAPI { return &PrivateAccountAPI{ ethAPI: ethAPI, logger: log.WithField("module", "personal"), diff --git a/ethereum/rpc/txpool_api.go b/ethereum/rpc/namespaces/txpool/api.go similarity index 66% rename from ethereum/rpc/txpool_api.go rename to ethereum/rpc/namespaces/txpool/api.go index bcecffe7..29894213 100644 --- a/ethereum/rpc/txpool_api.go +++ b/ethereum/rpc/namespaces/txpool/api.go @@ -1,4 +1,4 @@ -package rpc +package txpool import ( "github.com/ethereum/go-ethereum/common/hexutil" @@ -6,21 +6,21 @@ import ( log "github.com/xlab/suplog" ) -// PublicTxPoolAPI offers and API for the transaction pool. It only operates on data that is non-confidential. +// PublicAPI offers and API for the transaction pool. It only operates on data that is non-confidential. // NOTE: For more info about the current status of this endpoints see https://github.com/tharsis/ethermint/issues/124 -type PublicTxPoolAPI struct { +type PublicAPI struct { logger log.Logger } -// NewPublicTxPoolAPI creates a new tx pool service that gives information about the transaction pool. -func NewPublicTxPoolAPI() *PublicTxPoolAPI { - return &PublicTxPoolAPI{ +// NewPublicAPI creates a new tx pool service that gives information about the transaction pool. +func NewPublicAPI() *PublicAPI { + return &PublicAPI{ logger: log.WithField("module", "txpool"), } } // Content returns the transactions contained within the transaction pool -func (api *PublicTxPoolAPI) Content() (map[string]map[string]map[string]*types.RPCTransaction, error) { +func (api *PublicAPI) Content() (map[string]map[string]map[string]*types.RPCTransaction, error) { api.logger.Debug("txpool_content") content := map[string]map[string]map[string]*types.RPCTransaction{ "pending": make(map[string]map[string]*types.RPCTransaction), @@ -30,7 +30,7 @@ func (api *PublicTxPoolAPI) Content() (map[string]map[string]map[string]*types.R } // Inspect returns the content of the transaction pool and flattens it into an -func (api *PublicTxPoolAPI) Inspect() (map[string]map[string]map[string]string, error) { +func (api *PublicAPI) Inspect() (map[string]map[string]map[string]string, error) { api.logger.Debug("txpool_inspect") content := map[string]map[string]map[string]string{ "pending": make(map[string]map[string]string), @@ -40,7 +40,7 @@ func (api *PublicTxPoolAPI) Inspect() (map[string]map[string]map[string]string, } // Status returns the number of pending and queued transaction in the pool. -func (api *PublicTxPoolAPI) Status() map[string]hexutil.Uint { +func (api *PublicAPI) Status() map[string]hexutil.Uint { api.logger.Debug("txpool_status") return map[string]hexutil.Uint{ "pending": hexutil.Uint(0), diff --git a/ethereum/rpc/namespaces/web3/api.go b/ethereum/rpc/namespaces/web3/api.go new file mode 100644 index 00000000..54af350d --- /dev/null +++ b/ethereum/rpc/namespaces/web3/api.go @@ -0,0 +1,26 @@ +package web3 + +import ( + "github.com/tharsis/ethermint/version" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" +) + +// PublicAPI is the web3_ prefixed set of APIs in the Web3 JSON-RPC spec. +type PublicAPI struct{} + +// NewPublicAPI creates an instance of the Web3 API. +func NewPublicAPI() *PublicAPI { + return &PublicAPI{} +} + +// ClientVersion returns the client version in the Web3 user agent format. +func (a *PublicAPI) ClientVersion() string { + return version.Version() +} + +// Sha3 returns the keccak-256 hash of the passed-in input. +func (a *PublicAPI) Sha3(input hexutil.Bytes) hexutil.Bytes { + return crypto.Keccak256(input) +} diff --git a/ethereum/rpc/web3_api.go b/ethereum/rpc/web3_api.go deleted file mode 100644 index f54c46c5..00000000 --- a/ethereum/rpc/web3_api.go +++ /dev/null @@ -1,26 +0,0 @@ -package rpc - -import ( - "github.com/tharsis/ethermint/version" - - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/crypto" -) - -// PublicWeb3API is the web3_ prefixed set of APIs in the Web3 JSON-RPC spec. -type PublicWeb3API struct{} - -// NewPublicWeb3API creates an instance of the Web3 API. -func NewPublicWeb3API() *PublicWeb3API { - return &PublicWeb3API{} -} - -// ClientVersion returns the client version in the Web3 user agent format. -func (a *PublicWeb3API) ClientVersion() string { - return version.Version() -} - -// Sha3 returns the keccak-256 hash of the passed-in input. -func (a *PublicWeb3API) Sha3(input hexutil.Bytes) hexutil.Bytes { - return crypto.Keccak256(input) -} diff --git a/ethereum/rpc/websockets.go b/ethereum/rpc/websockets.go index f5357a3c..d7fb5ae0 100644 --- a/ethereum/rpc/websockets.go +++ b/ethereum/rpc/websockets.go @@ -9,6 +9,10 @@ import ( "net/http" "sync" + "github.com/ethereum/go-ethereum/eth/filters" + + rpcfilters "github.com/tharsis/ethermint/ethereum/rpc/namespaces/eth/filters" + "github.com/gorilla/mux" "github.com/gorilla/websocket" "github.com/pkg/errors" @@ -19,7 +23,6 @@ import ( tmtypes "github.com/tendermint/tendermint/types" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/rpc" "github.com/tharsis/ethermint/ethereum/rpc/types" @@ -262,7 +265,7 @@ func (s *websocketsServer) tcpGetAndSendResponse(wsConn *wsConn, mb []byte) erro } type wsSubscription struct { - sub *Subscription + sub *rpcfilters.Subscription unsubscribed chan struct{} // closed when unsubscribing wsConn *wsConn query string @@ -270,7 +273,7 @@ type wsSubscription struct { // pubSubAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec type pubSubAPI struct { - events *EventSystem + events *rpcfilters.EventSystem filtersMu *sync.RWMutex filters map[rpc.ID]*wsSubscription logger log.Logger @@ -279,7 +282,7 @@ type pubSubAPI struct { // newPubSubAPI creates an instance of the ethereum PubSub API. func newPubSubAPI(tmWSClient *rpcclient.WSClient) *pubSubAPI { return &pubSubAPI{ - events: NewEventSystem(tmWSClient), + events: rpcfilters.NewEventSystem(tmWSClient), filtersMu: new(sync.RWMutex), filters: make(map[rpc.ID]*wsSubscription), logger: log.WithField("module", "websocket-client"), @@ -414,7 +417,7 @@ func (api *pubSubAPI) subscribeNewHeads(wsConn *wsConn) (rpc.ID, error) { return } } - }(sub.eventCh, sub.Err()) + }(sub.Event(), sub.Err()) return subID, nil } @@ -604,7 +607,7 @@ func (api *pubSubAPI) subscribeLogs(wsConn *wsConn, extra interface{}) (rpc.ID, return } - logs := filterLogs(evmtypes.LogsToEthereum(txResponse.Logs), crit.FromBlock, crit.ToBlock, crit.Addresses, crit.Topics) + logs := rpcfilters.FilterLogs(evmtypes.LogsToEthereum(txResponse.Logs), crit.FromBlock, crit.ToBlock, crit.Addresses, crit.Topics) if len(logs) == 0 { continue } @@ -653,7 +656,7 @@ func (api *pubSubAPI) subscribeLogs(wsConn *wsConn, extra interface{}) (rpc.ID, return } } - }(sub.eventCh, sub.Err(), subID) + }(sub.Event(), sub.Err(), subID) return subID, nil } @@ -730,7 +733,7 @@ func (api *pubSubAPI) subscribePendingTransactions(wsConn *wsConn) (rpc.ID, erro return } } - }(sub.eventCh, sub.Err()) + }(sub.Event(), sub.Err()) return subID, nil }