filters: begin implementation (#230)

* adds Filter type and related methods
* updates PublicFilterAPI to include backend, filter mapping
* stub out filter related eth_ functions
This commit is contained in:
noot 2020-04-01 20:43:59 -04:00 committed by GitHub
parent da9157e406
commit 35e7a98ab2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 368 additions and 144 deletions

View File

@ -8,36 +8,42 @@ import (
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
) )
const Web3Namespace = "web3"
const EthNamespace = "eth"
const PersonalNamespace = "personal"
const NetNamespace = "net"
// GetRPCAPIs returns the list of all APIs // GetRPCAPIs returns the list of all APIs
func GetRPCAPIs(cliCtx context.CLIContext, key emintcrypto.PrivKeySecp256k1) []rpc.API { func GetRPCAPIs(cliCtx context.CLIContext, key emintcrypto.PrivKeySecp256k1) []rpc.API {
nonceLock := new(AddrLocker) nonceLock := new(AddrLocker)
backend := NewEthermintBackend(cliCtx)
return []rpc.API{ return []rpc.API{
{ {
Namespace: "web3", Namespace: Web3Namespace,
Version: "1.0", Version: "1.0",
Service: NewPublicWeb3API(), Service: NewPublicWeb3API(),
Public: true, Public: true,
}, },
{ {
Namespace: "eth", Namespace: EthNamespace,
Version: "1.0", Version: "1.0",
Service: NewPublicEthAPI(cliCtx, nonceLock, key), Service: NewPublicEthAPI(cliCtx, backend, nonceLock, key),
Public: true, Public: true,
}, },
{ {
Namespace: "personal", Namespace: PersonalNamespace,
Version: "1.0", Version: "1.0",
Service: NewPersonalEthAPI(cliCtx, nonceLock), Service: NewPersonalEthAPI(cliCtx, nonceLock),
Public: false, Public: false,
}, },
{ {
Namespace: "eth", Namespace: EthNamespace,
Version: "1.0", Version: "1.0",
Service: NewPublicFilterAPI(cliCtx), Service: NewPublicFilterAPI(cliCtx, backend),
Public: true, Public: true,
}, },
{ {
Namespace: "net", Namespace: NetNamespace,
Version: "1.0", Version: "1.0",
Service: NewPublicNetAPI(cliCtx), Service: NewPublicNetAPI(cliCtx),
Public: true, Public: true,

180
rpc/backend.go Normal file
View File

@ -0,0 +1,180 @@
package rpc
import (
"fmt"
"math/big"
"strconv"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/ethermint/x/evm"
"github.com/cosmos/ethermint/x/evm/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
ethtypes "github.com/ethereum/go-ethereum/core/types"
)
// Backend implements the functionality needed to filter changes.
// Implemented by EthermintBackend.
type Backend interface {
// Used by block filter; also used for polling
BlockNumber() (hexutil.Uint64, error)
GetBlockByNumber(blockNum BlockNumber, fullTx bool) (map[string]interface{}, error)
getEthBlockByNumber(height int64, fullTx bool) (map[string]interface{}, error)
getGasLimit() (int64, error)
// Used by pending transaction filter
PendingTransactions() ([]*Transaction, error)
// Used by log filter
GetTxLogs(txHash common.Hash) ([]*ethtypes.Log, error)
// TODO: Bloom methods
}
// EmintBackend implements Backend
type EthermintBackend struct {
cliCtx context.CLIContext
gasLimit int64
}
func NewEthermintBackend(cliCtx context.CLIContext) *EthermintBackend {
return &EthermintBackend{
cliCtx: cliCtx,
gasLimit: int64(^uint32(0)),
}
}
// BlockNumber returns the current block number.
func (e *EthermintBackend) BlockNumber() (hexutil.Uint64, error) {
res, _, err := e.cliCtx.QueryWithData(fmt.Sprintf("custom/%s/blockNumber", types.ModuleName), nil)
if err != nil {
return hexutil.Uint64(0), err
}
var out types.QueryResBlockNumber
e.cliCtx.Codec.MustUnmarshalJSON(res, &out)
return hexutil.Uint64(out.Number), nil
}
// GetBlockByNumber returns the block identified by number.
func (e *EthermintBackend) GetBlockByNumber(blockNum BlockNumber, fullTx bool) (map[string]interface{}, error) {
value := blockNum.Int64()
return e.getEthBlockByNumber(value, fullTx)
}
func (e *EthermintBackend) getEthBlockByNumber(height int64, fullTx bool) (map[string]interface{}, error) {
// Remove this check when 0 query is fixed ref: (https://github.com/tendermint/tendermint/issues/4014)
var blkNumPtr *int64
if height != 0 {
blkNumPtr = &height
}
block, err := e.cliCtx.Client.Block(blkNumPtr)
if err != nil {
return nil, err
}
header := block.Block.Header
gasLimit, err := e.getGasLimit()
if err != nil {
return nil, err
}
var (
gasUsed *big.Int
transactions []interface{}
)
if fullTx {
// Populate full transaction data
transactions, gasUsed, err = convertTransactionsToRPC(
e.cliCtx, block.Block.Txs, common.BytesToHash(header.Hash()), uint64(header.Height),
)
if err != nil {
return nil, err
}
} else {
// TODO: Gas used not saved and cannot be calculated by hashes
// Return slice of transaction hashes
transactions = make([]interface{}, len(block.Block.Txs))
for i, tx := range block.Block.Txs {
transactions[i] = common.BytesToHash(tx.Hash())
}
}
res, _, err := e.cliCtx.Query(fmt.Sprintf("custom/%s/%s/%s", types.ModuleName, evm.QueryLogsBloom, strconv.FormatInt(block.Block.Height, 10)))
if err != nil {
return nil, err
}
var out types.QueryBloomFilter
e.cliCtx.Codec.MustUnmarshalJSON(res, &out)
return formatBlock(header, block.Block.Size(), gasLimit, gasUsed, transactions, out.Bloom), nil
}
// getGasLimit returns the gas limit per block set in genesis
func (e *EthermintBackend) getGasLimit() (int64, error) {
// Retrieve from gasLimit variable cache
if e.gasLimit != -1 {
return e.gasLimit, nil
}
// Query genesis block if hasn't been retrieved yet
genesis, err := e.cliCtx.Client.Genesis()
if err != nil {
return 0, err
}
// Save value to gasLimit cached value
gasLimit := genesis.Genesis.ConsensusParams.Block.MaxGas
if gasLimit == -1 {
// Sets gas limit to max uint32 to not error with javascript dev tooling
// This -1 value indicating no block gas limit is set to max uint64 with geth hexutils
// which errors certain javascript dev tooling which only supports up to 53 bits
gasLimit = int64(^uint32(0))
}
e.gasLimit = gasLimit
return gasLimit, nil
}
// GetTxLogs returns the logs given a transaction hash.
func (e *EthermintBackend) GetTxLogs(txHash common.Hash) ([]*ethtypes.Log, error) {
// do we need to use the block height somewhere?
ctx := e.cliCtx
res, _, err := ctx.QueryWithData(fmt.Sprintf("custom/%s/%s/%s", types.ModuleName, evm.QueryTxLogs, txHash.Hex()), nil)
if err != nil {
return nil, err
}
var out types.QueryETHLogs
e.cliCtx.Codec.MustUnmarshalJSON(res, &out)
return out.Logs, nil
}
// 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 *EthermintBackend) PendingTransactions() ([]*Transaction, error) {
pendingTxs, err := e.cliCtx.Client.UnconfirmedTxs(100)
if err != nil {
return nil, err
}
transactions := make([]*Transaction, 0, 100)
for _, tx := range pendingTxs.Txs {
ethTx, err := bytesToEthTx(e.cliCtx, tx)
if err != nil {
return nil, err
}
// * Should check signer and reference against accounts the node manages in future
rpcTx, err := newRPCTransaction(*ethTx, common.Hash{}, nil, 0)
if err != nil {
return nil, err
}
transactions = append(transactions, rpcTx)
}
return transactions, nil
}

View File

@ -6,7 +6,6 @@ import (
"fmt" "fmt"
"log" "log"
"math/big" "math/big"
"strconv"
"sync" "sync"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -40,18 +39,19 @@ import (
// PublicEthAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec. // PublicEthAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec.
type PublicEthAPI struct { type PublicEthAPI struct {
cliCtx context.CLIContext cliCtx context.CLIContext
backend Backend
key emintcrypto.PrivKeySecp256k1 key emintcrypto.PrivKeySecp256k1
nonceLock *AddrLocker nonceLock *AddrLocker
keybaseLock sync.Mutex keybaseLock sync.Mutex
gasLimit *int64
} }
// NewPublicEthAPI creates an instance of the public ETH Web3 API. // NewPublicEthAPI creates an instance of the public ETH Web3 API.
func NewPublicEthAPI(cliCtx context.CLIContext, nonceLock *AddrLocker, func NewPublicEthAPI(cliCtx context.CLIContext, backend Backend, nonceLock *AddrLocker,
key emintcrypto.PrivKeySecp256k1) *PublicEthAPI { key emintcrypto.PrivKeySecp256k1) *PublicEthAPI {
return &PublicEthAPI{ return &PublicEthAPI{
cliCtx: cliCtx, cliCtx: cliCtx,
backend: backend,
key: key, key: key,
nonceLock: nonceLock, nonceLock: nonceLock,
} }
@ -132,14 +132,7 @@ func (e *PublicEthAPI) Accounts() ([]common.Address, error) {
// BlockNumber returns the current block number. // BlockNumber returns the current block number.
func (e *PublicEthAPI) BlockNumber() (hexutil.Uint64, error) { func (e *PublicEthAPI) BlockNumber() (hexutil.Uint64, error) {
res, _, err := e.cliCtx.QueryWithData(fmt.Sprintf("custom/%s/blockNumber", types.ModuleName), nil) return e.backend.BlockNumber()
if err != nil {
return hexutil.Uint64(0), err
}
var out types.QueryResBlockNumber
e.cliCtx.Codec.MustUnmarshalJSON(res, &out)
return hexutil.Uint64(out.Number), nil
} }
// GetBalance returns the provided account's balance up to the provided block number. // GetBalance returns the provided account's balance up to the provided block number.
@ -229,7 +222,7 @@ func (e *PublicEthAPI) GetUncleCountByBlockNumber(blockNum BlockNumber) hexutil.
// GetCode returns the contract code at the given address and block number. // GetCode returns the contract code at the given address and block number.
func (e *PublicEthAPI) GetCode(address common.Address, blockNumber BlockNumber) (hexutil.Bytes, error) { func (e *PublicEthAPI) GetCode(address common.Address, blockNumber BlockNumber) (hexutil.Bytes, error) {
ctx := e.cliCtx.WithHeight(blockNumber.Int64()) ctx := e.cliCtx.WithHeight(blockNumber.Int64())
res, _, err := ctx.QueryWithData(fmt.Sprintf("custom/%s/code/%s", types.ModuleName, address.Hex()), nil) res, _, err := ctx.QueryWithData(fmt.Sprintf("custom/%s/%s/%s", types.ModuleName, evm.QueryCode, address.Hex()), nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -239,6 +232,11 @@ func (e *PublicEthAPI) GetCode(address common.Address, blockNumber BlockNumber)
return out.Code, nil return out.Code, nil
} }
// GetTxLogs returns the logs given a transaction hash.
func (e *PublicEthAPI) GetTxLogs(txHash common.Hash) ([]*ethtypes.Log, error) {
return e.backend.GetTxLogs(txHash)
}
// Sign signs the provided data using the private key of address via Geth's signature standard. // 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 *PublicEthAPI) Sign(address common.Address, data hexutil.Bytes) (hexutil.Bytes, error) {
// TODO: Change this functionality to find an unlocked account by address // TODO: Change this functionality to find an unlocked account by address
@ -480,64 +478,12 @@ func (e *PublicEthAPI) GetBlockByHash(hash common.Hash, fullTx bool) (map[string
var out types.QueryResBlockNumber var out types.QueryResBlockNumber
e.cliCtx.Codec.MustUnmarshalJSON(res, &out) e.cliCtx.Codec.MustUnmarshalJSON(res, &out)
return e.getEthBlockByNumber(out.Number, fullTx) return e.backend.getEthBlockByNumber(out.Number, fullTx)
} }
// GetBlockByNumber returns the block identified by number. // GetBlockByNumber returns the block identified by number.
func (e *PublicEthAPI) GetBlockByNumber(blockNum BlockNumber, fullTx bool) (map[string]interface{}, error) { func (e *PublicEthAPI) GetBlockByNumber(blockNum BlockNumber, fullTx bool) (map[string]interface{}, error) {
value := blockNum.Int64() return e.backend.GetBlockByNumber(blockNum, fullTx)
return e.getEthBlockByNumber(value, fullTx)
}
func (e *PublicEthAPI) getEthBlockByNumber(height int64, fullTx bool) (map[string]interface{}, error) {
// Remove this check when 0 query is fixed ref: (https://github.com/tendermint/tendermint/issues/4014)
var blkNumPtr *int64
if height != 0 {
blkNumPtr = &height
}
block, err := e.cliCtx.Client.Block(blkNumPtr)
if err != nil {
return nil, err
}
header := block.Block.Header
gasLimit, err := e.getGasLimit()
if err != nil {
return nil, err
}
var (
gasUsed *big.Int
transactions []interface{}
)
if fullTx {
// Populate full transaction data
transactions, gasUsed, err = convertTransactionsToRPC(
e.cliCtx, block.Block.Txs, common.BytesToHash(header.Hash()), uint64(header.Height),
)
if err != nil {
return nil, err
}
} else {
// TODO: Gas used not saved and cannot be calculated by hashes
// Return slice of transaction hashes
transactions = make([]interface{}, len(block.Block.Txs))
for i, tx := range block.Block.Txs {
transactions[i] = common.BytesToHash(tx.Hash())
}
}
res, _, err := e.cliCtx.Query(fmt.Sprintf("custom/%s/%s/%s", types.ModuleName, evm.QueryLogsBloom, strconv.FormatInt(block.Block.Height, 10)))
if err != nil {
return nil, err
}
var out types.QueryBloomFilter
e.cliCtx.Codec.MustUnmarshalJSON(res, &out)
return formatBlock(header, block.Block.Size(), gasLimit, gasUsed, transactions, out.Bloom), nil
} }
func formatBlock( func formatBlock(
@ -782,28 +728,7 @@ func (e *PublicEthAPI) GetTransactionReceipt(hash common.Hash) (map[string]inter
// PendingTransactions returns the transactions that are in the transaction pool // PendingTransactions returns the transactions that are in the transaction pool
// and have a from address that is one of the accounts this node manages. // and have a from address that is one of the accounts this node manages.
func (e *PublicEthAPI) PendingTransactions() ([]*Transaction, error) { func (e *PublicEthAPI) PendingTransactions() ([]*Transaction, error) {
pendingTxs, err := e.cliCtx.Client.UnconfirmedTxs(100) return e.backend.PendingTransactions()
if err != nil {
return nil, err
}
transactions := make([]*Transaction, 0, 100)
for _, tx := range pendingTxs.Txs {
ethTx, err := bytesToEthTx(e.cliCtx, tx)
if err != nil {
return nil, err
}
// * Should check signer and reference against accounts the node manages in future
rpcTx, err := newRPCTransaction(*ethTx, common.Hash{}, nil, 0)
if err != nil {
return nil, err
}
transactions = append(transactions, rpcTx)
}
return transactions, nil
} }
// GetUncleByBlockHashAndIndex returns the uncle identified by hash and index. Always returns nil. // GetUncleByBlockHashAndIndex returns the uncle identified by hash and index. Always returns nil.
@ -878,31 +803,6 @@ func (e *PublicEthAPI) GetProof(address common.Address, storageKeys []string, bl
}, nil }, nil
} }
// getGasLimit returns the gas limit per block set in genesis
func (e *PublicEthAPI) getGasLimit() (int64, error) {
// Retrieve from gasLimit variable cache
if e.gasLimit != nil {
return *e.gasLimit, nil
}
// Query genesis block if hasn't been retrieved yet
genesis, err := e.cliCtx.Client.Genesis()
if err != nil {
return 0, err
}
// Save value to gasLimit cached value
gasLimit := genesis.Genesis.ConsensusParams.Block.MaxGas
if gasLimit == -1 {
// Sets gas limit to max uint32 to not error with javascript dev tooling
// This -1 value indicating no block gas limit is set to max uint64 with geth hexutils
// which errors certain javascript dev tooling which only supports up to 53 bits
gasLimit = int64(^uint32(0))
}
e.gasLimit = &gasLimit
return gasLimit, nil
}
// generateFromArgs populates tx message with args (used in RPC API) // generateFromArgs populates tx message with args (used in RPC API)
func (e *PublicEthAPI) generateFromArgs(args params.SendTxArgs) (*types.MsgEthereumTx, error) { func (e *PublicEthAPI) generateFromArgs(args params.SendTxArgs) (*types.MsgEthereumTx, error) {
var ( var (

View File

@ -3,12 +3,11 @@ package rpc
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"math/big"
"github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/ethermint/x/evm/types" "github.com/cosmos/ethermint/x/evm/types"
"math/big"
ethtypes "github.com/ethereum/go-ethereum/core/types" ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/eth/filters"
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
@ -17,15 +16,61 @@ import (
// PublicFilterAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec. // PublicFilterAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec.
type PublicFilterAPI struct { type PublicFilterAPI struct {
cliCtx context.CLIContext cliCtx context.CLIContext
backend Backend
filters map[rpc.ID]*Filter // ID to filter; TODO: change to sync.Map in case of concurrent writes
} }
// NewPublicEthAPI creates an instance of the public ETH Web3 API. // NewPublicEthAPI creates an instance of the public ETH Web3 API.
func NewPublicFilterAPI(cliCtx context.CLIContext) *PublicFilterAPI { func NewPublicFilterAPI(cliCtx context.CLIContext, backend Backend) *PublicFilterAPI {
return &PublicFilterAPI{ return &PublicFilterAPI{
cliCtx: cliCtx, cliCtx: cliCtx,
backend: backend,
filters: make(map[rpc.ID]*Filter),
} }
} }
// NewFilter instantiates a new filter.
func (e *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) rpc.ID {
id := rpc.NewID()
e.filters[id] = NewFilter(e.backend, &criteria)
return id
}
// NewBlockFilter instantiates a new block filter.
func (e *PublicFilterAPI) NewBlockFilter() rpc.ID {
id := rpc.NewID()
e.filters[id] = NewBlockFilter(e.backend)
return id
}
// NewPendingTransactionFilter instantiates a new pending transaction filter.
func (e *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID {
id := rpc.NewID()
e.filters[id] = NewPendingTransactionFilter(e.backend)
return id
}
// UninstallFilter uninstalls a filter with the given ID.
func (e *PublicFilterAPI) UninstallFilter(id rpc.ID) bool {
// TODO
e.filters[id].uninstallFilter()
delete(e.filters, id)
return true
}
// GetFilterChanges returns an array of changes since the last poll.
// If the filter is a log filter, it returns an array of Logs.
// If the filter is a block filter, it returns an array of block hashes.
// If the filter is a pending transaction filter, it returns an array of transaction hashes.
func (e *PublicFilterAPI) GetFilterChanges(id rpc.ID) interface{} {
return e.filters[id].getFilterChanges()
}
// GetFilterLogs returns an array of all logs matching filter with given id.
func (e *PublicFilterAPI) GetFilterLogs(id rpc.ID) []*ethtypes.Log {
return e.filters[id].getFilterLogs()
}
// GetLogs returns logs matching the given argument that are stored within the state. // GetLogs returns logs matching the given argument that are stored within the state.
// //
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getlogs // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getlogs
@ -35,7 +80,7 @@ func (e *PublicFilterAPI) GetLogs(criteria filters.FilterCriteria) ([]*ethtypes.
/* /*
Still need to add blockhash in prepare function for log entry Still need to add blockhash in prepare function for log entry
*/ */
filter = NewBlockFilter(*criteria.BlockHash, criteria.Addresses, criteria.Topics) filter = NewFilterWithBlockHash(e.backend, &criteria)
results := e.getLogs() results := e.getLogs()
logs := filterLogs(results, nil, nil, filter.addresses, filter.topics) logs := filterLogs(results, nil, nil, filter.addresses, filter.topics)
return logs, nil return logs, nil

View File

@ -5,6 +5,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types" ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/filters"
) )
/* /*
@ -14,28 +15,65 @@ import (
// Filter can be used to retrieve and filter logs. // Filter can be used to retrieve and filter logs.
type Filter struct { type Filter struct {
addresses []common.Address backend Backend
topics [][]common.Hash fromBlock, toBlock *big.Int // start and end block numbers
addresses []common.Address // contract addresses to watch
block common.Hash // Block hash if filtering a single block topics [][]common.Hash // log topics to watch for
blockHash *common.Hash // Block hash if filtering a single block
} }
// NewBlockFilter creates a new filter which directly inspects the contents of // NewFilter returns a new Filter
// a block to figure out whether it is interesting or not. func NewFilter(backend Backend, criteria *filters.FilterCriteria) *Filter {
func NewBlockFilter(block common.Hash, addresses []common.Address, topics [][]common.Hash) *Filter { return &Filter{
// Create a generic filter and convert it into a block filter backend: backend,
filter := newFilter(addresses, topics) fromBlock: criteria.FromBlock,
filter.block = block toBlock: criteria.ToBlock,
addresses: criteria.Addresses,
topics: criteria.Topics,
}
}
// NewFilterWithBlockHash returns a new Filter with a blockHash.
func NewFilterWithBlockHash(backend Backend, criteria *filters.FilterCriteria) *Filter {
return &Filter{
backend: backend,
fromBlock: criteria.FromBlock,
toBlock: criteria.ToBlock,
addresses: criteria.Addresses,
topics: criteria.Topics,
blockHash: criteria.BlockHash,
}
}
// NewBlockFilter creates a new filter that notifies when a block arrives.
func NewBlockFilter(backend Backend) *Filter {
// TODO: finish
filter := NewFilter(backend, nil)
return filter return filter
} }
// newFilter creates a generic filter that can either filter based on a block hash, // NewPendingTransactionFilter creates a new filter that notifies when a pending transaction arrives.
// or based on range queries. The search criteria needs to be explicitly set. func NewPendingTransactionFilter(backend Backend) *Filter {
func newFilter(addresses []common.Address, topics [][]common.Hash) *Filter { // TODO: finish
return &Filter{ filter := NewFilter(backend, nil)
addresses: addresses, return filter
topics: topics,
} }
func (f *Filter) uninstallFilter() {
// TODO
}
func (f *Filter) getFilterChanges() interface{} {
// TODO
// we might want to use an interface for Filters themselves because of this function, it may return an array of logs
// or an array of hashes, depending of whether Filter is a log filter or a block/transaction filter.
// or, we can add a type field to Filter.
return nil
}
func (f *Filter) getFilterLogs() []*ethtypes.Log {
// TODO
return nil
} }
func includes(addresses []common.Address, a common.Address) bool { func includes(addresses []common.Address, a common.Address) bool {

View File

@ -1,12 +1,14 @@
package evm_test package evm_test
import ( import (
"fmt"
"math/big" "math/big"
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
@ -14,6 +16,7 @@ import (
"github.com/cosmos/ethermint/app" "github.com/cosmos/ethermint/app"
"github.com/cosmos/ethermint/x/evm" "github.com/cosmos/ethermint/x/evm"
"github.com/cosmos/ethermint/x/evm/keeper"
"github.com/cosmos/ethermint/x/evm/types" "github.com/cosmos/ethermint/x/evm/types"
abci "github.com/tendermint/tendermint/abci/types" abci "github.com/tendermint/tendermint/abci/types"
@ -24,7 +27,9 @@ type EvmTestSuite struct {
ctx sdk.Context ctx sdk.Context
handler sdk.Handler handler sdk.Handler
querier sdk.Querier
app *app.EthermintApp app *app.EthermintApp
codec *codec.Codec
} }
func (suite *EvmTestSuite) SetupTest() { func (suite *EvmTestSuite) SetupTest() {
@ -33,6 +38,8 @@ func (suite *EvmTestSuite) SetupTest() {
suite.app = app.Setup(checkTx) suite.app = app.Setup(checkTx)
suite.ctx = suite.app.BaseApp.NewContext(checkTx, abci.Header{Height: 1, ChainID: "3", Time: time.Now().UTC()}) suite.ctx = suite.app.BaseApp.NewContext(checkTx, abci.Header{Height: 1, ChainID: "3", Time: time.Now().UTC()})
suite.handler = evm.NewHandler(suite.app.EvmKeeper) suite.handler = evm.NewHandler(suite.app.EvmKeeper)
suite.querier = keeper.NewQuerier(suite.app.EvmKeeper)
suite.codec = codec.New()
} }
func TestEvmTestSuite(t *testing.T) { func TestEvmTestSuite(t *testing.T) {
@ -87,3 +94,48 @@ func (suite *EvmTestSuite) TestHandler_Logs() {
suite.Require().Equal(logs, resultData.Logs) suite.Require().Equal(logs, resultData.Logs)
} }
func (suite *EvmTestSuite) TestQueryTxLogs() {
gasLimit := uint64(100000)
gasPrice := big.NewInt(1000000)
priv, err := crypto.GenerateKey()
suite.Require().NoError(err, "failed to create key")
// send contract deployment transaction with an event in the constructor
bytecode := common.FromHex("0x6080604052348015600f57600080fd5b5060117f775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd73889860405160405180910390a2603580604b6000396000f3fe6080604052600080fdfea165627a7a723058206cab665f0f557620554bb45adf266708d2bd349b8a4314bdff205ee8440e3c240029")
tx := types.NewMsgEthereumTx(1, nil, big.NewInt(0), gasLimit, gasPrice, bytecode)
tx.Sign(big.NewInt(3), priv)
// result, err := evm.HandleEthTxMsg(suite.ctx, suite.app.EvmKeeper, tx)
// suite.Require().NoError(err, "failed to handle eth tx msg")
result := suite.handler(suite.ctx, tx)
suite.Require().True(result.IsOK())
resultData, err := types.DecodeResultData(result.Data)
suite.Require().NoError(err, "failed to decode result data")
suite.Require().Equal(len(resultData.Logs), 1)
suite.Require().Equal(len(resultData.Logs[0].Topics), 2)
// get logs by tx hash
hash := resultData.TxHash.Bytes()
logs, err := suite.app.EvmKeeper.GetTransactionLogs(suite.ctx, hash)
suite.Require().NoError(err, "failed to get logs")
suite.Require().Equal(logs, resultData.Logs)
// query tx logs
path := []string{"txLogs", fmt.Sprintf("0x%x", hash)}
res, err := suite.querier(suite.ctx, path, abci.RequestQuery{})
suite.Require().NoError(err, "failed to query txLogs")
var txLogs types.QueryETHLogs
suite.codec.MustUnmarshalJSON(res, &txLogs)
// amino decodes an empty byte array as nil, whereas JSON decodes it as []byte{} causing a discrepancy
resultData.Logs[0].Data = []byte{}
suite.Require().Equal(txLogs.Logs[0], resultData.Logs[0])
}

View File

@ -138,6 +138,7 @@ func (st StateTransition) TransitionCSDB(ctx sdk.Context) (*ReturnData, error) {
Bloom: bloomFilter, Bloom: bloomFilter,
Logs: logs, Logs: logs,
Ret: ret, Ret: ret,
TxHash: *st.THash,
} }
resultData, err := EncodeResultData(res) resultData, err := EncodeResultData(res)

View File

@ -7,6 +7,7 @@ import (
"sync" "sync"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
emint "github.com/cosmos/ethermint/types" emint "github.com/cosmos/ethermint/types"
ethcmn "github.com/ethereum/go-ethereum/common" ethcmn "github.com/ethereum/go-ethereum/common"
@ -302,7 +303,7 @@ func (csdb *CommitStateDB) GetLogs(hash ethcmn.Hash) ([]*ethtypes.Log, error) {
logs, err := DecodeLogs(encLogs) logs, err := DecodeLogs(encLogs)
if err != nil { if err != nil {
return []*ethtypes.Log{}, err return nil, err
} }
return logs, nil return logs, nil

View File

@ -53,6 +53,7 @@ type ResultData struct {
Bloom ethtypes.Bloom Bloom ethtypes.Bloom
Logs []*ethtypes.Log Logs []*ethtypes.Log
Ret []byte Ret []byte
TxHash ethcmn.Hash
} }
// EncodeReturnData takes all of the necessary data from the EVM execution // EncodeReturnData takes all of the necessary data from the EVM execution