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
This commit is contained in:
Dustin Brickwood 2019-10-07 22:32:28 -05:00 committed by GitHub
parent eab81bc578
commit a61f3b892d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 184 additions and 1 deletions

2
go.mod
View File

@ -50,9 +50,9 @@ require (
github.com/tyler-smith/go-bip39 v1.0.0 // indirect github.com/tyler-smith/go-bip39 v1.0.0 // indirect
github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 // indirect github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 // indirect
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 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/sys v0.0.0-20190626221950-04f50cda93cb // indirect
golang.org/x/text v0.3.2 // 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 google.golang.org/genproto v0.0.0-20190708153700-3bdd9d9f5532 // indirect
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
gopkg.in/yaml.v2 v2.2.2 gopkg.in/yaml.v2 v2.2.2

View File

@ -30,5 +30,11 @@ func GetRPCAPIs(cliCtx context.CLIContext, key emintcrypto.PrivKeySecp256k1) []r
Service: NewPersonalEthAPI(cliCtx, nonceLock), Service: NewPersonalEthAPI(cliCtx, nonceLock),
Public: false, Public: false,
}, },
{
Namespace: "eth",
Version: "1.0",
Service: NewPublicFilterAPI(cliCtx),
Public: true,
},
} }
} }

80
rpc/filter_api.go Normal file
View File

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

84
rpc/filters.go Normal file
View File

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

View File

@ -24,6 +24,7 @@ const (
QueryHashToHeight = "hashToHeight" QueryHashToHeight = "hashToHeight"
QueryTxLogs = "txLogs" QueryTxLogs = "txLogs"
QueryLogsBloom = "logsBloom" QueryLogsBloom = "logsBloom"
QueryLogs = "logs"
) )
// NewQuerier is the module level router for state queries // NewQuerier is the module level router for state queries
@ -48,6 +49,8 @@ func NewQuerier(keeper Keeper) sdk.Querier {
return queryTxLogs(ctx, path, keeper) return queryTxLogs(ctx, path, keeper)
case QueryLogsBloom: case QueryLogsBloom:
return queryBlockLogsBloom(ctx, path, keeper) return queryBlockLogsBloom(ctx, path, keeper)
case QueryLogs:
return queryLogs(ctx, path, keeper)
default: default:
return nil, sdk.ErrUnknownRequest("unknown query endpoint") 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 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
}