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:
parent
da9157e406
commit
35e7a98ab2
20
rpc/apis.go
20
rpc/apis.go
@ -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
180
rpc/backend.go
Normal 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
|
||||||
|
}
|
126
rpc/eth_api.go
126
rpc/eth_api.go
@ -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 (
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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])
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user