eth_getFilterLogs, eth_getLogs implementation (#248)
This commit is contained in:
parent
9b8dd598bb
commit
199484fc2e
@ -58,3 +58,9 @@ Ref: https://keepachangelog.com/en/1.0.0/
|
||||
* Update uninstallFilter and getFilterChanges accordingly
|
||||
* uninstallFilter stops the polling goroutine
|
||||
* 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
|
@ -20,6 +20,7 @@ type Backend interface {
|
||||
// Used by block filter; also used for polling
|
||||
BlockNumber() (hexutil.Uint64, 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)
|
||||
getGasLimit() (int64, error)
|
||||
|
||||
@ -62,6 +63,21 @@ func (e *EthermintBackend) GetBlockByNumber(blockNum BlockNumber, fullTx bool) (
|
||||
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) {
|
||||
// Remove this check when 0 query is fixed ref: (https://github.com/tendermint/tendermint/issues/4014)
|
||||
var blkNumPtr *int64
|
||||
@ -82,7 +98,7 @@ func (e *EthermintBackend) getEthBlockByNumber(height int64, fullTx bool) (map[s
|
||||
|
||||
var (
|
||||
gasUsed *big.Int
|
||||
transactions []interface{}
|
||||
transactions []common.Hash
|
||||
)
|
||||
|
||||
if fullTx {
|
||||
@ -96,7 +112,7 @@ func (e *EthermintBackend) getEthBlockByNumber(height int64, fullTx bool) (map[s
|
||||
} else {
|
||||
// TODO: Gas used not saved and cannot be calculated by 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 {
|
||||
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) {
|
||||
// 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)
|
||||
out := new(types.QueryETHLogs)
|
||||
if err := e.cliCtx.Codec.UnmarshalJSON(res, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return out.Logs, nil
|
||||
}
|
||||
|
||||
|
@ -293,8 +293,6 @@ func (e *PublicEthAPI) SendTransaction(args params.SendTxArgs) (common.Hash, err
|
||||
// Broadcast transaction
|
||||
res, err := e.cliCtx.BroadcastTx(txBytes)
|
||||
// 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 {
|
||||
return common.Hash{}, err
|
||||
}
|
||||
@ -471,14 +469,7 @@ func (e *PublicEthAPI) EstimateGas(args CallArgs) (hexutil.Uint64, error) {
|
||||
|
||||
// GetBlockByHash returns the block identified by hash.
|
||||
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()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var out types.QueryResBlockNumber
|
||||
e.cliCtx.Codec.MustUnmarshalJSON(res, &out)
|
||||
return e.backend.getEthBlockByNumber(out.Number, fullTx)
|
||||
return e.backend.GetBlockByHash(hash, fullTx)
|
||||
}
|
||||
|
||||
// GetBlockByNumber returns the block identified by number.
|
||||
@ -488,7 +479,7 @@ func (e *PublicEthAPI) GetBlockByNumber(blockNum BlockNumber, fullTx bool) (map[
|
||||
|
||||
func formatBlock(
|
||||
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{} {
|
||||
return map[string]interface{}{
|
||||
"number": hexutil.Uint64(header.Height),
|
||||
@ -507,13 +498,13 @@ func formatBlock(
|
||||
"gasLimit": hexutil.Uint64(gasLimit), // Static gas limit
|
||||
"gasUsed": (*hexutil.Big)(gasUsed),
|
||||
"timestamp": hexutil.Uint64(header.Time.Unix()),
|
||||
"transactions": transactions,
|
||||
"transactions": transactions.([]common.Hash),
|
||||
"uncles": nil,
|
||||
}
|
||||
}
|
||||
|
||||
func convertTransactionsToRPC(cliCtx context.CLIContext, txs []tmtypes.Tx, blockHash common.Hash, height uint64) ([]interface{}, *big.Int, error) {
|
||||
transactions := make([]interface{}, len(txs))
|
||||
func convertTransactionsToRPC(cliCtx context.CLIContext, txs []tmtypes.Tx, blockHash common.Hash, height uint64) ([]common.Hash, *big.Int, error) {
|
||||
transactions := make([]common.Hash, len(txs))
|
||||
gasUsed := big.NewInt(0)
|
||||
|
||||
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
|
||||
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 {
|
||||
return nil, nil, err
|
||||
}
|
||||
transactions[i] = tx.Hash
|
||||
}
|
||||
|
||||
return transactions, gasUsed, nil
|
||||
|
@ -1,12 +1,9 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"errors"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
"github.com/cosmos/ethermint/x/evm/types"
|
||||
|
||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||
"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 pending transaction filter, it returns an array of transaction hashes.
|
||||
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()
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
@ -74,50 +74,6 @@ func (e *PublicFilterAPI) GetFilterLogs(id rpc.ID) []*ethtypes.Log {
|
||||
//
|
||||
// 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 = 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
|
||||
filter := NewFilter(e.backend, &criteria)
|
||||
return filter.getFilterLogs()
|
||||
}
|
||||
|
116
rpc/filters.go
116
rpc/filters.go
@ -8,6 +8,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||
"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
|
||||
func NewFilter(backend Backend, criteria *filters.FilterCriteria) *Filter {
|
||||
return &Filter{
|
||||
filter := &Filter{
|
||||
backend: backend,
|
||||
fromBlock: criteria.FromBlock,
|
||||
toBlock: criteria.ToBlock,
|
||||
@ -46,6 +47,8 @@ func NewFilter(backend Backend, criteria *filters.FilterCriteria) *Filter {
|
||||
typ: logFilter,
|
||||
stopped: false,
|
||||
}
|
||||
|
||||
return filter
|
||||
}
|
||||
|
||||
// NewFilterWithBlockHash returns a new Filter with a blockHash.
|
||||
@ -139,28 +142,107 @@ func (f *Filter) getFilterChanges() (interface{}, error) {
|
||||
case pendingTxFilter:
|
||||
// TODO
|
||||
case logFilter:
|
||||
// TODO
|
||||
return f.getFilterLogs()
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
return nil, errors.New("unsupported filter")
|
||||
}
|
||||
|
||||
func (f *Filter) getFilterLogs() []*ethtypes.Log {
|
||||
// TODO
|
||||
return nil
|
||||
func (f *Filter) getFilterLogs() ([]*ethtypes.Log, error) {
|
||||
ret := []*ethtypes.Log{}
|
||||
|
||||
// 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 {
|
||||
for _, addr := range addresses {
|
||||
if addr == a {
|
||||
return true
|
||||
}
|
||||
func (f *Filter) checkMatches(block map[string]interface{}) ([]*ethtypes.Log, error) {
|
||||
transactions, ok := block["transactions"].([]common.Hash)
|
||||
if !ok {
|
||||
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.
|
||||
// [] -> 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 {
|
||||
var ret []*ethtypes.Log
|
||||
Logs:
|
||||
@ -194,3 +276,13 @@ Logs:
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func includes(addresses []common.Address, a common.Address) bool {
|
||||
for _, addr := range addresses {
|
||||
if addr == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
@ -15,9 +15,11 @@ import (
|
||||
"math/big"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cosmos/ethermint/version"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@ -29,6 +31,7 @@ const (
|
||||
)
|
||||
|
||||
var addr = fmt.Sprintf("http://%s:%d", host, port)
|
||||
var zeroString = "0x0"
|
||||
|
||||
type Request struct {
|
||||
Version string `json:"jsonrpc"`
|
||||
@ -78,7 +81,7 @@ func call(t *testing.T, method string, params interface{}) (*Response, error) {
|
||||
}
|
||||
|
||||
if rpcRes.Error != nil {
|
||||
t.Fatal(errors.New(rpcRes.Error.Message))
|
||||
return nil, errors.New(rpcRes.Error.Message)
|
||||
}
|
||||
|
||||
err = res.Body.Close()
|
||||
@ -115,7 +118,7 @@ func TestEth_blockNumber(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)
|
||||
|
||||
var res hexutil.Big
|
||||
@ -132,7 +135,7 @@ func TestEth_GetBalance(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}
|
||||
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)
|
||||
|
||||
var storage hexutil.Bytes
|
||||
@ -146,7 +149,7 @@ func TestEth_GetStorageAt(t *testing.T) {
|
||||
|
||||
func TestEth_GetCode(t *testing.T) {
|
||||
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)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
param := make([]map[string][]string, 1)
|
||||
param[0] = make(map[string][]string)
|
||||
@ -165,7 +195,299 @@ func TestEth_NewFilter(t *testing.T) {
|
||||
rpcRes, err := call(t, "eth_newFilter", param)
|
||||
require.NoError(t, err)
|
||||
|
||||
var code hexutil.Bytes
|
||||
err = code.UnmarshalJSON(rpcRes.Result)
|
||||
var ID hexutil.Bytes
|
||||
err = json.Unmarshal(rpcRes.Result, &ID)
|
||||
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))
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package rpc
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
@ -19,6 +20,10 @@ const (
|
||||
EarliestBlockNumber = BlockNumber(1)
|
||||
)
|
||||
|
||||
func NewBlockNumber(n *big.Int) BlockNumber {
|
||||
return BlockNumber(n.Int64())
|
||||
}
|
||||
|
||||
// UnmarshalJSON parses the given JSON fragment into a BlockNumber. It supports:
|
||||
// - "latest", "earliest" or "pending" as string arguments
|
||||
// - the block number
|
||||
|
@ -43,8 +43,7 @@ func HandleMsgEthereumTx(ctx sdk.Context, k Keeper, msg types.MsgEthereumTx) sdk
|
||||
return sdk.ResultFromError(err)
|
||||
}
|
||||
|
||||
txHash := tmtypes.Tx(ctx.TxBytes()).Hash()
|
||||
ethHash := common.BytesToHash(txHash)
|
||||
txHash := msg.Hash()
|
||||
|
||||
st := types.StateTransition{
|
||||
Sender: sender,
|
||||
@ -56,11 +55,12 @@ func HandleMsgEthereumTx(ctx sdk.Context, k Keeper, msg types.MsgEthereumTx) sdk
|
||||
Payload: msg.Data.Payload,
|
||||
Csdb: k.CommitStateDB.WithContext(ctx),
|
||||
ChainID: intChainID,
|
||||
THash: ðHash,
|
||||
THash: &txHash,
|
||||
Simulate: ctx.IsCheckTx(),
|
||||
}
|
||||
// Prepare db for logs
|
||||
k.CommitStateDB.Prepare(ethHash, common.Hash{}, k.TxCount)
|
||||
// TODO: block hash
|
||||
k.CommitStateDB.Prepare(txHash, txHash, k.TxCount)
|
||||
k.TxCount++
|
||||
|
||||
// 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)
|
||||
|
||||
// update transaction logs in KVStore
|
||||
err = k.SetTransactionLogs(ctx, returnData.Logs, txHash)
|
||||
err = k.SetTransactionLogs(ctx, returnData.Logs, txHash[:])
|
||||
if err != nil {
|
||||
return sdk.ResultFromError(err)
|
||||
}
|
||||
|
@ -40,7 +40,6 @@ type ReturnData struct {
|
||||
// TODO: update godoc, it doesn't explain what it does in depth.
|
||||
func (st StateTransition) TransitionCSDB(ctx sdk.Context) (*ReturnData, error) {
|
||||
returnData := new(ReturnData)
|
||||
|
||||
contractCreation := st.Recipient == nil
|
||||
|
||||
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)
|
||||
var bloomFilter ethtypes.Bloom
|
||||
var logs []*ethtypes.Log
|
||||
|
||||
if st.THash != nil && !st.Simulate {
|
||||
logs, err = csdb.GetLogs(*st.THash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bloomInt = ethtypes.LogsBloom(logs)
|
||||
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
|
||||
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.Bloom = bloomInt
|
||||
returnData.Result = &sdk.Result{Data: resultData, GasUsed: gasConsumed}
|
||||
|
@ -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.
|
||||
func (csdb *CommitStateDB) AddLog(log *ethtypes.Log) {
|
||||
csdb.journal.append(addLogChange{txhash: csdb.thash})
|
||||
@ -288,7 +300,7 @@ func (csdb *CommitStateDB) GetCommittedState(addr ethcmn.Address, hash ethcmn.Ha
|
||||
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) {
|
||||
if csdb.logs[hash] != nil {
|
||||
return csdb.logs[hash], nil
|
||||
|
Loading…
Reference in New Issue
Block a user