From a61f3b892d168e9ebbb40cb0912e34a8d41b486e Mon Sep 17 00:00:00 2001 From: Dustin Brickwood Date: Mon, 7 Oct 2019 22:32:28 -0500 Subject: [PATCH] Filtering for eth_getLogs (#120) * Filtered logs based param criteria * addded PublicFilterAPI * added filter logs func to filter based on params * added Unmarshal func from go-ethereum * Linted * made requested changes --- go.mod | 2 +- rpc/apis.go | 6 ++++ rpc/filter_api.go | 80 ++++++++++++++++++++++++++++++++++++++++++++ rpc/filters.go | 84 +++++++++++++++++++++++++++++++++++++++++++++++ x/evm/querier.go | 13 ++++++++ 5 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 rpc/filter_api.go create mode 100644 rpc/filters.go diff --git a/go.mod b/go.mod index 15393ca7..bfb4587e 100644 --- a/go.mod +++ b/go.mod @@ -50,9 +50,9 @@ require ( github.com/tyler-smith/go-bip39 v1.0.0 // indirect github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 // indirect golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 + 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/apis.go b/rpc/apis.go index c3aa2281..3c9ed42e 100644 --- a/rpc/apis.go +++ b/rpc/apis.go @@ -30,5 +30,11 @@ func GetRPCAPIs(cliCtx context.CLIContext, key emintcrypto.PrivKeySecp256k1) []r Service: NewPersonalEthAPI(cliCtx, nonceLock), Public: false, }, + { + Namespace: "eth", + Version: "1.0", + Service: NewPublicFilterAPI(cliCtx), + Public: true, + }, } } diff --git a/rpc/filter_api.go b/rpc/filter_api.go new file mode 100644 index 00000000..0025a962 --- /dev/null +++ b/rpc/filter_api.go @@ -0,0 +1,80 @@ +package rpc + +import ( + "encoding/json" + "fmt" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/ethermint/x/evm/types" + + "math/big" + + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/eth/filters" + "github.com/ethereum/go-ethereum/rpc" +) + +// PublicFilterAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec. +type PublicFilterAPI struct { + cliCtx context.CLIContext +} + +// NewPublicEthAPI creates an instance of the public ETH Web3 API. +func NewPublicFilterAPI(cliCtx context.CLIContext) *PublicFilterAPI { + return &PublicFilterAPI{ + cliCtx: cliCtx, + } +} + +// GetLogs returns logs matching the given argument that are stored within the state. +// +// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getlogs +func (e *PublicFilterAPI) GetLogs(criteria filters.FilterCriteria) ([]*ethtypes.Log, error) { + var filter *Filter + if criteria.BlockHash != nil { + /* + Still need to add blockhash in prepare function for log entry + */ + filter = NewBlockFilter(*criteria.BlockHash, criteria.Addresses, criteria.Topics) + results := e.getLogs() + logs := filterLogs(results, nil, nil, filter.addresses, filter.topics) + return logs, nil + } else { + // Convert the RPC block numbers into internal representations + begin := rpc.LatestBlockNumber.Int64() + if criteria.FromBlock != nil { + begin = criteria.FromBlock.Int64() + } + from := big.NewInt(begin) + end := rpc.LatestBlockNumber.Int64() + if criteria.ToBlock != nil { + end = criteria.ToBlock.Int64() + } + to := big.NewInt(end) + results := e.getLogs() + logs := filterLogs(results, from, to, criteria.Addresses, criteria.Topics) + + return returnLogs(logs), nil + } +} + +func (e *PublicFilterAPI) getLogs() (results []*ethtypes.Log) { + l, _, err := e.cliCtx.QueryWithData(fmt.Sprintf("custom/%s/logs", types.ModuleName), nil) + if err != nil { + fmt.Printf("error from querier %e ", err) + } + + if err := json.Unmarshal(l, &results); err != nil { + panic(err) + } + return results +} + +// 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 +} \ No newline at end of file diff --git a/rpc/filters.go b/rpc/filters.go new file mode 100644 index 00000000..01711f3e --- /dev/null +++ b/rpc/filters.go @@ -0,0 +1,84 @@ +package rpc + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + ethtypes "github.com/ethereum/go-ethereum/core/types" +) + +/* + - Filter functions derived from go-ethereum + Used to set the criteria passed in from RPC params +*/ + +// Filter can be used to retrieve and filter logs. +type Filter struct { + addresses []common.Address + topics [][]common.Hash + + block common.Hash // Block hash if filtering a single block +} + +// NewBlockFilter creates a new filter which directly inspects the contents of +// a block to figure out whether it is interesting or not. +func NewBlockFilter(block common.Hash, addresses []common.Address, topics [][]common.Hash) *Filter { + // Create a generic filter and convert it into a block filter + filter := newFilter(addresses, topics) + filter.block = block + return filter +} + +// newFilter creates a generic filter that can either filter based on a block hash, +// or based on range queries. The search criteria needs to be explicitly set. +func newFilter(addresses []common.Address, topics [][]common.Hash) *Filter { + return &Filter{ + addresses: addresses, + topics: topics, + } +} + +func includes(addresses []common.Address, a common.Address) bool { + for _, addr := range addresses { + if addr == a { + return true + } + } + + return false +} + +// filterLogs creates a slice of logs matching the given criteria. +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 Logs + } + 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 +} diff --git a/x/evm/querier.go b/x/evm/querier.go index e10aa9ab..9f077e9e 100644 --- a/x/evm/querier.go +++ b/x/evm/querier.go @@ -24,6 +24,7 @@ const ( QueryHashToHeight = "hashToHeight" QueryTxLogs = "txLogs" QueryLogsBloom = "logsBloom" + QueryLogs = "logs" ) // NewQuerier is the module level router for state queries @@ -48,6 +49,8 @@ func NewQuerier(keeper Keeper) sdk.Querier { return queryTxLogs(ctx, path, keeper) case QueryLogsBloom: return queryBlockLogsBloom(ctx, path, keeper) + case QueryLogs: + return queryLogs(ctx, path, keeper) default: return nil, sdk.ErrUnknownRequest("unknown query endpoint") } @@ -167,3 +170,13 @@ 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) { + logs := keeper.Logs(ctx) + + l, err := codec.MarshalJSONIndent(keeper.cdc, logs) + if err != nil { + panic("could not marshal result to JSON: " + err.Error()) + } + return l, nil +}