Skip to content
This repository has been archived by the owner on Nov 30, 2021. It is now read-only.

Filtering for eth_getLogs #120

Merged
merged 3 commits into from
Oct 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions rpc/apis.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
}
}
80 changes: 80 additions & 0 deletions rpc/filter_api.go
Original file line number Diff line number Diff line change
@@ -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()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it not be beneficial to include the range filter on the type you created to abstract this logic so you aren't doing it duplicate times?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you elaborate on what you mean from the above?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just replicating the e.getLogs and filterLogs calls, can't the filter just be generated in this if/ else then call these equivalently outside of it for consistency?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still need some clarity here.... e.getLogs is the actual call to retrieve the logs whereas filterLogs does the filtering. We want to keep this functionality separate when we introduce bloom filters so that we only call filterLogs on the logs that may meet the filter criteria which will be indicated by the bloom filter.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to be more specific, I just mean having the functionality to create a range filter like you do with NewBlockFilter() then get logs and filter them below this conditional (let me know what you think of this

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only problem I have is that you are handling block filters and range filters asymmetrically, which is somewhat hard to follow. Also small thing with this is that it adds a linting issue with returning both in the coniditonal (that doesn't matter as much)

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
}
84 changes: 84 additions & 0 deletions rpc/filters.go
Original file line number Diff line number Diff line change
@@ -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 {
dutterbutter marked this conversation as resolved.
Show resolved Hide resolved
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
}
13 changes: 13 additions & 0 deletions x/evm/querier.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const (
QueryHashToHeight = "hashToHeight"
QueryTxLogs = "txLogs"
QueryLogsBloom = "logsBloom"
QueryLogs = "logs"
)

// NewQuerier is the module level router for state queries
Expand All @@ -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")
}
Expand Down Expand Up @@ -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
}