eth_getFilterLogs, eth_getLogs implementation (#248)

This commit is contained in:
noot 2020-04-13 15:18:50 -04:00 committed by GitHub
parent 9b8dd598bb
commit 199484fc2e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 506 additions and 95 deletions

View File

@ -58,3 +58,9 @@ Ref: https://keepachangelog.com/en/1.0.0/
* Update uninstallFilter and getFilterChanges accordingly * Update uninstallFilter and getFilterChanges accordingly
* uninstallFilter stops the polling goroutine * uninstallFilter stops the polling goroutine
* getFilterChanges returns the filter's internal list of block hashes and resets it * getFilterChanges returns the filter's internal list of block hashes and resets it
* (rpc) [\#54](https://github.com/ChainSafe/ethermint/issues/54) [\#55](https://github.com/ChainSafe/ethermint/issues/55)
Implement eth_getFilterLogs and eth_getLogs
* for a given filter, look through each block for transactions. If there are transactions in the block, get the logs from it, and filter using the filterLogs method
* eth_getLogs and eth_getFilterChanges for log filters use the same underlying method as eth_getFilterLogs
* update HandleMsgEthereumTx to store logs using the ethereum hash

View File

@ -20,6 +20,7 @@ type Backend interface {
// Used by block filter; also used for polling // Used by block filter; also used for polling
BlockNumber() (hexutil.Uint64, error) BlockNumber() (hexutil.Uint64, error)
GetBlockByNumber(blockNum BlockNumber, fullTx bool) (map[string]interface{}, error) GetBlockByNumber(blockNum BlockNumber, fullTx bool) (map[string]interface{}, error)
GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error)
getEthBlockByNumber(height int64, fullTx bool) (map[string]interface{}, error) getEthBlockByNumber(height int64, fullTx bool) (map[string]interface{}, error)
getGasLimit() (int64, error) getGasLimit() (int64, error)
@ -62,6 +63,21 @@ func (e *EthermintBackend) GetBlockByNumber(blockNum BlockNumber, fullTx bool) (
return e.getEthBlockByNumber(value, fullTx) return e.getEthBlockByNumber(value, fullTx)
} }
// GetBlockByHash returns the block identified by hash.
func (e *EthermintBackend) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) {
res, _, err := e.cliCtx.Query(fmt.Sprintf("custom/%s/%s/%s", types.ModuleName, evm.QueryHashToHeight, hash.Hex()))
if err != nil {
return nil, err
}
var out types.QueryResBlockNumber
if err := e.cliCtx.Codec.UnmarshalJSON(res, &out); err != nil {
return nil, err
}
return e.getEthBlockByNumber(out.Number, fullTx)
}
func (e *EthermintBackend) getEthBlockByNumber(height int64, fullTx bool) (map[string]interface{}, error) { 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) // Remove this check when 0 query is fixed ref: (https://github.com/tendermint/tendermint/issues/4014)
var blkNumPtr *int64 var blkNumPtr *int64
@ -82,7 +98,7 @@ func (e *EthermintBackend) getEthBlockByNumber(height int64, fullTx bool) (map[s
var ( var (
gasUsed *big.Int gasUsed *big.Int
transactions []interface{} transactions []common.Hash
) )
if fullTx { if fullTx {
@ -96,7 +112,7 @@ func (e *EthermintBackend) getEthBlockByNumber(height int64, fullTx bool) (map[s
} else { } else {
// TODO: Gas used not saved and cannot be calculated by hashes // TODO: Gas used not saved and cannot be calculated by hashes
// Return slice of transaction hashes // Return slice of transaction hashes
transactions = make([]interface{}, len(block.Block.Txs)) transactions = make([]common.Hash, len(block.Block.Txs))
for i, tx := range block.Block.Txs { for i, tx := range block.Block.Txs {
transactions[i] = common.BytesToHash(tx.Hash()) transactions[i] = common.BytesToHash(tx.Hash())
} }
@ -142,13 +158,17 @@ func (e *EthermintBackend) getGasLimit() (int64, error) {
func (e *EthermintBackend) GetTxLogs(txHash common.Hash) ([]*ethtypes.Log, error) { func (e *EthermintBackend) GetTxLogs(txHash common.Hash) ([]*ethtypes.Log, error) {
// do we need to use the block height somewhere? // do we need to use the block height somewhere?
ctx := e.cliCtx ctx := e.cliCtx
res, _, err := ctx.QueryWithData(fmt.Sprintf("custom/%s/%s/%s", types.ModuleName, evm.QueryTxLogs, txHash.Hex()), nil) res, _, err := ctx.QueryWithData(fmt.Sprintf("custom/%s/%s/%s", types.ModuleName, evm.QueryTxLogs, txHash.Hex()), nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var out types.QueryETHLogs out := new(types.QueryETHLogs)
e.cliCtx.Codec.MustUnmarshalJSON(res, &out) if err := e.cliCtx.Codec.UnmarshalJSON(res, &out); err != nil {
return nil, err
}
return out.Logs, nil return out.Logs, nil
} }

View File

@ -293,8 +293,6 @@ func (e *PublicEthAPI) SendTransaction(args params.SendTxArgs) (common.Hash, err
// Broadcast transaction // Broadcast transaction
res, err := e.cliCtx.BroadcastTx(txBytes) res, err := e.cliCtx.BroadcastTx(txBytes)
// If error is encountered on the node, the broadcast will not return an error // If error is encountered on the node, the broadcast will not return an error
// TODO: Remove res log
fmt.Println(res.RawLog)
if err != nil { if err != nil {
return common.Hash{}, err return common.Hash{}, err
} }
@ -471,14 +469,7 @@ func (e *PublicEthAPI) EstimateGas(args CallArgs) (hexutil.Uint64, error) {
// GetBlockByHash returns the block identified by hash. // GetBlockByHash returns the block identified by hash.
func (e *PublicEthAPI) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) { func (e *PublicEthAPI) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) {
res, _, err := e.cliCtx.Query(fmt.Sprintf("custom/%s/%s/%s", types.ModuleName, evm.QueryHashToHeight, hash.Hex())) return e.backend.GetBlockByHash(hash, fullTx)
if err != nil {
return nil, err
}
var out types.QueryResBlockNumber
e.cliCtx.Codec.MustUnmarshalJSON(res, &out)
return e.backend.getEthBlockByNumber(out.Number, fullTx)
} }
// GetBlockByNumber returns the block identified by number. // GetBlockByNumber returns the block identified by number.
@ -488,7 +479,7 @@ func (e *PublicEthAPI) GetBlockByNumber(blockNum BlockNumber, fullTx bool) (map[
func formatBlock( func formatBlock(
header tmtypes.Header, size int, gasLimit int64, header tmtypes.Header, size int, gasLimit int64,
gasUsed *big.Int, transactions []interface{}, bloom ethtypes.Bloom, gasUsed *big.Int, transactions interface{}, bloom ethtypes.Bloom,
) map[string]interface{} { ) map[string]interface{} {
return map[string]interface{}{ return map[string]interface{}{
"number": hexutil.Uint64(header.Height), "number": hexutil.Uint64(header.Height),
@ -507,13 +498,13 @@ func formatBlock(
"gasLimit": hexutil.Uint64(gasLimit), // Static gas limit "gasLimit": hexutil.Uint64(gasLimit), // Static gas limit
"gasUsed": (*hexutil.Big)(gasUsed), "gasUsed": (*hexutil.Big)(gasUsed),
"timestamp": hexutil.Uint64(header.Time.Unix()), "timestamp": hexutil.Uint64(header.Time.Unix()),
"transactions": transactions, "transactions": transactions.([]common.Hash),
"uncles": nil, "uncles": nil,
} }
} }
func convertTransactionsToRPC(cliCtx context.CLIContext, txs []tmtypes.Tx, blockHash common.Hash, height uint64) ([]interface{}, *big.Int, error) { func convertTransactionsToRPC(cliCtx context.CLIContext, txs []tmtypes.Tx, blockHash common.Hash, height uint64) ([]common.Hash, *big.Int, error) {
transactions := make([]interface{}, len(txs)) transactions := make([]common.Hash, len(txs))
gasUsed := big.NewInt(0) gasUsed := big.NewInt(0)
for i, tx := range txs { for i, tx := range txs {
@ -523,10 +514,11 @@ func convertTransactionsToRPC(cliCtx context.CLIContext, txs []tmtypes.Tx, block
} }
// TODO: Remove gas usage calculation if saving gasUsed per block // TODO: Remove gas usage calculation if saving gasUsed per block
gasUsed.Add(gasUsed, ethTx.Fee()) gasUsed.Add(gasUsed, ethTx.Fee())
transactions[i], err = newRPCTransaction(*ethTx, blockHash, &height, uint64(i)) tx, err := newRPCTransaction(*ethTx, blockHash, &height, uint64(i))
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
transactions[i] = tx.Hash
} }
return transactions, gasUsed, nil return transactions, gasUsed, nil

View File

@ -1,12 +1,9 @@
package rpc package rpc
import ( import (
"encoding/json" "errors"
"fmt"
"math/big"
"github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/ethermint/x/evm/types"
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"
@ -62,11 +59,14 @@ func (e *PublicFilterAPI) UninstallFilter(id rpc.ID) bool {
// If the filter is a block filter, it returns an array of block hashes. // 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. // If the filter is a pending transaction filter, it returns an array of transaction hashes.
func (e *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) { func (e *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) {
if e.filters[id] == nil {
return nil, errors.New("invalid filter ID")
}
return e.filters[id].getFilterChanges() return e.filters[id].getFilterChanges()
} }
// GetFilterLogs returns an array of all logs matching filter with given id. // GetFilterLogs returns an array of all logs matching filter with given id.
func (e *PublicFilterAPI) GetFilterLogs(id rpc.ID) []*ethtypes.Log { func (e *PublicFilterAPI) GetFilterLogs(id rpc.ID) ([]*ethtypes.Log, error) {
return e.filters[id].getFilterLogs() return e.filters[id].getFilterLogs()
} }
@ -74,50 +74,6 @@ func (e *PublicFilterAPI) GetFilterLogs(id rpc.ID) []*ethtypes.Log {
// //
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getlogs // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getlogs
func (e *PublicFilterAPI) GetLogs(criteria filters.FilterCriteria) ([]*ethtypes.Log, error) { func (e *PublicFilterAPI) GetLogs(criteria filters.FilterCriteria) ([]*ethtypes.Log, error) {
var filter *Filter filter := NewFilter(e.backend, &criteria)
if criteria.BlockHash != nil { return filter.getFilterLogs()
/*
Still need to add blockhash in prepare function for log entry
*/
filter = NewFilterWithBlockHash(e.backend, &criteria)
results := e.getLogs()
logs := filterLogs(results, nil, nil, filter.addresses, filter.topics)
return logs, nil
}
// 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
} }

View File

@ -8,6 +8,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
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/log"
) )
/* /*
@ -37,7 +38,7 @@ type Filter struct {
// NewFilter returns a new Filter // NewFilter returns a new Filter
func NewFilter(backend Backend, criteria *filters.FilterCriteria) *Filter { func NewFilter(backend Backend, criteria *filters.FilterCriteria) *Filter {
return &Filter{ filter := &Filter{
backend: backend, backend: backend,
fromBlock: criteria.FromBlock, fromBlock: criteria.FromBlock,
toBlock: criteria.ToBlock, toBlock: criteria.ToBlock,
@ -46,6 +47,8 @@ func NewFilter(backend Backend, criteria *filters.FilterCriteria) *Filter {
typ: logFilter, typ: logFilter,
stopped: false, stopped: false,
} }
return filter
} }
// NewFilterWithBlockHash returns a new Filter with a blockHash. // NewFilterWithBlockHash returns a new Filter with a blockHash.
@ -139,28 +142,107 @@ func (f *Filter) getFilterChanges() (interface{}, error) {
case pendingTxFilter: case pendingTxFilter:
// TODO // TODO
case logFilter: case logFilter:
// TODO return f.getFilterLogs()
} }
return nil, nil return nil, errors.New("unsupported filter")
} }
func (f *Filter) getFilterLogs() []*ethtypes.Log { func (f *Filter) getFilterLogs() ([]*ethtypes.Log, error) {
// TODO ret := []*ethtypes.Log{}
return nil
// filter specific block only
if f.blockHash != nil {
block, err := f.backend.GetBlockByHash(*f.blockHash, true)
if err != nil {
return nil, err
}
// if the logsBloom == 0, there are no logs in that block
if txs, ok := block["transactions"].([]common.Hash); !ok {
return ret, nil
} else if len(txs) != 0 {
return f.checkMatches(block)
}
}
// filter range of blocks
num, err := f.backend.BlockNumber()
if err != nil {
return nil, err
}
// if f.fromBlock is set to 0, set it to the latest block number
if f.fromBlock == nil || f.fromBlock.Cmp(big.NewInt(0)) == 0 {
f.fromBlock = big.NewInt(int64(num))
}
// if f.toBlock is set to 0, set it to the latest block number
if f.toBlock == nil || f.toBlock.Cmp(big.NewInt(0)) == 0 {
f.toBlock = big.NewInt(int64(num))
}
log.Debug("[ethAPI] Retrieving filter logs", "fromBlock", f.fromBlock, "toBlock", f.toBlock,
"topics", f.topics, "addresses", f.addresses)
from := f.fromBlock.Int64()
to := f.toBlock.Int64()
for i := from; i <= to; i++ {
block, err := f.backend.GetBlockByNumber(NewBlockNumber(big.NewInt(i)), true)
if err != nil {
f.err = err
log.Debug("[ethAPI] Cannot get block", "block", block["number"], "error", err)
break
}
log.Debug("[ethAPI] filtering", "block", block)
// TODO: block logsBloom is often set in the wrong block
// if the logsBloom == 0, there are no logs in that block
if txs, ok := block["transactions"].([]common.Hash); !ok {
continue
} else if len(txs) != 0 {
logs, err := f.checkMatches(block)
if err != nil {
f.err = err
break
}
ret = append(ret, logs...)
}
}
return ret, nil
} }
func includes(addresses []common.Address, a common.Address) bool { func (f *Filter) checkMatches(block map[string]interface{}) ([]*ethtypes.Log, error) {
for _, addr := range addresses { transactions, ok := block["transactions"].([]common.Hash)
if addr == a { if !ok {
return true return nil, errors.New("invalid block transactions")
}
} }
return false unfiltered := []*ethtypes.Log{}
for _, tx := range transactions {
logs, err := f.backend.GetTxLogs(common.BytesToHash(tx[:]))
if err != nil {
return nil, err
}
unfiltered = append(unfiltered, logs...)
}
return filterLogs(unfiltered, f.fromBlock, f.toBlock, f.addresses, f.topics), nil
} }
// filterLogs creates a slice of logs matching the given criteria. // 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 { func filterLogs(logs []*ethtypes.Log, fromBlock, toBlock *big.Int, addresses []common.Address, topics [][]common.Hash) []*ethtypes.Log {
var ret []*ethtypes.Log var ret []*ethtypes.Log
Logs: Logs:
@ -194,3 +276,13 @@ Logs:
} }
return ret return ret
} }
func includes(addresses []common.Address, a common.Address) bool {
for _, addr := range addresses {
if addr == a {
return true
}
}
return false
}

View File

@ -15,9 +15,11 @@ import (
"math/big" "math/big"
"net/http" "net/http"
"testing" "testing"
"time"
"github.com/cosmos/ethermint/version" "github.com/cosmos/ethermint/version"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -29,6 +31,7 @@ const (
) )
var addr = fmt.Sprintf("http://%s:%d", host, port) var addr = fmt.Sprintf("http://%s:%d", host, port)
var zeroString = "0x0"
type Request struct { type Request struct {
Version string `json:"jsonrpc"` Version string `json:"jsonrpc"`
@ -78,7 +81,7 @@ func call(t *testing.T, method string, params interface{}) (*Response, error) {
} }
if rpcRes.Error != nil { if rpcRes.Error != nil {
t.Fatal(errors.New(rpcRes.Error.Message)) return nil, errors.New(rpcRes.Error.Message)
} }
err = res.Body.Close() err = res.Body.Close()
@ -115,7 +118,7 @@ func TestEth_blockNumber(t *testing.T) {
} }
func TestEth_GetBalance(t *testing.T) { func TestEth_GetBalance(t *testing.T) {
rpcRes, err := call(t, "eth_getBalance", []string{addrA, "0x0"}) rpcRes, err := call(t, "eth_getBalance", []string{addrA, zeroString})
require.NoError(t, err) require.NoError(t, err)
var res hexutil.Big var res hexutil.Big
@ -132,7 +135,7 @@ func TestEth_GetBalance(t *testing.T) {
func TestEth_GetStorageAt(t *testing.T) { func TestEth_GetStorageAt(t *testing.T) {
expectedRes := hexutil.Bytes{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} expectedRes := hexutil.Bytes{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
rpcRes, err := call(t, "eth_getStorageAt", []string{addrA, string(addrAStoreKey), "0x0"}) rpcRes, err := call(t, "eth_getStorageAt", []string{addrA, string(addrAStoreKey), zeroString})
require.NoError(t, err) require.NoError(t, err)
var storage hexutil.Bytes var storage hexutil.Bytes
@ -146,7 +149,7 @@ func TestEth_GetStorageAt(t *testing.T) {
func TestEth_GetCode(t *testing.T) { func TestEth_GetCode(t *testing.T) {
expectedRes := hexutil.Bytes{} expectedRes := hexutil.Bytes{}
rpcRes, err := call(t, "eth_getCode", []string{addrA, "0x0"}) rpcRes, err := call(t, "eth_getCode", []string{addrA, zeroString})
require.NoError(t, err) require.NoError(t, err)
var code hexutil.Bytes var code hexutil.Bytes
@ -158,6 +161,33 @@ func TestEth_GetCode(t *testing.T) {
require.True(t, bytes.Equal(expectedRes, code), "expected: %X got: %X", expectedRes, code) require.True(t, bytes.Equal(expectedRes, code), "expected: %X got: %X", expectedRes, code)
} }
func getAddress(t *testing.T) []byte {
rpcRes, err := call(t, "eth_accounts", []string{})
require.NoError(t, err)
var res []hexutil.Bytes
err = json.Unmarshal(rpcRes.Result, &res)
require.NoError(t, err)
return res[0]
}
func TestEth_SendTransaction(t *testing.T) {
from := getAddress(t)
param := make([]map[string]string, 1)
param[0] = make(map[string]string)
param[0]["from"] = "0x" + fmt.Sprintf("%x", from)
param[0]["data"] = "0x6080604052348015600f57600080fd5b5060117f775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd73889860405160405180910390a2603580604b6000396000f3fe6080604052600080fdfea165627a7a723058206cab665f0f557620554bb45adf266708d2bd349b8a4314bdff205ee8440e3c240029"
rpcRes, err := call(t, "eth_sendTransaction", param)
require.NoError(t, err)
var hash hexutil.Bytes
err = json.Unmarshal(rpcRes.Result, &hash)
require.NoError(t, err)
}
func TestEth_NewFilter(t *testing.T) { func TestEth_NewFilter(t *testing.T) {
param := make([]map[string][]string, 1) param := make([]map[string][]string, 1)
param[0] = make(map[string][]string) param[0] = make(map[string][]string)
@ -165,7 +195,299 @@ func TestEth_NewFilter(t *testing.T) {
rpcRes, err := call(t, "eth_newFilter", param) rpcRes, err := call(t, "eth_newFilter", param)
require.NoError(t, err) require.NoError(t, err)
var code hexutil.Bytes var ID hexutil.Bytes
err = code.UnmarshalJSON(rpcRes.Result) err = json.Unmarshal(rpcRes.Result, &ID)
require.NoError(t, err) require.NoError(t, err)
} }
func TestEth_NewBlockFilter(t *testing.T) {
rpcRes, err := call(t, "eth_newBlockFilter", []string{})
require.NoError(t, err)
var ID hexutil.Bytes
err = json.Unmarshal(rpcRes.Result, &ID)
require.NoError(t, err)
}
func TestEth_GetFilterChanges_NoLogs(t *testing.T) {
param := make([]map[string][]string, 1)
param[0] = make(map[string][]string)
param[0]["topics"] = []string{}
rpcRes, err := call(t, "eth_newFilter", param)
require.NoError(t, err)
var ID hexutil.Bytes
err = json.Unmarshal(rpcRes.Result, &ID)
require.NoError(t, err)
changesRes, err := call(t, "eth_getFilterChanges", []string{ID.String()})
require.NoError(t, err)
var logs []*ethtypes.Log
err = json.Unmarshal(changesRes.Result, &logs)
require.NoError(t, err)
}
func TestEth_GetFilterChanges_WrongID(t *testing.T) {
_, err := call(t, "eth_getFilterChanges", []string{"0x1122334400000077"})
require.NotNil(t, err)
}
// deployTestContract deploys a contract that emits an event in the constructor
func deployTestContract(t *testing.T) hexutil.Bytes {
from := getAddress(t)
param := make([]map[string]string, 1)
param[0] = make(map[string]string)
param[0]["from"] = "0x" + fmt.Sprintf("%x", from)
param[0]["data"] = "0x6080604052348015600f57600080fd5b5060117f775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd73889860405160405180910390a2603580604b6000396000f3fe6080604052600080fdfea165627a7a723058206cab665f0f557620554bb45adf266708d2bd349b8a4314bdff205ee8440e3c240029"
rpcRes, err := call(t, "eth_sendTransaction", param)
require.NoError(t, err)
var hash hexutil.Bytes
err = json.Unmarshal(rpcRes.Result, &hash)
require.NoError(t, err)
return hash
}
func TestEth_GetTransactionReceipt(t *testing.T) {
hash := deployTestContract(t)
time.Sleep(time.Second * 2)
param := []string{hash.String()}
rpcRes, err := call(t, "eth_getTransactionReceipt", param)
require.NoError(t, err)
t.Log(rpcRes.Result)
// TODO: why does this not return a receipt?
}
func TestEth_GetTxLogs(t *testing.T) {
// currently fails due to eth_sendTransaction returning the tendermint hash,
// while the logs are stored in the db using the ethereum hash
t.Skip()
hash := deployTestContract(t)
time.Sleep(time.Second * 5)
param := []string{hash.String()}
rpcRes, err := call(t, "eth_getTxLogs", param)
require.NoError(t, err)
logs := new([]*ethtypes.Log)
err = json.Unmarshal(rpcRes.Result, logs)
require.NoError(t, err)
require.Equal(t, 1, len(*logs))
t.Log((*logs)[0])
time.Sleep(time.Second)
}
func TestEth_GetFilterChanges_NoTopics(t *testing.T) {
rpcRes, err := call(t, "eth_blockNumber", []string{})
require.NoError(t, err)
var res hexutil.Uint64
err = res.UnmarshalJSON(rpcRes.Result)
require.NoError(t, err)
param := make([]map[string]interface{}, 1)
param[0] = make(map[string]interface{})
param[0]["topics"] = []string{}
param[0]["fromBlock"] = res.String()
param[0]["toBlock"] = zeroString // latest
// deploy contract, emitting some event
deployTestContract(t)
rpcRes, err = call(t, "eth_newFilter", param)
require.NoError(t, err)
var ID hexutil.Bytes
err = json.Unmarshal(rpcRes.Result, &ID)
require.NoError(t, err)
time.Sleep(time.Second)
// get filter changes
changesRes, err := call(t, "eth_getFilterChanges", []string{ID.String()})
require.NoError(t, err)
var logs []*ethtypes.Log
err = json.Unmarshal(changesRes.Result, &logs)
require.NoError(t, err)
require.Equal(t, 1, len(logs))
time.Sleep(time.Second)
//t.Log(logs[0])
// TODO: why is the tx hash in the log not the same as the tx hash of the transaction?
//require.Equal(t, logs[0].TxHash, common.BytesToHash(hash))
}
func TestEth_GetFilterChanges_Addresses(t *testing.T) {
// TODO: need transaction receipts to determine contract deployment address
}
func TestEth_GetFilterChanges_BlockHash(t *testing.T) {
// TODO: need transaction receipts to determine tx block
}
// hash of Hello event
var helloTopic = "0x775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd738898"
// world parameter in Hello event
var worldTopic = "0x0000000000000000000000000000000000000000000000000000000000000011"
func deployTestContractWithFunction(t *testing.T) hexutil.Bytes {
// pragma solidity ^0.5.1;
// contract Test {
// event Hello(uint256 indexed world);
// event Test(uint256 indexed a, uint256 indexed b);
// constructor() public {
// emit Hello(17);
// }
// function test(uint256 a, uint256 b) public {
// emit Test(a, b);
// }
// }
bytecode := "0x608060405234801561001057600080fd5b5060117f775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd73889860405160405180910390a260c98061004d6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063eb8ac92114602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506062565b005b80827f91916a5e2c96453ddf6b585497262675140eb9f7a774095fb003d93e6dc6921660405160405180910390a3505056fea265627a7a72315820ef746422e676b3ed22147cd771a6f689e7c33ef17bf5cd91921793b5dd01e3e064736f6c63430005110032"
from := getAddress(t)
param := make([]map[string]string, 1)
param[0] = make(map[string]string)
param[0]["from"] = "0x" + fmt.Sprintf("%x", from)
param[0]["data"] = bytecode
rpcRes, err := call(t, "eth_sendTransaction", param)
require.NoError(t, err)
var hash hexutil.Bytes
err = json.Unmarshal(rpcRes.Result, &hash)
require.NoError(t, err)
return hash
}
// Tests topics case where there are topics in first two positions
func TestEth_GetFilterChanges_Topics_AB(t *testing.T) {
time.Sleep(time.Second)
rpcRes, err := call(t, "eth_blockNumber", []string{})
require.NoError(t, err)
var res hexutil.Uint64
err = res.UnmarshalJSON(rpcRes.Result)
require.NoError(t, err)
param := make([]map[string]interface{}, 1)
param[0] = make(map[string]interface{})
param[0]["topics"] = []string{helloTopic, worldTopic}
param[0]["fromBlock"] = res.String()
param[0]["toBlock"] = zeroString // latest
deployTestContractWithFunction(t)
rpcRes, err = call(t, "eth_newFilter", param)
require.NoError(t, err)
var ID hexutil.Bytes
err = json.Unmarshal(rpcRes.Result, &ID)
require.NoError(t, err)
time.Sleep(time.Second * 2)
// get filter changes
changesRes, err := call(t, "eth_getFilterChanges", []string{ID.String()})
require.NoError(t, err)
var logs []*ethtypes.Log
err = json.Unmarshal(changesRes.Result, &logs)
require.NoError(t, err)
require.Equal(t, 1, len(logs))
time.Sleep(time.Second * 2)
}
func TestEth_GetFilterChanges_Topics_XB(t *testing.T) {
rpcRes, err := call(t, "eth_blockNumber", []string{})
require.NoError(t, err)
var res hexutil.Uint64
err = res.UnmarshalJSON(rpcRes.Result)
require.NoError(t, err)
param := make([]map[string]interface{}, 1)
param[0] = make(map[string]interface{})
param[0]["topics"] = []interface{}{nil, worldTopic}
param[0]["fromBlock"] = res.String()
param[0]["toBlock"] = "0x0" // latest
deployTestContractWithFunction(t)
rpcRes, err = call(t, "eth_newFilter", param)
require.NoError(t, err)
var ID hexutil.Bytes
err = json.Unmarshal(rpcRes.Result, &ID)
require.NoError(t, err)
time.Sleep(time.Second * 2)
// get filter changes
changesRes, err := call(t, "eth_getFilterChanges", []string{ID.String()})
require.NoError(t, err)
var logs []*ethtypes.Log
err = json.Unmarshal(changesRes.Result, &logs)
require.NoError(t, err)
require.Equal(t, 1, len(logs))
time.Sleep(time.Second)
}
func TestEth_GetFilterChanges_Topics_XXC(t *testing.T) {
// TODO: call test function, need tx receipts to determine contract address
}
func TestEth_GetLogs_NoLogs(t *testing.T) {
param := make([]map[string][]string, 1)
param[0] = make(map[string][]string)
param[0]["topics"] = []string{}
_, err := call(t, "eth_getLogs", param)
require.NoError(t, err)
}
func TestEth_GetLogs_Topics_AB(t *testing.T) {
rpcRes, err := call(t, "eth_blockNumber", []string{})
require.NoError(t, err)
var res hexutil.Uint64
err = res.UnmarshalJSON(rpcRes.Result)
require.NoError(t, err)
param := make([]map[string]interface{}, 1)
param[0] = make(map[string]interface{})
param[0]["topics"] = []string{helloTopic, worldTopic}
param[0]["fromBlock"] = res.String()
param[0]["toBlock"] = zeroString // latest
deployTestContractWithFunction(t)
rpcRes, err = call(t, "eth_getLogs", param)
require.NoError(t, err)
var logs []*ethtypes.Log
err = json.Unmarshal(rpcRes.Result, &logs)
require.NoError(t, err)
require.Equal(t, 1, len(logs))
}

View File

@ -3,6 +3,7 @@ package rpc
import ( import (
"fmt" "fmt"
"math" "math"
"math/big"
"strings" "strings"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
@ -19,6 +20,10 @@ const (
EarliestBlockNumber = BlockNumber(1) EarliestBlockNumber = BlockNumber(1)
) )
func NewBlockNumber(n *big.Int) BlockNumber {
return BlockNumber(n.Int64())
}
// UnmarshalJSON parses the given JSON fragment into a BlockNumber. It supports: // UnmarshalJSON parses the given JSON fragment into a BlockNumber. It supports:
// - "latest", "earliest" or "pending" as string arguments // - "latest", "earliest" or "pending" as string arguments
// - the block number // - the block number

View File

@ -43,8 +43,7 @@ func HandleMsgEthereumTx(ctx sdk.Context, k Keeper, msg types.MsgEthereumTx) sdk
return sdk.ResultFromError(err) return sdk.ResultFromError(err)
} }
txHash := tmtypes.Tx(ctx.TxBytes()).Hash() txHash := msg.Hash()
ethHash := common.BytesToHash(txHash)
st := types.StateTransition{ st := types.StateTransition{
Sender: sender, Sender: sender,
@ -56,11 +55,12 @@ func HandleMsgEthereumTx(ctx sdk.Context, k Keeper, msg types.MsgEthereumTx) sdk
Payload: msg.Data.Payload, Payload: msg.Data.Payload,
Csdb: k.CommitStateDB.WithContext(ctx), Csdb: k.CommitStateDB.WithContext(ctx),
ChainID: intChainID, ChainID: intChainID,
THash: &ethHash, THash: &txHash,
Simulate: ctx.IsCheckTx(), Simulate: ctx.IsCheckTx(),
} }
// Prepare db for logs // Prepare db for logs
k.CommitStateDB.Prepare(ethHash, common.Hash{}, k.TxCount) // TODO: block hash
k.CommitStateDB.Prepare(txHash, txHash, k.TxCount)
k.TxCount++ k.TxCount++
// TODO: move to keeper // TODO: move to keeper
@ -73,7 +73,7 @@ func HandleMsgEthereumTx(ctx sdk.Context, k Keeper, msg types.MsgEthereumTx) sdk
k.Bloom.Or(k.Bloom, returnData.Bloom) k.Bloom.Or(k.Bloom, returnData.Bloom)
// update transaction logs in KVStore // update transaction logs in KVStore
err = k.SetTransactionLogs(ctx, returnData.Logs, txHash) err = k.SetTransactionLogs(ctx, returnData.Logs, txHash[:])
if err != nil { if err != nil {
return sdk.ResultFromError(err) return sdk.ResultFromError(err)
} }

View File

@ -40,7 +40,6 @@ type ReturnData struct {
// TODO: update godoc, it doesn't explain what it does in depth. // TODO: update godoc, it doesn't explain what it does in depth.
func (st StateTransition) TransitionCSDB(ctx sdk.Context) (*ReturnData, error) { func (st StateTransition) TransitionCSDB(ctx sdk.Context) (*ReturnData, error) {
returnData := new(ReturnData) returnData := new(ReturnData)
contractCreation := st.Recipient == nil contractCreation := st.Recipient == nil
cost, err := core.IntrinsicGas(st.Payload, contractCreation, true) cost, err := core.IntrinsicGas(st.Payload, contractCreation, true)
@ -123,11 +122,13 @@ func (st StateTransition) TransitionCSDB(ctx sdk.Context) (*ReturnData, error) {
bloomInt := big.NewInt(0) bloomInt := big.NewInt(0)
var bloomFilter ethtypes.Bloom var bloomFilter ethtypes.Bloom
var logs []*ethtypes.Log var logs []*ethtypes.Log
if st.THash != nil && !st.Simulate { if st.THash != nil && !st.Simulate {
logs, err = csdb.GetLogs(*st.THash) logs, err = csdb.GetLogs(*st.THash)
if err != nil { if err != nil {
return nil, err return nil, err
} }
bloomInt = ethtypes.LogsBloom(logs) bloomInt = ethtypes.LogsBloom(logs)
bloomFilter = ethtypes.BytesToBloom(bloomInt.Bytes()) bloomFilter = ethtypes.BytesToBloom(bloomInt.Bytes())
} }
@ -171,6 +172,11 @@ func (st StateTransition) TransitionCSDB(ctx sdk.Context) (*ReturnData, error) {
// Out of gas check does not need to be done here since it is done within the EVM execution // Out of gas check does not need to be done here since it is done within the EVM execution
ctx.WithGasMeter(currentGasMeter).GasMeter().ConsumeGas(gasConsumed, "EVM execution consumption") ctx.WithGasMeter(currentGasMeter).GasMeter().ConsumeGas(gasConsumed, "EVM execution consumption")
err = st.Csdb.SetLogs(*st.THash, logs)
if err != nil {
return nil, err
}
returnData.Logs = logs returnData.Logs = logs
returnData.Bloom = bloomInt returnData.Bloom = bloomInt
returnData.Result = &sdk.Result{Data: resultData, GasUsed: gasConsumed} returnData.Result = &sdk.Result{Data: resultData, GasUsed: gasConsumed}

View File

@ -157,6 +157,18 @@ func (csdb *CommitStateDB) SetCode(addr ethcmn.Address, code []byte) {
} }
} }
// SetLogs sets the logs for a transaction in the KVStore.
func (csdb *CommitStateDB) SetLogs(hash ethcmn.Hash, logs []*ethtypes.Log) error {
store := csdb.ctx.KVStore(csdb.storeKey)
enc, err := EncodeLogs(logs)
if err != nil {
return err
}
store.Set(LogsKey(hash[:]), enc)
return nil
}
// AddLog adds a new log to the state and sets the log metadata from the state. // AddLog adds a new log to the state and sets the log metadata from the state.
func (csdb *CommitStateDB) AddLog(log *ethtypes.Log) { func (csdb *CommitStateDB) AddLog(log *ethtypes.Log) {
csdb.journal.append(addLogChange{txhash: csdb.thash}) csdb.journal.append(addLogChange{txhash: csdb.thash})
@ -288,7 +300,7 @@ func (csdb *CommitStateDB) GetCommittedState(addr ethcmn.Address, hash ethcmn.Ha
return ethcmn.Hash{} return ethcmn.Hash{}
} }
// GetLogs returns the current logs for a given hash in the state. // GetLogs returns the current logs for a given transaction hash from the KVStore.
func (csdb *CommitStateDB) GetLogs(hash ethcmn.Hash) ([]*ethtypes.Log, error) { func (csdb *CommitStateDB) GetLogs(hash ethcmn.Hash) ([]*ethtypes.Log, error) {
if csdb.logs[hash] != nil { if csdb.logs[hash] != nil {
return csdb.logs[hash], nil return csdb.logs[hash], nil