rpc, evm: refactor (#588)
* stargate: refactor * remove evm CLI * rpc: refactor * more fixes * fixes fixes fixes * changelog * refactor according to namespaces * fix * lint * remove export logic * fix rpc test * godoc
This commit is contained in:
parent
be09a6ee1b
commit
4501bbccdc
@ -42,6 +42,14 @@ Ref: https://keepachangelog.com/en/1.0.0/
|
|||||||
* (crypto) [\#559](https://github.com/cosmos/ethermint/pull/559) Refactored crypto package in preparation for the SDK's Stargate release:
|
* (crypto) [\#559](https://github.com/cosmos/ethermint/pull/559) Refactored crypto package in preparation for the SDK's Stargate release:
|
||||||
* `crypto.PubKeySecp256k1` and `crypto.PrivKeySecp256k1` are now `ethsecp256k1.PubKey` and `ethsecp256k1.PrivKey`, respectively
|
* `crypto.PubKeySecp256k1` and `crypto.PrivKeySecp256k1` are now `ethsecp256k1.PubKey` and `ethsecp256k1.PrivKey`, respectively
|
||||||
* Moved SDK `SigningAlgo` implementation for Ethermint's Secp256k1 key to `crypto/hd` package.
|
* Moved SDK `SigningAlgo` implementation for Ethermint's Secp256k1 key to `crypto/hd` package.
|
||||||
|
* (rpc) [\#588](https://github.com/cosmos/ethermint/pull/588) The `rpc` package has been refactored to account for the separation of each
|
||||||
|
corresponding Ethereum API namespace:
|
||||||
|
* `rpc/namespaces/eth`: `eth` namespace. Exposes the `PublicEthereumAPI` and the `PublicFilterAPI`.
|
||||||
|
* `rpc/namespaces/personal`: `personal` namespace. Exposes the `PrivateAccountAPI`.
|
||||||
|
* `rpc/namespaces/net`: `net` namespace. Exposes the `PublicNetAPI`.
|
||||||
|
* `rpc/namespaces/web3`: `web3` namespace. Exposes the `PublicWeb3API`.
|
||||||
|
|
||||||
|
* (evm) [\#588](https://github.com/cosmos/ethermint/pull/588) The EVM transaction CLI has been removed in favor of the JSON-RPC.
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ func main() {
|
|||||||
queryCmd(cdc),
|
queryCmd(cdc),
|
||||||
txCmd(cdc),
|
txCmd(cdc),
|
||||||
client.ValidateChainID(
|
client.ValidateChainID(
|
||||||
rpc.EmintServeCmd(cdc),
|
rpc.ServeCmd(cdc),
|
||||||
),
|
),
|
||||||
flags.LineBreak,
|
flags.LineBreak,
|
||||||
client.KeyCommands(),
|
client.KeyCommands(),
|
||||||
|
37
rpc/apis.go
37
rpc/apis.go
@ -1,5 +1,3 @@
|
|||||||
// Package rpc contains RPC handler methods and utilities to start
|
|
||||||
// Ethermint's Web3-compatibly JSON-RPC server.
|
|
||||||
package rpc
|
package rpc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -8,6 +6,13 @@ import (
|
|||||||
"github.com/cosmos/cosmos-sdk/client/context"
|
"github.com/cosmos/cosmos-sdk/client/context"
|
||||||
|
|
||||||
"github.com/cosmos/ethermint/crypto/ethsecp256k1"
|
"github.com/cosmos/ethermint/crypto/ethsecp256k1"
|
||||||
|
"github.com/cosmos/ethermint/rpc/backend"
|
||||||
|
"github.com/cosmos/ethermint/rpc/namespaces/eth"
|
||||||
|
"github.com/cosmos/ethermint/rpc/namespaces/eth/filters"
|
||||||
|
"github.com/cosmos/ethermint/rpc/namespaces/net"
|
||||||
|
"github.com/cosmos/ethermint/rpc/namespaces/personal"
|
||||||
|
"github.com/cosmos/ethermint/rpc/namespaces/web3"
|
||||||
|
rpctypes "github.com/cosmos/ethermint/rpc/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RPC namespaces and API version
|
// RPC namespaces and API version
|
||||||
@ -20,17 +25,17 @@ const (
|
|||||||
apiVersion = "1.0"
|
apiVersion = "1.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetRPCAPIs returns the list of all APIs
|
// GetAPIs returns the list of all APIs from the Ethereum namespaces
|
||||||
func GetRPCAPIs(cliCtx context.CLIContext, keys []ethsecp256k1.PrivKey) []rpc.API {
|
func GetAPIs(clientCtx context.CLIContext, keys ...ethsecp256k1.PrivKey) []rpc.API {
|
||||||
nonceLock := new(AddrLocker)
|
nonceLock := new(rpctypes.AddrLocker)
|
||||||
backend := NewEthermintBackend(cliCtx)
|
backend := backend.New(clientCtx)
|
||||||
ethAPI := NewPublicEthAPI(cliCtx, backend, nonceLock, keys)
|
ethAPI := eth.NewAPI(clientCtx, backend, nonceLock, keys...)
|
||||||
|
|
||||||
return []rpc.API{
|
return []rpc.API{
|
||||||
{
|
{
|
||||||
Namespace: Web3Namespace,
|
Namespace: Web3Namespace,
|
||||||
Version: apiVersion,
|
Version: apiVersion,
|
||||||
Service: NewPublicWeb3API(),
|
Service: web3.NewAPI(),
|
||||||
Public: true,
|
Public: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -39,22 +44,22 @@ func GetRPCAPIs(cliCtx context.CLIContext, keys []ethsecp256k1.PrivKey) []rpc.AP
|
|||||||
Service: ethAPI,
|
Service: ethAPI,
|
||||||
Public: true,
|
Public: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Namespace: PersonalNamespace,
|
|
||||||
Version: apiVersion,
|
|
||||||
Service: NewPersonalEthAPI(ethAPI),
|
|
||||||
Public: false,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Namespace: EthNamespace,
|
Namespace: EthNamespace,
|
||||||
Version: apiVersion,
|
Version: apiVersion,
|
||||||
Service: NewPublicFilterAPI(cliCtx, backend),
|
Service: filters.NewAPI(clientCtx, backend),
|
||||||
Public: true,
|
Public: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Namespace: PersonalNamespace,
|
||||||
|
Version: apiVersion,
|
||||||
|
Service: personal.NewAPI(ethAPI),
|
||||||
|
Public: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Namespace: NetNamespace,
|
Namespace: NetNamespace,
|
||||||
Version: apiVersion,
|
Version: apiVersion,
|
||||||
Service: NewPublicNetAPI(cliCtx),
|
Service: net.NewAPI(clientCtx),
|
||||||
Public: true,
|
Public: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
package args
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SendTxArgs represents the arguments to submit a new transaction into the transaction pool.
|
|
||||||
// Duplicate struct definition since geth struct is in internal package
|
|
||||||
// Ref: https://github.com/ethereum/go-ethereum/blob/release/1.9/internal/ethapi/api.go#L1346
|
|
||||||
type SendTxArgs struct {
|
|
||||||
From common.Address `json:"from"`
|
|
||||||
To *common.Address `json:"to"`
|
|
||||||
Gas *hexutil.Uint64 `json:"gas"`
|
|
||||||
GasPrice *hexutil.Big `json:"gasPrice"`
|
|
||||||
Value *hexutil.Big `json:"value"`
|
|
||||||
Nonce *hexutil.Uint64 `json:"nonce"`
|
|
||||||
// We accept "data" and "input" for backwards-compatibility reasons. "input" is the
|
|
||||||
// newer name and should be preferred by clients.
|
|
||||||
Data *hexutil.Bytes `json:"data"`
|
|
||||||
Input *hexutil.Bytes `json:"input"`
|
|
||||||
}
|
|
328
rpc/backend.go
328
rpc/backend.go
@ -1,328 +0,0 @@
|
|||||||
package rpc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/tendermint/tendermint/libs/log"
|
|
||||||
tmtypes "github.com/tendermint/tendermint/types"
|
|
||||||
|
|
||||||
evmtypes "github.com/cosmos/ethermint/x/evm/types"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/context"
|
|
||||||
|
|
||||||
"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)
|
|
||||||
HeaderByNumber(blockNum BlockNumber) (*ethtypes.Header, error)
|
|
||||||
HeaderByHash(blockHash common.Hash) (*ethtypes.Header, 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)
|
|
||||||
// returns the logs of a given block
|
|
||||||
GetLogs(blockHash common.Hash) ([][]*ethtypes.Log, error)
|
|
||||||
|
|
||||||
// Used by pending transaction filter
|
|
||||||
PendingTransactions() ([]*Transaction, error)
|
|
||||||
|
|
||||||
// Used by log filter
|
|
||||||
GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error)
|
|
||||||
BloomStatus() (uint64, uint64)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ Backend = (*EthermintBackend)(nil)
|
|
||||||
|
|
||||||
// EthermintBackend implements the Backend interface
|
|
||||||
type EthermintBackend struct {
|
|
||||||
cliCtx context.CLIContext
|
|
||||||
logger log.Logger
|
|
||||||
gasLimit int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewEthermintBackend creates a new EthermintBackend instance
|
|
||||||
func NewEthermintBackend(cliCtx context.CLIContext) *EthermintBackend {
|
|
||||||
return &EthermintBackend{
|
|
||||||
cliCtx: cliCtx,
|
|
||||||
logger: log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "json-rpc"),
|
|
||||||
gasLimit: int64(^uint32(0)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BlockNumber returns the current block number.
|
|
||||||
func (e *EthermintBackend) BlockNumber() (hexutil.Uint64, error) {
|
|
||||||
res, height, err := e.cliCtx.QueryWithData(fmt.Sprintf("custom/%s/blockNumber", evmtypes.ModuleName), nil)
|
|
||||||
if err != nil {
|
|
||||||
return hexutil.Uint64(0), err
|
|
||||||
}
|
|
||||||
|
|
||||||
var out evmtypes.QueryResBlockNumber
|
|
||||||
e.cliCtx.Codec.MustUnmarshalJSON(res, &out)
|
|
||||||
|
|
||||||
e.cliCtx.WithHeight(height)
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBlockByHash returns the block identified by hash.
|
|
||||||
func (e *EthermintBackend) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) {
|
|
||||||
res, height, err := e.cliCtx.Query(fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryHashToHeight, hash.Hex()))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var out evmtypes.QueryResBlockNumber
|
|
||||||
if err := e.cliCtx.Codec.UnmarshalJSON(res, &out); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
e.cliCtx = e.cliCtx.WithHeight(height)
|
|
||||||
return e.getEthBlockByNumber(out.Number, fullTx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HeaderByNumber returns the block header identified by height.
|
|
||||||
func (e *EthermintBackend) HeaderByNumber(blockNum BlockNumber) (*ethtypes.Header, error) {
|
|
||||||
return e.getBlockHeader(blockNum.Int64())
|
|
||||||
}
|
|
||||||
|
|
||||||
// HeaderByHash returns the block header identified by hash.
|
|
||||||
func (e *EthermintBackend) HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error) {
|
|
||||||
res, height, err := e.cliCtx.Query(fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryHashToHeight, blockHash.Hex()))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var out evmtypes.QueryResBlockNumber
|
|
||||||
if err := e.cliCtx.Codec.UnmarshalJSON(res, &out); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
e.cliCtx = e.cliCtx.WithHeight(height)
|
|
||||||
return e.getBlockHeader(out.Number)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *EthermintBackend) getBlockHeader(height int64) (*ethtypes.Header, error) {
|
|
||||||
if height <= 0 {
|
|
||||||
// get latest block height
|
|
||||||
num, err := e.BlockNumber()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
height = int64(num)
|
|
||||||
}
|
|
||||||
|
|
||||||
block, err := e.cliCtx.Client.Block(&height)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
res, _, err := e.cliCtx.Query(fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryBloom, strconv.FormatInt(height, 10)))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var bloomRes evmtypes.QueryBloomFilter
|
|
||||||
e.cliCtx.Codec.MustUnmarshalJSON(res, &bloomRes)
|
|
||||||
|
|
||||||
ethHeader := EthHeaderFromTendermint(block.Block.Header)
|
|
||||||
ethHeader.Bloom = bloomRes.Bloom
|
|
||||||
|
|
||||||
return ethHeader, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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 []common.Hash
|
|
||||||
)
|
|
||||||
|
|
||||||
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([]common.Hash, 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", evmtypes.ModuleName, evmtypes.QueryBloom, strconv.FormatInt(block.Block.Height, 10)))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var out evmtypes.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
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTransactionLogs returns the logs given a transaction hash.
|
|
||||||
// It returns an error if there's an encoding error.
|
|
||||||
// If no logs are found for the tx hash, the error is nil.
|
|
||||||
func (e *EthermintBackend) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error) {
|
|
||||||
ctx := e.cliCtx
|
|
||||||
|
|
||||||
res, height, err := ctx.QueryWithData(fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryTransactionLogs, txHash.Hex()), nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
out := new(evmtypes.QueryETHLogs)
|
|
||||||
if err := e.cliCtx.Codec.UnmarshalJSON(res, &out); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
e.cliCtx = e.cliCtx.WithHeight(height)
|
|
||||||
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, pendingTxs.Count)
|
|
||||||
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.BytesToHash(tx.Hash()), common.Hash{}, nil, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
transactions = append(transactions, rpcTx)
|
|
||||||
}
|
|
||||||
|
|
||||||
return transactions, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetLogs returns all the logs from all the ethreum transactions in a block.
|
|
||||||
func (e *EthermintBackend) GetLogs(blockHash common.Hash) ([][]*ethtypes.Log, error) {
|
|
||||||
res, _, err := e.cliCtx.Query(fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryHashToHeight, blockHash.Hex()))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var out evmtypes.QueryResBlockNumber
|
|
||||||
if err := e.cliCtx.Codec.UnmarshalJSON(res, &out); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
block, err := e.cliCtx.Client.Block(&out.Number)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var blockLogs = [][]*ethtypes.Log{}
|
|
||||||
for _, tx := range block.Block.Txs {
|
|
||||||
// NOTE: we query the state in case the tx result logs are not persisted after an upgrade.
|
|
||||||
res, _, err := e.cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryTransactionLogs, common.BytesToHash(tx.Hash()).Hex()), nil)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
out := new(evmtypes.QueryETHLogs)
|
|
||||||
if err := e.cliCtx.Codec.UnmarshalJSON(res, &out); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
blockLogs = append(blockLogs, out.Logs)
|
|
||||||
}
|
|
||||||
|
|
||||||
return blockLogs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// BloomStatus returns the BloomBitsBlocks and the number of processed sections maintained
|
|
||||||
// by the chain indexer.
|
|
||||||
func (e *EthermintBackend) BloomStatus() (uint64, uint64) {
|
|
||||||
return 4096, 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// EthHeaderFromTendermint is an util function that returns an Ethereum Header
|
|
||||||
// from a tendermint Header.
|
|
||||||
func EthHeaderFromTendermint(header tmtypes.Header) *ethtypes.Header {
|
|
||||||
return ðtypes.Header{
|
|
||||||
ParentHash: common.BytesToHash(header.LastBlockID.Hash.Bytes()),
|
|
||||||
UncleHash: common.Hash{},
|
|
||||||
Coinbase: common.Address{},
|
|
||||||
Root: common.BytesToHash(header.AppHash),
|
|
||||||
TxHash: common.BytesToHash(header.DataHash),
|
|
||||||
ReceiptHash: common.Hash{},
|
|
||||||
Difficulty: nil,
|
|
||||||
Number: big.NewInt(header.Height),
|
|
||||||
Time: uint64(header.Time.Unix()),
|
|
||||||
Extra: nil,
|
|
||||||
MixDigest: common.Hash{},
|
|
||||||
Nonce: ethtypes.BlockNonce{},
|
|
||||||
}
|
|
||||||
}
|
|
259
rpc/backend/backend.go
Normal file
259
rpc/backend/backend.go
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
package backend
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/tendermint/tendermint/libs/log"
|
||||||
|
|
||||||
|
rpctypes "github.com/cosmos/ethermint/rpc/types"
|
||||||
|
evmtypes "github.com/cosmos/ethermint/x/evm/types"
|
||||||
|
|
||||||
|
clientcontext "github.com/cosmos/cosmos-sdk/client/context"
|
||||||
|
|
||||||
|
"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)
|
||||||
|
HeaderByNumber(blockNum rpctypes.BlockNumber) (*ethtypes.Header, error)
|
||||||
|
HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error)
|
||||||
|
GetBlockByNumber(blockNum rpctypes.BlockNumber, fullTx bool) (map[string]interface{}, error)
|
||||||
|
GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error)
|
||||||
|
|
||||||
|
// returns the logs of a given block
|
||||||
|
GetLogs(blockHash common.Hash) ([][]*ethtypes.Log, error)
|
||||||
|
|
||||||
|
// Used by pending transaction filter
|
||||||
|
PendingTransactions() ([]*rpctypes.Transaction, error)
|
||||||
|
|
||||||
|
// Used by log filter
|
||||||
|
GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error)
|
||||||
|
BloomStatus() (uint64, uint64)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Backend = (*EthermintBackend)(nil)
|
||||||
|
|
||||||
|
// EthermintBackend implements the Backend interface
|
||||||
|
type EthermintBackend struct {
|
||||||
|
ctx context.Context
|
||||||
|
clientCtx clientcontext.CLIContext
|
||||||
|
logger log.Logger
|
||||||
|
gasLimit int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new EthermintBackend instance
|
||||||
|
func New(clientCtx clientcontext.CLIContext) *EthermintBackend {
|
||||||
|
return &EthermintBackend{
|
||||||
|
ctx: context.Background(),
|
||||||
|
clientCtx: clientCtx,
|
||||||
|
logger: log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "json-rpc"),
|
||||||
|
gasLimit: int64(^uint32(0)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockNumber returns the current block number.
|
||||||
|
func (b *EthermintBackend) BlockNumber() (hexutil.Uint64, error) {
|
||||||
|
// NOTE: using 0 as min and max height returns the blockchain info up to the latest block.
|
||||||
|
info, err := b.clientCtx.Client.BlockchainInfo(0, 0)
|
||||||
|
if err != nil {
|
||||||
|
return hexutil.Uint64(0), err
|
||||||
|
}
|
||||||
|
|
||||||
|
return hexutil.Uint64(info.LastHeight), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockByNumber returns the block identified by number.
|
||||||
|
func (b *EthermintBackend) GetBlockByNumber(blockNum rpctypes.BlockNumber, fullTx bool) (map[string]interface{}, error) {
|
||||||
|
height := blockNum.Int64()
|
||||||
|
if height <= 0 {
|
||||||
|
// get latest block height
|
||||||
|
num, err := b.BlockNumber()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
height = int64(num)
|
||||||
|
}
|
||||||
|
|
||||||
|
resBlock, err := b.clientCtx.Client.Block(&height)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return rpctypes.EthBlockFromTendermint(b.clientCtx, resBlock.Block)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockByHash returns the block identified by hash.
|
||||||
|
func (b *EthermintBackend) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) {
|
||||||
|
res, _, err := b.clientCtx.Query(fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryHashToHeight, hash.Hex()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var out evmtypes.QueryResBlockNumber
|
||||||
|
if err := b.clientCtx.Codec.UnmarshalJSON(res, &out); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resBlock, err := b.clientCtx.Client.Block(&out.Number)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return rpctypes.EthBlockFromTendermint(b.clientCtx, resBlock.Block)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeaderByNumber returns the block header identified by height.
|
||||||
|
func (b *EthermintBackend) HeaderByNumber(blockNum rpctypes.BlockNumber) (*ethtypes.Header, error) {
|
||||||
|
height := blockNum.Int64()
|
||||||
|
if height <= 0 {
|
||||||
|
// get latest block height
|
||||||
|
num, err := b.BlockNumber()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
height = int64(num)
|
||||||
|
}
|
||||||
|
|
||||||
|
resBlock, err := b.clientCtx.Client.Block(&height)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res, _, err := b.clientCtx.Query(fmt.Sprintf("custom/%s/%s/%d", evmtypes.ModuleName, evmtypes.QueryBloom, resBlock.Block.Height))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var bloomRes evmtypes.QueryBloomFilter
|
||||||
|
b.clientCtx.Codec.MustUnmarshalJSON(res, &bloomRes)
|
||||||
|
|
||||||
|
ethHeader := rpctypes.EthHeaderFromTendermint(resBlock.Block.Header)
|
||||||
|
ethHeader.Bloom = bloomRes.Bloom
|
||||||
|
return ethHeader, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeaderByHash returns the block header identified by hash.
|
||||||
|
func (b *EthermintBackend) HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error) {
|
||||||
|
res, _, err := b.clientCtx.Query(fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryHashToHeight, blockHash.Hex()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var out evmtypes.QueryResBlockNumber
|
||||||
|
if err := b.clientCtx.Codec.UnmarshalJSON(res, &out); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resBlock, err := b.clientCtx.Client.Block(&out.Number)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res, _, err = b.clientCtx.Query(fmt.Sprintf("custom/%s/%s/%d", evmtypes.ModuleName, evmtypes.QueryBloom, resBlock.Block.Height))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var bloomRes evmtypes.QueryBloomFilter
|
||||||
|
b.clientCtx.Codec.MustUnmarshalJSON(res, &bloomRes)
|
||||||
|
|
||||||
|
ethHeader := rpctypes.EthHeaderFromTendermint(resBlock.Block.Header)
|
||||||
|
ethHeader.Bloom = bloomRes.Bloom
|
||||||
|
return ethHeader, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTransactionLogs returns the logs given a transaction hash.
|
||||||
|
// It returns an error if there's an encoding error.
|
||||||
|
// If no logs are found for the tx hash, the error is nil.
|
||||||
|
func (b *EthermintBackend) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error) {
|
||||||
|
res, _, err := b.clientCtx.QueryWithData(fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryTransactionLogs, txHash.String()), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
out := new(evmtypes.QueryETHLogs)
|
||||||
|
if err := b.clientCtx.Codec.UnmarshalJSON(res, &out); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (b *EthermintBackend) PendingTransactions() ([]*rpctypes.Transaction, error) {
|
||||||
|
pendingTxs, err := b.clientCtx.Client.UnconfirmedTxs(1000)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
transactions := make([]*rpctypes.Transaction, pendingTxs.Count)
|
||||||
|
for _, tx := range pendingTxs.Txs {
|
||||||
|
ethTx, err := rpctypes.RawTxToEthTx(b.clientCtx, tx)
|
||||||
|
if err != nil {
|
||||||
|
// ignore non Ethermint EVM transactions
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: check signer and reference against accounts the node manages
|
||||||
|
rpcTx, err := rpctypes.NewTransaction(ethTx, common.BytesToHash(tx.Hash()), common.Hash{}, 0, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
transactions = append(transactions, rpcTx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return transactions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLogs returns all the logs from all the ethereum transactions in a block.
|
||||||
|
func (b *EthermintBackend) GetLogs(blockHash common.Hash) ([][]*ethtypes.Log, error) {
|
||||||
|
res, _, err := b.clientCtx.Query(fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryHashToHeight, blockHash.Hex()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var out evmtypes.QueryResBlockNumber
|
||||||
|
if err := b.clientCtx.Codec.UnmarshalJSON(res, &out); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
block, err := b.clientCtx.Client.Block(&out.Number)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var blockLogs = [][]*ethtypes.Log{}
|
||||||
|
for _, tx := range block.Block.Txs {
|
||||||
|
// NOTE: we query the state in case the tx result logs are not persisted after an upgrade.
|
||||||
|
res, _, err := b.clientCtx.QueryWithData(fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryTransactionLogs, common.BytesToHash(tx.Hash()).String()), nil)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
out := new(evmtypes.QueryETHLogs)
|
||||||
|
if err := b.clientCtx.Codec.UnmarshalJSON(res, &out); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
blockLogs = append(blockLogs, out.Logs)
|
||||||
|
}
|
||||||
|
|
||||||
|
return blockLogs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BloomStatus returns the BloomBitsBlocks and the number of processed sections maintained
|
||||||
|
// by the chain indexer.
|
||||||
|
func (b *EthermintBackend) BloomStatus() (uint64, uint64) {
|
||||||
|
return 4096, 0
|
||||||
|
}
|
18
rpc/cmd.go
Normal file
18
rpc/cmd.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package rpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/lcd"
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ServeCmd creates a CLI command to start Cosmos REST server with web3 RPC API and
|
||||||
|
// Cosmos rest-server endpoints
|
||||||
|
func ServeCmd(cdc *codec.Codec) *cobra.Command {
|
||||||
|
cmd := lcd.ServeCommand(cdc, RegisterRoutes)
|
||||||
|
cmd.Flags().String(flagUnlockKey, "", "Select a key to unlock on the RPC server")
|
||||||
|
cmd.Flags().String(flagWebsocket, "8546", "websocket port to listen to")
|
||||||
|
cmd.Flags().StringP(flags.FlagBroadcastMode, "b", flags.BroadcastSync, "Transaction broadcasting mode (sync|async|block)")
|
||||||
|
return cmd
|
||||||
|
}
|
@ -6,14 +6,12 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
"github.com/cosmos/cosmos-sdk/client"
|
||||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||||
"github.com/cosmos/cosmos-sdk/client/input"
|
"github.com/cosmos/cosmos-sdk/client/input"
|
||||||
"github.com/cosmos/cosmos-sdk/client/lcd"
|
"github.com/cosmos/cosmos-sdk/client/lcd"
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keys"
|
"github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
authrest "github.com/cosmos/cosmos-sdk/x/auth/client/rest"
|
authrest "github.com/cosmos/cosmos-sdk/x/auth/client/rest"
|
||||||
@ -21,6 +19,7 @@ import (
|
|||||||
"github.com/cosmos/ethermint/app"
|
"github.com/cosmos/ethermint/app"
|
||||||
"github.com/cosmos/ethermint/crypto/ethsecp256k1"
|
"github.com/cosmos/ethermint/crypto/ethsecp256k1"
|
||||||
"github.com/cosmos/ethermint/crypto/hd"
|
"github.com/cosmos/ethermint/crypto/hd"
|
||||||
|
"github.com/cosmos/ethermint/rpc/websockets"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -29,20 +28,10 @@ const (
|
|||||||
flagWebsocket = "wsport"
|
flagWebsocket = "wsport"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EmintServeCmd creates a CLI command to start Cosmos REST server with web3 RPC API and
|
// RegisterRoutes creates a new server and registers the `/rpc` endpoint.
|
||||||
// Cosmos rest-server endpoints
|
|
||||||
func EmintServeCmd(cdc *codec.Codec) *cobra.Command {
|
|
||||||
cmd := lcd.ServeCommand(cdc, registerRoutes)
|
|
||||||
cmd.Flags().String(flagUnlockKey, "", "Select a key to unlock on the RPC server")
|
|
||||||
cmd.Flags().String(flagWebsocket, "8546", "websocket port to listen to")
|
|
||||||
cmd.Flags().StringP(flags.FlagBroadcastMode, "b", flags.BroadcastSync, "Transaction broadcasting mode (sync|async|block)")
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// registerRoutes creates a new server and registers the `/rpc` endpoint.
|
|
||||||
// Rpc calls are enabled based on their associated module (eg. "eth").
|
// Rpc calls are enabled based on their associated module (eg. "eth").
|
||||||
func registerRoutes(rs *lcd.RestServer) {
|
func RegisterRoutes(rs *lcd.RestServer) {
|
||||||
s := rpc.NewServer()
|
server := rpc.NewServer()
|
||||||
accountName := viper.GetString(flagUnlockKey)
|
accountName := viper.GetString(flagUnlockKey)
|
||||||
accountNames := strings.Split(accountName, ",")
|
accountNames := strings.Split(accountName, ",")
|
||||||
|
|
||||||
@ -71,26 +60,18 @@ func registerRoutes(rs *lcd.RestServer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apis := GetRPCAPIs(rs.CliCtx, privkeys)
|
apis := GetAPIs(rs.CliCtx, privkeys...)
|
||||||
|
|
||||||
// TODO: Allow cli to configure modules https://github.com/cosmos/ethermint/issues/74
|
// Register all the APIs exposed by the namespace services
|
||||||
whitelist := make(map[string]bool)
|
// TODO: handle allowlist and private APIs
|
||||||
|
|
||||||
// Register all the APIs exposed by the services
|
|
||||||
for _, api := range apis {
|
for _, api := range apis {
|
||||||
if whitelist[api.Namespace] || (len(whitelist) == 0 && api.Public) {
|
if err := server.RegisterName(api.Namespace, api.Service); err != nil {
|
||||||
if err := s.RegisterName(api.Namespace, api.Service); err != nil {
|
panic(err)
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
} else if !api.Public { // TODO: how to handle private apis? should only accept local calls
|
|
||||||
if err := s.RegisterName(api.Namespace, api.Service); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Web3 RPC API route
|
// Web3 RPC API route
|
||||||
rs.Mux.HandleFunc("/", s.ServeHTTP).Methods("POST", "OPTIONS")
|
rs.Mux.HandleFunc("/", server.ServeHTTP).Methods("POST", "OPTIONS")
|
||||||
|
|
||||||
// Register all other Cosmos routes
|
// Register all other Cosmos routes
|
||||||
client.RegisterRoutes(rs.CliCtx, rs.Mux)
|
client.RegisterRoutes(rs.CliCtx, rs.Mux)
|
||||||
@ -99,8 +80,8 @@ func registerRoutes(rs *lcd.RestServer) {
|
|||||||
|
|
||||||
// start websockets server
|
// start websockets server
|
||||||
websocketAddr := viper.GetString(flagWebsocket)
|
websocketAddr := viper.GetString(flagWebsocket)
|
||||||
ws := newWebsocketsServer(rs.CliCtx, websocketAddr)
|
ws := websockets.NewServer(rs.CliCtx, websocketAddr)
|
||||||
ws.start()
|
ws.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
func unlockKeyFromNameAndPassphrase(accountNames []string, passphrase string) ([]ethsecp256k1.PrivKey, error) {
|
func unlockKeyFromNameAndPassphrase(accountNames []string, passphrase string) ([]ethsecp256k1.PrivKey, error) {
|
||||||
|
10
rpc/doc.go
Normal file
10
rpc/doc.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// Package rpc contains RPC handler methods, namespaces and utilities to start
|
||||||
|
// Ethermint's Web3-compatible JSON-RPC server.
|
||||||
|
//
|
||||||
|
// The list of available namespaces are:
|
||||||
|
//
|
||||||
|
// * `rpc/namespaces/eth`: `eth` namespace. Exposes the `PublicEthereumAPI` and the `PublicFilterAPI`.
|
||||||
|
// * `rpc/namespaces/personal`: `personal` namespace. Exposes the `PrivateAccountAPI`.
|
||||||
|
// * `rpc/namespaces/net`: `net` namespace. Exposes the `PublicNetAPI`.
|
||||||
|
// * `rpc/namespaces/web3`: `web3` namespace. Exposes the `PublicWeb3API`
|
||||||
|
package rpc
|
1070
rpc/eth_api.go
1070
rpc/eth_api.go
File diff suppressed because it is too large
Load Diff
890
rpc/namespaces/eth/api.go
Normal file
890
rpc/namespaces/eth/api.go
Normal file
@ -0,0 +1,890 @@
|
|||||||
|
package eth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
|
"github.com/cosmos/ethermint/crypto/ethsecp256k1"
|
||||||
|
"github.com/cosmos/ethermint/crypto/hd"
|
||||||
|
"github.com/cosmos/ethermint/rpc/backend"
|
||||||
|
rpctypes "github.com/cosmos/ethermint/rpc/types"
|
||||||
|
ethermint "github.com/cosmos/ethermint/types"
|
||||||
|
"github.com/cosmos/ethermint/utils"
|
||||||
|
"github.com/cosmos/ethermint/version"
|
||||||
|
evmtypes "github.com/cosmos/ethermint/x/evm/types"
|
||||||
|
|
||||||
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
|
"github.com/tendermint/tendermint/crypto/merkle"
|
||||||
|
"github.com/tendermint/tendermint/libs/log"
|
||||||
|
"github.com/tendermint/tendermint/rpc/client"
|
||||||
|
tmtypes "github.com/tendermint/tendermint/types"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
|
||||||
|
clientcontext "github.com/cosmos/cosmos-sdk/client/context"
|
||||||
|
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||||
|
"github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||||
|
authclient "github.com/cosmos/cosmos-sdk/x/auth/client/utils"
|
||||||
|
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PublicEthereumAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec.
|
||||||
|
type PublicEthereumAPI struct {
|
||||||
|
ctx context.Context
|
||||||
|
clientCtx clientcontext.CLIContext
|
||||||
|
chainIDEpoch *big.Int
|
||||||
|
logger log.Logger
|
||||||
|
backend backend.Backend
|
||||||
|
keys []ethsecp256k1.PrivKey // unlocked keys
|
||||||
|
nonceLock *rpctypes.AddrLocker
|
||||||
|
keyringLock sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAPI creates an instance of the public ETH Web3 API.
|
||||||
|
func NewAPI(
|
||||||
|
clientCtx clientcontext.CLIContext, backend backend.Backend, nonceLock *rpctypes.AddrLocker,
|
||||||
|
keys ...ethsecp256k1.PrivKey,
|
||||||
|
) *PublicEthereumAPI {
|
||||||
|
|
||||||
|
epoch, err := ethermint.ParseChainID(clientCtx.ChainID)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
api := &PublicEthereumAPI{
|
||||||
|
ctx: context.Background(),
|
||||||
|
clientCtx: clientCtx,
|
||||||
|
chainIDEpoch: epoch,
|
||||||
|
logger: log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "json-rpc", "namespace", "eth"),
|
||||||
|
backend: backend,
|
||||||
|
keys: keys,
|
||||||
|
nonceLock: nonceLock,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := api.GetKeyringInfo(); err != nil {
|
||||||
|
api.logger.Error("failed to get keybase info", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return api
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetKeyringInfo checks if the keyring is present on the client context. If not, it creates a new
|
||||||
|
// instance and sets it to the client context for later usage.
|
||||||
|
func (api *PublicEthereumAPI) GetKeyringInfo() error {
|
||||||
|
api.keyringLock.Lock()
|
||||||
|
defer api.keyringLock.Unlock()
|
||||||
|
|
||||||
|
if api.clientCtx.Keybase != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
keybase, err := keys.NewKeyring(
|
||||||
|
sdk.KeyringServiceName(),
|
||||||
|
viper.GetString(flags.FlagKeyringBackend),
|
||||||
|
viper.GetString(flags.FlagHome),
|
||||||
|
api.clientCtx.Input,
|
||||||
|
hd.EthSecp256k1Options()...,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
api.clientCtx.Keybase = keybase
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientCtx returns the Cosmos SDK client context.
|
||||||
|
func (api *PublicEthereumAPI) ClientCtx() clientcontext.CLIContext {
|
||||||
|
return api.clientCtx
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetKeys returns the Cosmos SDK client context.
|
||||||
|
func (api *PublicEthereumAPI) GetKeys() []ethsecp256k1.PrivKey {
|
||||||
|
return api.keys
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetKeys sets the given key slice to the set of private keys
|
||||||
|
func (api *PublicEthereumAPI) SetKeys(keys []ethsecp256k1.PrivKey) {
|
||||||
|
api.keys = keys
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProtocolVersion returns the supported Ethereum protocol version.
|
||||||
|
func (api *PublicEthereumAPI) ProtocolVersion() hexutil.Uint {
|
||||||
|
api.logger.Debug("eth_protocolVersion")
|
||||||
|
return hexutil.Uint(version.ProtocolVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChainId returns the chain's identifier in hex format
|
||||||
|
func (api *PublicEthereumAPI) ChainId() (hexutil.Uint, error) { // nolint
|
||||||
|
api.logger.Debug("eth_chainId")
|
||||||
|
return hexutil.Uint(uint(api.chainIDEpoch.Uint64())), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Syncing returns whether or not the current node is syncing with other peers. Returns false if not, or a struct
|
||||||
|
// outlining the state of the sync if it is.
|
||||||
|
func (api *PublicEthereumAPI) Syncing() (interface{}, error) {
|
||||||
|
api.logger.Debug("eth_syncing")
|
||||||
|
|
||||||
|
status, err := api.clientCtx.Client.Status()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !status.SyncInfo.CatchingUp {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]interface{}{
|
||||||
|
// "startingBlock": nil, // NA
|
||||||
|
"currentBlock": hexutil.Uint64(status.SyncInfo.LatestBlockHeight),
|
||||||
|
// "highestBlock": nil, // NA
|
||||||
|
// "pulledStates": nil, // NA
|
||||||
|
// "knownStates": nil, // NA
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Coinbase is the address that staking rewards will be send to (alias for Etherbase).
|
||||||
|
func (api *PublicEthereumAPI) Coinbase() (common.Address, error) {
|
||||||
|
api.logger.Debug("eth_coinbase")
|
||||||
|
|
||||||
|
node, err := api.clientCtx.GetNode()
|
||||||
|
if err != nil {
|
||||||
|
return common.Address{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
status, err := node.Status()
|
||||||
|
if err != nil {
|
||||||
|
return common.Address{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return common.BytesToAddress(status.ValidatorInfo.Address.Bytes()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mining returns whether or not this node is currently mining. Always false.
|
||||||
|
func (api *PublicEthereumAPI) Mining() bool {
|
||||||
|
api.logger.Debug("eth_mining")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hashrate returns the current node's hashrate. Always 0.
|
||||||
|
func (api *PublicEthereumAPI) Hashrate() hexutil.Uint64 {
|
||||||
|
api.logger.Debug("eth_hashrate")
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GasPrice returns the current gas price based on Ethermint's gas price oracle.
|
||||||
|
func (api *PublicEthereumAPI) GasPrice() *hexutil.Big {
|
||||||
|
api.logger.Debug("eth_gasPrice")
|
||||||
|
out := big.NewInt(0)
|
||||||
|
return (*hexutil.Big)(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accounts returns the list of accounts available to this node.
|
||||||
|
func (api *PublicEthereumAPI) Accounts() ([]common.Address, error) {
|
||||||
|
api.logger.Debug("eth_accounts")
|
||||||
|
api.keyringLock.Lock()
|
||||||
|
|
||||||
|
addresses := make([]common.Address, 0) // return [] instead of nil if empty
|
||||||
|
|
||||||
|
infos, err := api.clientCtx.Keybase.List()
|
||||||
|
if err != nil {
|
||||||
|
return addresses, err
|
||||||
|
}
|
||||||
|
|
||||||
|
api.keyringLock.Unlock()
|
||||||
|
|
||||||
|
for _, info := range infos {
|
||||||
|
addressBytes := info.GetPubKey().Address().Bytes()
|
||||||
|
addresses = append(addresses, common.BytesToAddress(addressBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
return addresses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// rpctypes.BlockNumber returns the current block number.
|
||||||
|
func (api *PublicEthereumAPI) BlockNumber() (hexutil.Uint64, error) {
|
||||||
|
api.logger.Debug("eth_blockNumber")
|
||||||
|
return api.backend.BlockNumber()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBalance returns the provided account's balance up to the provided block number.
|
||||||
|
func (api *PublicEthereumAPI) GetBalance(address common.Address, blockNum rpctypes.BlockNumber) (*hexutil.Big, error) {
|
||||||
|
api.logger.Debug("eth_getBalance", "address", address, "block number", blockNum)
|
||||||
|
clientCtx := api.clientCtx.WithHeight(blockNum.Int64())
|
||||||
|
res, _, err := clientCtx.QueryWithData(fmt.Sprintf("custom/%s/balance/%s", evmtypes.ModuleName, address.Hex()), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var out evmtypes.QueryResBalance
|
||||||
|
api.clientCtx.Codec.MustUnmarshalJSON(res, &out)
|
||||||
|
val, err := utils.UnmarshalBigInt(out.Balance)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return (*hexutil.Big)(val), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStorageAt returns the contract storage at the given address, block number, and key.
|
||||||
|
func (api *PublicEthereumAPI) GetStorageAt(address common.Address, key string, blockNum rpctypes.BlockNumber) (hexutil.Bytes, error) {
|
||||||
|
api.logger.Debug("eth_getStorageAt", "address", address, "key", key, "block number", blockNum)
|
||||||
|
clientCtx := api.clientCtx.WithHeight(blockNum.Int64())
|
||||||
|
res, _, err := clientCtx.QueryWithData(fmt.Sprintf("custom/%s/storage/%s/%s", evmtypes.ModuleName, address.Hex(), key), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var out evmtypes.QueryResStorage
|
||||||
|
api.clientCtx.Codec.MustUnmarshalJSON(res, &out)
|
||||||
|
return out.Value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTransactionCount returns the number of transactions at the given address up to the given block number.
|
||||||
|
func (api *PublicEthereumAPI) GetTransactionCount(address common.Address, blockNum rpctypes.BlockNumber) (*hexutil.Uint64, error) {
|
||||||
|
api.logger.Debug("eth_getTransactionCount", "address", address, "block number", blockNum)
|
||||||
|
clientCtx := api.clientCtx.WithHeight(blockNum.Int64())
|
||||||
|
|
||||||
|
// Get nonce (sequence) from account
|
||||||
|
from := sdk.AccAddress(address.Bytes())
|
||||||
|
accRet := authtypes.NewAccountRetriever(clientCtx)
|
||||||
|
|
||||||
|
err := accRet.EnsureExists(from)
|
||||||
|
if err != nil {
|
||||||
|
// account doesn't exist yet, return 0
|
||||||
|
n := hexutil.Uint64(0)
|
||||||
|
return &n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, nonce, err := accRet.GetAccountNumberSequence(from)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
n := hexutil.Uint64(nonce)
|
||||||
|
return &n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockTransactionCountByHash returns the number of transactions in the block identified by hash.
|
||||||
|
func (api *PublicEthereumAPI) GetBlockTransactionCountByHash(hash common.Hash) *hexutil.Uint {
|
||||||
|
api.logger.Debug("eth_getBlockTransactionCountByHash", "hash", hash)
|
||||||
|
res, _, err := api.clientCtx.Query(fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryHashToHeight, hash.Hex()))
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var out evmtypes.QueryResBlockNumber
|
||||||
|
if err := api.clientCtx.Codec.UnmarshalJSON(res, &out); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
resBlock, err := api.clientCtx.Client.Block(&out.Number)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
n := hexutil.Uint(len(resBlock.Block.Txs))
|
||||||
|
return &n
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockTransactionCountByNumber returns the number of transactions in the block identified by number.
|
||||||
|
func (api *PublicEthereumAPI) GetBlockTransactionCountByNumber(blockNum rpctypes.BlockNumber) *hexutil.Uint {
|
||||||
|
api.logger.Debug("eth_getBlockTransactionCountByNumber", "block number", blockNum)
|
||||||
|
height := blockNum.Int64()
|
||||||
|
resBlock, err := api.clientCtx.Client.Block(&height)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
n := hexutil.Uint(len(resBlock.Block.Txs))
|
||||||
|
return &n
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUncleCountByBlockHash returns the number of uncles in the block idenfied by hash. Always zero.
|
||||||
|
func (api *PublicEthereumAPI) GetUncleCountByBlockHash(_ common.Hash) hexutil.Uint {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUncleCountByBlockNumber returns the number of uncles in the block idenfied by number. Always zero.
|
||||||
|
func (api *PublicEthereumAPI) GetUncleCountByBlockNumber(_ rpctypes.BlockNumber) hexutil.Uint {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCode returns the contract code at the given address and block number.
|
||||||
|
func (api *PublicEthereumAPI) GetCode(address common.Address, blockNumber rpctypes.BlockNumber) (hexutil.Bytes, error) {
|
||||||
|
api.logger.Debug("eth_getCode", "address", address, "block number", blockNumber)
|
||||||
|
clientCtx := api.clientCtx.WithHeight(blockNumber.Int64())
|
||||||
|
res, _, err := clientCtx.QueryWithData(fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryCode, address.Hex()), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var out evmtypes.QueryResCode
|
||||||
|
api.clientCtx.Codec.MustUnmarshalJSON(res, &out)
|
||||||
|
return out.Code, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTransactionLogs returns the logs given a transaction hash.
|
||||||
|
func (api *PublicEthereumAPI) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error) {
|
||||||
|
api.logger.Debug("eth_getTransactionLogs", "hash", txHash)
|
||||||
|
return api.backend.GetTransactionLogs(txHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign signs the provided data using the private key of address via Geth's signature standard.
|
||||||
|
func (api *PublicEthereumAPI) Sign(address common.Address, data hexutil.Bytes) (hexutil.Bytes, error) {
|
||||||
|
api.logger.Debug("eth_sign", "address", address, "data", data)
|
||||||
|
// TODO: Change this functionality to find an unlocked account by address
|
||||||
|
|
||||||
|
key, exist := rpctypes.GetKeyByAddress(api.keys, address)
|
||||||
|
if !exist {
|
||||||
|
return nil, keystore.ErrLocked
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign the requested hash with the wallet
|
||||||
|
signature, err := key.Sign(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
signature[64] += 27 // Transform V from 0/1 to 27/28 according to the yellow paper
|
||||||
|
return signature, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendTransaction sends an Ethereum transaction.
|
||||||
|
func (api *PublicEthereumAPI) SendTransaction(args rpctypes.SendTxArgs) (common.Hash, error) {
|
||||||
|
api.logger.Debug("eth_sendTransaction", "args", args)
|
||||||
|
// TODO: Change this functionality to find an unlocked account by address
|
||||||
|
|
||||||
|
key, exist := rpctypes.GetKeyByAddress(api.keys, args.From)
|
||||||
|
if !exist {
|
||||||
|
api.logger.Debug("failed to find key in keyring", "key", args.From)
|
||||||
|
return common.Hash{}, keystore.ErrLocked
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mutex lock the address' nonce to avoid assigning it to multiple requests
|
||||||
|
if args.Nonce == nil {
|
||||||
|
api.nonceLock.LockAddr(args.From)
|
||||||
|
defer api.nonceLock.UnlockAddr(args.From)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assemble transaction from fields
|
||||||
|
tx, err := api.generateFromArgs(args)
|
||||||
|
if err != nil {
|
||||||
|
api.logger.Debug("failed to generate tx", "error", err)
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign transaction
|
||||||
|
if err := tx.Sign(api.chainIDEpoch, key.ToECDSA()); err != nil {
|
||||||
|
api.logger.Debug("failed to sign tx", "error", err)
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode transaction by default Tx encoder
|
||||||
|
txEncoder := authclient.GetTxEncoder(api.clientCtx.Codec)
|
||||||
|
txBytes, err := txEncoder(tx)
|
||||||
|
if err != nil {
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Broadcast transaction in sync mode (default)
|
||||||
|
// NOTE: If error is encountered on the node, the broadcast will not return an error
|
||||||
|
res, err := api.clientCtx.BroadcastTx(txBytes)
|
||||||
|
if err != nil {
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return transaction hash
|
||||||
|
return common.HexToHash(res.TxHash), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendRawTransaction send a raw Ethereum transaction.
|
||||||
|
func (api *PublicEthereumAPI) SendRawTransaction(data hexutil.Bytes) (common.Hash, error) {
|
||||||
|
api.logger.Debug("eth_sendRawTransaction", "data", data)
|
||||||
|
tx := new(evmtypes.MsgEthereumTx)
|
||||||
|
|
||||||
|
// RLP decode raw transaction bytes
|
||||||
|
if err := rlp.DecodeBytes(data, tx); err != nil {
|
||||||
|
// Return nil is for when gasLimit overflows uint64
|
||||||
|
return common.Hash{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode transaction by default Tx encoder
|
||||||
|
txEncoder := authclient.GetTxEncoder(api.clientCtx.Codec)
|
||||||
|
txBytes, err := txEncoder(tx)
|
||||||
|
if err != nil {
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Possibly log the contract creation address (if recipient address is nil) or tx data
|
||||||
|
// If error is encountered on the node, the broadcast will not return an error
|
||||||
|
res, err := api.clientCtx.BroadcastTx(txBytes)
|
||||||
|
if err != nil {
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return transaction hash
|
||||||
|
return common.HexToHash(res.TxHash), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call performs a raw contract call.
|
||||||
|
func (api *PublicEthereumAPI) Call(args rpctypes.CallArgs, blockNr rpctypes.BlockNumber, _ *map[common.Address]rpctypes.Account) (hexutil.Bytes, error) {
|
||||||
|
api.logger.Debug("eth_call", "args", args, "block number", blockNr)
|
||||||
|
simRes, err := api.doCall(args, blockNr, big.NewInt(ethermint.DefaultRPCGasLimit))
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := evmtypes.DecodeResultData(simRes.Result.Data)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return (hexutil.Bytes)(data.Ret), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DoCall performs a simulated call operation through the evmtypes. It returns the
|
||||||
|
// estimated gas used on the operation or an error if fails.
|
||||||
|
func (api *PublicEthereumAPI) doCall(
|
||||||
|
args rpctypes.CallArgs, blockNr rpctypes.BlockNumber, globalGasCap *big.Int,
|
||||||
|
) (*sdk.SimulationResponse, error) {
|
||||||
|
// Set height for historical queries
|
||||||
|
clientCtx := api.clientCtx
|
||||||
|
|
||||||
|
if blockNr.Int64() != 0 {
|
||||||
|
clientCtx = api.clientCtx.WithHeight(blockNr.Int64())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sender address or use a default if none specified
|
||||||
|
var addr common.Address
|
||||||
|
|
||||||
|
if args.From == nil {
|
||||||
|
addrs, err := api.Accounts()
|
||||||
|
if err == nil && len(addrs) > 0 {
|
||||||
|
addr = addrs[0]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addr = *args.From
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set default gas & gas price if none were set
|
||||||
|
// Change this to uint64(math.MaxUint64 / 2) if gas cap can be configured
|
||||||
|
gas := uint64(ethermint.DefaultRPCGasLimit)
|
||||||
|
if args.Gas != nil {
|
||||||
|
gas = uint64(*args.Gas)
|
||||||
|
}
|
||||||
|
if globalGasCap != nil && globalGasCap.Uint64() < gas {
|
||||||
|
api.logger.Debug("Caller gas above allowance, capping", "requested", gas, "cap", globalGasCap)
|
||||||
|
gas = globalGasCap.Uint64()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set gas price using default or parameter if passed in
|
||||||
|
gasPrice := new(big.Int).SetUint64(ethermint.DefaultGasPrice)
|
||||||
|
if args.GasPrice != nil {
|
||||||
|
gasPrice = args.GasPrice.ToInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set value for transaction
|
||||||
|
value := new(big.Int)
|
||||||
|
if args.Value != nil {
|
||||||
|
value = args.Value.ToInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set Data if provided
|
||||||
|
var data []byte
|
||||||
|
if args.Data != nil {
|
||||||
|
data = []byte(*args.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set destination address for call
|
||||||
|
var toAddr sdk.AccAddress
|
||||||
|
if args.To != nil {
|
||||||
|
toAddr = sdk.AccAddress(args.To.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new call message
|
||||||
|
msg := evmtypes.NewMsgEthermint(0, &toAddr, sdk.NewIntFromBigInt(value), gas,
|
||||||
|
sdk.NewIntFromBigInt(gasPrice), data, sdk.AccAddress(addr.Bytes()))
|
||||||
|
|
||||||
|
if err := msg.ValidateBasic(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate tx to be used to simulate (signature isn't needed)
|
||||||
|
var stdSig authtypes.StdSignature
|
||||||
|
tx := authtypes.NewStdTx([]sdk.Msg{msg}, authtypes.StdFee{}, []authtypes.StdSignature{stdSig}, "")
|
||||||
|
|
||||||
|
txEncoder := authclient.GetTxEncoder(clientCtx.Codec)
|
||||||
|
txBytes, err := txEncoder(tx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transaction simulation through query
|
||||||
|
res, _, err := clientCtx.QueryWithData("app/simulate", txBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var simResponse sdk.SimulationResponse
|
||||||
|
if err := clientCtx.Codec.UnmarshalBinaryBare(res, &simResponse); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &simResponse, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EstimateGas returns an estimate of gas usage for the given smart contract call.
|
||||||
|
// It adds 1,000 gas to the returned value instead of using the gas adjustment
|
||||||
|
// param from the SDK.
|
||||||
|
func (api *PublicEthereumAPI) EstimateGas(args rpctypes.CallArgs) (hexutil.Uint64, error) {
|
||||||
|
api.logger.Debug("eth_estimateGas", "args", args)
|
||||||
|
simResponse, err := api.doCall(args, 0, big.NewInt(ethermint.DefaultRPCGasLimit))
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: change 1000 buffer for more accurate buffer (eg: SDK's gasAdjusted)
|
||||||
|
estimatedGas := simResponse.GasInfo.GasUsed
|
||||||
|
gas := estimatedGas + 1000
|
||||||
|
|
||||||
|
return hexutil.Uint64(gas), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockByHash returns the block identified by hash.
|
||||||
|
func (api *PublicEthereumAPI) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) {
|
||||||
|
api.logger.Debug("eth_getBlockByHash", "hash", hash, "full", fullTx)
|
||||||
|
return api.backend.GetBlockByHash(hash, fullTx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockByNumber returns the block identified by number.
|
||||||
|
func (api *PublicEthereumAPI) GetBlockByNumber(blockNum rpctypes.BlockNumber, fullTx bool) (map[string]interface{}, error) {
|
||||||
|
api.logger.Debug("eth_getBlockByNumber", "number", blockNum, "full", fullTx)
|
||||||
|
return api.backend.GetBlockByNumber(blockNum, fullTx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTransactionByHash returns the transaction identified by hash.
|
||||||
|
func (api *PublicEthereumAPI) GetTransactionByHash(hash common.Hash) (*rpctypes.Transaction, error) {
|
||||||
|
api.logger.Debug("eth_getTransactionByHash", "hash", hash)
|
||||||
|
tx, err := api.clientCtx.Client.Tx(hash.Bytes(), false)
|
||||||
|
if err != nil {
|
||||||
|
// Return nil for transaction when not found
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can either cache or just leave this out if not necessary
|
||||||
|
block, err := api.clientCtx.Client.Block(&tx.Height)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
blockHash := common.BytesToHash(block.Block.Header.Hash())
|
||||||
|
|
||||||
|
ethTx, err := rpctypes.RawTxToEthTx(api.clientCtx, tx.Tx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
height := uint64(tx.Height)
|
||||||
|
return rpctypes.NewTransaction(ethTx, common.BytesToHash(tx.Tx.Hash()), blockHash, height, uint64(tx.Index))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTransactionByBlockHashAndIndex returns the transaction identified by hash and index.
|
||||||
|
func (api *PublicEthereumAPI) GetTransactionByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) (*rpctypes.Transaction, error) {
|
||||||
|
api.logger.Debug("eth_getTransactionByHashAndIndex", "hash", hash, "index", idx)
|
||||||
|
res, _, err := api.clientCtx.Query(fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryHashToHeight, hash.Hex()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var out evmtypes.QueryResBlockNumber
|
||||||
|
api.clientCtx.Codec.MustUnmarshalJSON(res, &out)
|
||||||
|
|
||||||
|
resBlock, err := api.clientCtx.Client.Block(&out.Number)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return api.getTransactionByBlockAndIndex(resBlock.Block, idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTransactionByBlockNumberAndIndex returns the transaction identified by number and index.
|
||||||
|
func (api *PublicEthereumAPI) GetTransactionByBlockNumberAndIndex(blockNum rpctypes.BlockNumber, idx hexutil.Uint) (*rpctypes.Transaction, error) {
|
||||||
|
api.logger.Debug("eth_getTransactionByBlockNumberAndIndex", "number", blockNum, "index", idx)
|
||||||
|
height := blockNum.Int64()
|
||||||
|
resBlock, err := api.clientCtx.Client.Block(&height)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return api.getTransactionByBlockAndIndex(resBlock.Block, idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *PublicEthereumAPI) getTransactionByBlockAndIndex(block *tmtypes.Block, idx hexutil.Uint) (*rpctypes.Transaction, error) {
|
||||||
|
// return if index out of bounds
|
||||||
|
if uint64(idx) >= uint64(len(block.Txs)) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ethTx, err := rpctypes.RawTxToEthTx(api.clientCtx, block.Txs[idx])
|
||||||
|
if err != nil {
|
||||||
|
// return nil error if the transaction is not a MsgEthereumTx
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
height := uint64(block.Height)
|
||||||
|
txHash := common.BytesToHash(block.Txs[idx].Hash())
|
||||||
|
blockHash := common.BytesToHash(block.Header.Hash())
|
||||||
|
return rpctypes.NewTransaction(ethTx, txHash, blockHash, height, uint64(idx))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTransactionReceipt returns the transaction receipt identified by hash.
|
||||||
|
func (api *PublicEthereumAPI) GetTransactionReceipt(hash common.Hash) (map[string]interface{}, error) {
|
||||||
|
api.logger.Debug("eth_getTransactionReceipt", "hash", hash)
|
||||||
|
tx, err := api.clientCtx.Client.Tx(hash.Bytes(), false)
|
||||||
|
if err != nil {
|
||||||
|
// Return nil for transaction when not found
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query block for consensus hash
|
||||||
|
block, err := api.clientCtx.Client.Block(&tx.Height)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
blockHash := common.BytesToHash(block.Block.Header.Hash())
|
||||||
|
|
||||||
|
// Convert tx bytes to eth transaction
|
||||||
|
ethTx, err := rpctypes.RawTxToEthTx(api.clientCtx, tx.Tx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
from, err := ethTx.VerifySig(ethTx.ChainID())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set status codes based on tx result
|
||||||
|
var status hexutil.Uint
|
||||||
|
if tx.TxResult.IsOK() {
|
||||||
|
status = hexutil.Uint(1)
|
||||||
|
} else {
|
||||||
|
status = hexutil.Uint(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
txData := tx.TxResult.GetData()
|
||||||
|
|
||||||
|
data, err := evmtypes.DecodeResultData(txData)
|
||||||
|
if err != nil {
|
||||||
|
status = 0 // transaction failed
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data.Logs) == 0 {
|
||||||
|
data.Logs = []*ethtypes.Log{}
|
||||||
|
}
|
||||||
|
|
||||||
|
receipt := map[string]interface{}{
|
||||||
|
// Consensus fields: These fields are defined by the Yellow Paper
|
||||||
|
"status": status,
|
||||||
|
"cumulativeGasUsed": nil, // ignore until needed
|
||||||
|
"logsBloom": data.Bloom,
|
||||||
|
"logs": data.Logs,
|
||||||
|
|
||||||
|
// Implementation fields: These fields are added by geth when processing a transaction.
|
||||||
|
// They are stored in the chain database.
|
||||||
|
"transactionHash": hash,
|
||||||
|
"contractAddress": data.ContractAddress,
|
||||||
|
"gasUsed": hexutil.Uint64(tx.TxResult.GasUsed),
|
||||||
|
|
||||||
|
// Inclusion information: These fields provide information about the inclusion of the
|
||||||
|
// transaction corresponding to this receipt.
|
||||||
|
"blockHash": blockHash,
|
||||||
|
"blockNumber": hexutil.Uint64(tx.Height),
|
||||||
|
"transactionIndex": hexutil.Uint64(tx.Index),
|
||||||
|
|
||||||
|
// sender and receiver (contract or EOA) addreses
|
||||||
|
"from": from,
|
||||||
|
"to": ethTx.To(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return receipt, 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 (api *PublicEthereumAPI) PendingTransactions() ([]*rpctypes.Transaction, error) {
|
||||||
|
api.logger.Debug("eth_getPendingTransactions")
|
||||||
|
return api.backend.PendingTransactions()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUncleByBlockHashAndIndex returns the uncle identified by hash and index. Always returns nil.
|
||||||
|
func (api *PublicEthereumAPI) GetUncleByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) map[string]interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUncleByBlockNumberAndIndex returns the uncle identified by number and index. Always returns nil.
|
||||||
|
func (api *PublicEthereumAPI) GetUncleByBlockNumberAndIndex(number hexutil.Uint, idx hexutil.Uint) map[string]interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetProof returns an account object with proof and any storage proofs
|
||||||
|
func (api *PublicEthereumAPI) GetProof(address common.Address, storageKeys []string, block rpctypes.BlockNumber) (*rpctypes.AccountResult, error) {
|
||||||
|
api.logger.Debug("eth_getProof", "address", address, "keys", storageKeys, "number", block)
|
||||||
|
|
||||||
|
clientCtx := api.clientCtx.WithHeight(int64(block))
|
||||||
|
path := fmt.Sprintf("custom/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryAccount, address.Hex())
|
||||||
|
|
||||||
|
// query eth account at block height
|
||||||
|
resBz, _, err := clientCtx.Query(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var account evmtypes.QueryResAccount
|
||||||
|
clientCtx.Codec.MustUnmarshalJSON(resBz, &account)
|
||||||
|
|
||||||
|
storageProofs := make([]rpctypes.StorageResult, len(storageKeys))
|
||||||
|
opts := client.ABCIQueryOptions{Height: int64(block), Prove: true}
|
||||||
|
for i, k := range storageKeys {
|
||||||
|
// Get value for key
|
||||||
|
vPath := fmt.Sprintf("custom/%s/%s/%s/%s", evmtypes.ModuleName, evmtypes.QueryStorage, address, k)
|
||||||
|
vRes, err := api.clientCtx.Client.ABCIQueryWithOptions(vPath, nil, opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var value evmtypes.QueryResStorage
|
||||||
|
clientCtx.Codec.MustUnmarshalJSON(vRes.Response.GetValue(), &value)
|
||||||
|
|
||||||
|
// check for proof
|
||||||
|
proof := vRes.Response.GetProof()
|
||||||
|
proofStr := new(merkle.Proof).String()
|
||||||
|
if proof != nil {
|
||||||
|
proofStr = proof.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
storageProofs[i] = rpctypes.StorageResult{
|
||||||
|
Key: k,
|
||||||
|
Value: (*hexutil.Big)(common.BytesToHash(value.Value).Big()),
|
||||||
|
Proof: []string{proofStr},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req := abci.RequestQuery{
|
||||||
|
Path: fmt.Sprintf("store/%s/key", auth.StoreKey),
|
||||||
|
Data: auth.AddressStoreKey(sdk.AccAddress(address.Bytes())),
|
||||||
|
Height: int64(block),
|
||||||
|
Prove: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := clientCtx.QueryABCI(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for proof
|
||||||
|
accountProof := res.GetProof()
|
||||||
|
accProofStr := new(merkle.Proof).String()
|
||||||
|
if accountProof != nil {
|
||||||
|
accProofStr = accountProof.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return &rpctypes.AccountResult{
|
||||||
|
Address: address,
|
||||||
|
AccountProof: []string{accProofStr},
|
||||||
|
Balance: (*hexutil.Big)(utils.MustUnmarshalBigInt(account.Balance)),
|
||||||
|
CodeHash: common.BytesToHash(account.CodeHash),
|
||||||
|
Nonce: hexutil.Uint64(account.Nonce),
|
||||||
|
StorageHash: common.Hash{}, // Ethermint doesn't have a storage hash
|
||||||
|
StorageProof: storageProofs,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateFromArgs populates tx message with args (used in RPC API)
|
||||||
|
func (api *PublicEthereumAPI) generateFromArgs(args rpctypes.SendTxArgs) (*evmtypes.MsgEthereumTx, error) {
|
||||||
|
var (
|
||||||
|
nonce uint64
|
||||||
|
gasLimit uint64
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
amount := (*big.Int)(args.Value)
|
||||||
|
gasPrice := (*big.Int)(args.GasPrice)
|
||||||
|
|
||||||
|
if args.GasPrice == nil {
|
||||||
|
|
||||||
|
// Set default gas price
|
||||||
|
// TODO: Change to min gas price from context once available through server/daemon
|
||||||
|
gasPrice = big.NewInt(ethermint.DefaultGasPrice)
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.Nonce == nil {
|
||||||
|
// Get nonce (sequence) from account
|
||||||
|
from := sdk.AccAddress(args.From.Bytes())
|
||||||
|
accRet := authtypes.NewAccountRetriever(api.clientCtx)
|
||||||
|
|
||||||
|
if api.clientCtx.Keybase == nil {
|
||||||
|
return nil, fmt.Errorf("clientCtx.Keybase is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, nonce, err = accRet.GetAccountNumberSequence(from)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nonce = (uint64)(*args.Nonce)
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.Data != nil && args.Input != nil && !bytes.Equal(*args.Data, *args.Input) {
|
||||||
|
return nil, errors.New(`both "data" and "input" are set and not equal. Please use "input" to pass transaction call data`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets input to either Input or Data, if both are set and not equal error above returns
|
||||||
|
var input []byte
|
||||||
|
if args.Input != nil {
|
||||||
|
input = *args.Input
|
||||||
|
} else if args.Data != nil {
|
||||||
|
input = *args.Data
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.To == nil && len(input) == 0 {
|
||||||
|
// Contract creation
|
||||||
|
return nil, fmt.Errorf("contract creation without any data provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
if args.Gas == nil {
|
||||||
|
callArgs := rpctypes.CallArgs{
|
||||||
|
From: &args.From,
|
||||||
|
To: args.To,
|
||||||
|
Gas: args.Gas,
|
||||||
|
GasPrice: args.GasPrice,
|
||||||
|
Value: args.Value,
|
||||||
|
Data: args.Data,
|
||||||
|
}
|
||||||
|
gl, err := api.EstimateGas(callArgs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
gasLimit = uint64(gl)
|
||||||
|
} else {
|
||||||
|
gasLimit = (uint64)(*args.Gas)
|
||||||
|
}
|
||||||
|
msg := evmtypes.NewMsgEthereumTx(nonce, args.To, amount, gasLimit, gasPrice, input)
|
||||||
|
|
||||||
|
return &msg, nil
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package rpc
|
package filters
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -16,13 +16,14 @@ import (
|
|||||||
|
|
||||||
clientcontext "github.com/cosmos/cosmos-sdk/client/context"
|
clientcontext "github.com/cosmos/cosmos-sdk/client/context"
|
||||||
|
|
||||||
|
rpctypes "github.com/cosmos/ethermint/rpc/types"
|
||||||
evmtypes "github.com/cosmos/ethermint/x/evm/types"
|
evmtypes "github.com/cosmos/ethermint/x/evm/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FiltersBackend defines the methods requided by the PublicFilterAPI backend
|
// Backend defines the methods requided by the PublicFilterAPI backend
|
||||||
type FiltersBackend interface {
|
type Backend interface {
|
||||||
GetBlockByNumber(blockNum BlockNumber, fullTx bool) (map[string]interface{}, error)
|
GetBlockByNumber(blockNum rpctypes.BlockNumber, fullTx bool) (map[string]interface{}, error)
|
||||||
HeaderByNumber(blockNr BlockNumber) (*ethtypes.Header, error)
|
HeaderByNumber(blockNr rpctypes.BlockNumber) (*ethtypes.Header, error)
|
||||||
HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error)
|
HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error)
|
||||||
GetLogs(blockHash common.Hash) ([][]*ethtypes.Log, error)
|
GetLogs(blockHash common.Hash) ([][]*ethtypes.Log, error)
|
||||||
|
|
||||||
@ -47,26 +48,26 @@ type filter struct {
|
|||||||
// PublicFilterAPI offers support to create and manage filters. This will allow external clients to retrieve various
|
// PublicFilterAPI offers support to create and manage filters. This will allow external clients to retrieve various
|
||||||
// information related to the Ethereum protocol such as blocks, transactions and logs.
|
// information related to the Ethereum protocol such as blocks, transactions and logs.
|
||||||
type PublicFilterAPI struct {
|
type PublicFilterAPI struct {
|
||||||
cliCtx clientcontext.CLIContext
|
clientCtx clientcontext.CLIContext
|
||||||
backend FiltersBackend
|
backend Backend
|
||||||
events *EventSystem
|
events *EventSystem
|
||||||
filtersMu sync.Mutex
|
filtersMu sync.Mutex
|
||||||
filters map[rpc.ID]*filter
|
filters map[rpc.ID]*filter
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPublicFilterAPI returns a new PublicFilterAPI instance.
|
// NewAPI returns a new PublicFilterAPI instance.
|
||||||
func NewPublicFilterAPI(cliCtx clientcontext.CLIContext, backend FiltersBackend) *PublicFilterAPI {
|
func NewAPI(clientCtx clientcontext.CLIContext, backend Backend) *PublicFilterAPI {
|
||||||
// start the client to subscribe to Tendermint events
|
// start the client to subscribe to Tendermint events
|
||||||
err := cliCtx.Client.Start()
|
err := clientCtx.Client.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
api := &PublicFilterAPI{
|
api := &PublicFilterAPI{
|
||||||
cliCtx: cliCtx,
|
clientCtx: clientCtx,
|
||||||
backend: backend,
|
backend: backend,
|
||||||
filters: make(map[rpc.ID]*filter),
|
filters: make(map[rpc.ID]*filter),
|
||||||
events: NewEventSystem(cliCtx.Client),
|
events: NewEventSystem(clientCtx.Client),
|
||||||
}
|
}
|
||||||
|
|
||||||
go api.timeoutLoop()
|
go api.timeoutLoop()
|
||||||
@ -209,7 +210,7 @@ func (api *PublicFilterAPI) NewBlockFilter() rpc.ID {
|
|||||||
select {
|
select {
|
||||||
case ev := <-headersCh:
|
case ev := <-headersCh:
|
||||||
data, _ := ev.Data.(tmtypes.EventDataNewBlockHeader)
|
data, _ := ev.Data.(tmtypes.EventDataNewBlockHeader)
|
||||||
header := EthHeaderFromTendermint(data.Header)
|
header := rpctypes.EthHeaderFromTendermint(data.Header)
|
||||||
api.filtersMu.Lock()
|
api.filtersMu.Lock()
|
||||||
if f, found := api.filters[headerSub.ID()]; found {
|
if f, found := api.filters[headerSub.ID()]; found {
|
||||||
f.hashes = append(f.hashes, header.Hash())
|
f.hashes = append(f.hashes, header.Hash())
|
||||||
@ -255,7 +256,7 @@ func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, er
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
header := EthHeaderFromTendermint(data.Header)
|
header := rpctypes.EthHeaderFromTendermint(data.Header)
|
||||||
err = notifier.Notify(rpcSub.ID, header)
|
err = notifier.Notify(rpcSub.ID, header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
headersSub.err <- err
|
headersSub.err <- err
|
||||||
@ -317,7 +318,7 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logs := filterLogs(resultData.Logs, crit.FromBlock, crit.ToBlock, crit.Addresses, crit.Topics)
|
logs := FilterLogs(resultData.Logs, crit.FromBlock, crit.ToBlock, crit.Addresses, crit.Topics)
|
||||||
|
|
||||||
for _, log := range logs {
|
for _, log := range logs {
|
||||||
err = notifier.Notify(rpcSub.ID, log)
|
err = notifier.Notify(rpcSub.ID, log)
|
||||||
@ -386,7 +387,7 @@ func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logs := filterLogs(resultData.Logs, criteria.FromBlock, criteria.ToBlock, criteria.Addresses, criteria.Topics)
|
logs := FilterLogs(resultData.Logs, criteria.FromBlock, criteria.ToBlock, criteria.Addresses, criteria.Topics)
|
||||||
|
|
||||||
api.filtersMu.Lock()
|
api.filtersMu.Lock()
|
||||||
if f, found := api.filters[filterID]; found {
|
if f, found := api.filters[filterID]; found {
|
||||||
@ -407,7 +408,7 @@ func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID,
|
|||||||
|
|
||||||
// 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
|
||||||
func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit filters.FilterCriteria) ([]*ethtypes.Log, error) {
|
func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit filters.FilterCriteria) ([]*ethtypes.Log, error) {
|
||||||
var filter *Filter
|
var filter *Filter
|
||||||
if crit.BlockHash != nil {
|
if crit.BlockHash != nil {
|
||||||
@ -433,7 +434,7 @@ func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit filters.FilterCrit
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return returnLogs(logs), err
|
return returnLogs(logs), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UninstallFilter removes the filter with the given filter id.
|
// UninstallFilter removes the filter with the given filter id.
|
||||||
@ -533,21 +534,3 @@ func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) {
|
|||||||
return nil, fmt.Errorf("invalid filter %s type %d", id, f.typ)
|
return nil, fmt.Errorf("invalid filter %s type %d", id, f.typ)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// returnHashes is a helper that will return an empty hash array case the given hash array is nil,
|
|
||||||
// otherwise the given hashes array is returned.
|
|
||||||
func returnHashes(hashes []common.Hash) []common.Hash {
|
|
||||||
if hashes == nil {
|
|
||||||
return []common.Hash{}
|
|
||||||
}
|
|
||||||
return hashes
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
@ -1,9 +1,8 @@
|
|||||||
package rpc
|
package filters
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
tmquery "github.com/tendermint/tendermint/libs/pubsub/query"
|
tmquery "github.com/tendermint/tendermint/libs/pubsub/query"
|
||||||
@ -18,6 +17,7 @@ import (
|
|||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
|
rpctypes "github.com/cosmos/ethermint/rpc/types"
|
||||||
evmtypes "github.com/cosmos/ethermint/x/evm/types"
|
evmtypes "github.com/cosmos/ethermint/x/evm/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -262,7 +262,7 @@ func (es *EventSystem) handleLogs(ev coretypes.ResultEvent) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, f := range es.index[filters.LogsSubscription] {
|
for _, f := range es.index[filters.LogsSubscription] {
|
||||||
matchedLogs := filterLogs(resultData.Logs, f.logsCrit.FromBlock, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics)
|
matchedLogs := FilterLogs(resultData.Logs, f.logsCrit.FromBlock, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics)
|
||||||
if len(matchedLogs) > 0 {
|
if len(matchedLogs) > 0 {
|
||||||
f.logs <- matchedLogs
|
f.logs <- matchedLogs
|
||||||
}
|
}
|
||||||
@ -279,7 +279,7 @@ func (es *EventSystem) handleTxsEvent(ev coretypes.ResultEvent) {
|
|||||||
func (es *EventSystem) handleChainEvent(ev coretypes.ResultEvent) {
|
func (es *EventSystem) handleChainEvent(ev coretypes.ResultEvent) {
|
||||||
data, _ := ev.Data.(tmtypes.EventDataNewBlockHeader)
|
data, _ := ev.Data.(tmtypes.EventDataNewBlockHeader)
|
||||||
for _, f := range es.index[filters.BlocksSubscription] {
|
for _, f := range es.index[filters.BlocksSubscription] {
|
||||||
f.headers <- EthHeaderFromTendermint(data.Header)
|
f.headers <- rpctypes.EthHeaderFromTendermint(data.Header)
|
||||||
}
|
}
|
||||||
// TODO: light client
|
// TODO: light client
|
||||||
}
|
}
|
||||||
@ -377,57 +377,3 @@ func (es *EventSystem) eventLoop() {
|
|||||||
}
|
}
|
||||||
// }()
|
// }()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subscription defines a wrapper for the private subscription
|
|
||||||
type Subscription struct {
|
|
||||||
id rpc.ID
|
|
||||||
typ filters.Type
|
|
||||||
event string
|
|
||||||
created time.Time
|
|
||||||
logsCrit filters.FilterCriteria
|
|
||||||
logs chan []*ethtypes.Log
|
|
||||||
hashes chan []common.Hash
|
|
||||||
headers chan *ethtypes.Header
|
|
||||||
installed chan struct{} // closed when the filter is installed
|
|
||||||
eventCh <-chan coretypes.ResultEvent
|
|
||||||
err chan error
|
|
||||||
}
|
|
||||||
|
|
||||||
// ID returns the underlying subscription RPC identifier.
|
|
||||||
func (s Subscription) ID() rpc.ID {
|
|
||||||
return s.id
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unsubscribe to the current subscription from Tendermint Websocket. It sends an error to the
|
|
||||||
// subscription error channel if unsubscription fails.
|
|
||||||
func (s *Subscription) Unsubscribe(es *EventSystem) {
|
|
||||||
if err := es.client.Unsubscribe(es.ctx, string(s.ID()), s.event); err != nil {
|
|
||||||
s.err <- err
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer func() {
|
|
||||||
log.Println("successfully unsubscribed to event", s.event)
|
|
||||||
}()
|
|
||||||
|
|
||||||
uninstallLoop:
|
|
||||||
for {
|
|
||||||
// write uninstall request and consume logs/hashes. This prevents
|
|
||||||
// the eventLoop broadcast method to deadlock when writing to the
|
|
||||||
// filter event channel while the subscription loop is waiting for
|
|
||||||
// this method to return (and thus not reading these events).
|
|
||||||
select {
|
|
||||||
case es.uninstall <- s:
|
|
||||||
break uninstallLoop
|
|
||||||
case <-s.logs:
|
|
||||||
case <-s.hashes:
|
|
||||||
case <-s.headers:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Err returns the error channel
|
|
||||||
func (s *Subscription) Err() <-chan error {
|
|
||||||
return s.err
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
package rpc
|
package filters
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -9,25 +9,27 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/core/bloombits"
|
"github.com/ethereum/go-ethereum/core/bloombits"
|
||||||
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"
|
||||||
|
|
||||||
|
rpctypes "github.com/cosmos/ethermint/rpc/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Filter can be used to retrieve and filter logs.
|
// Filter can be used to retrieve and filter logs.
|
||||||
type Filter struct {
|
type Filter struct {
|
||||||
backend FiltersBackend
|
backend Backend
|
||||||
criteria filters.FilterCriteria
|
criteria filters.FilterCriteria
|
||||||
matcher *bloombits.Matcher
|
matcher *bloombits.Matcher
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBlockFilter creates a new filter which directly inspects the contents of
|
// NewBlockFilter creates a new filter which directly inspects the contents of
|
||||||
// a block to figure out whether it is interesting or not.
|
// a block to figure out whether it is interesting or not.
|
||||||
func NewBlockFilter(backend FiltersBackend, criteria filters.FilterCriteria) *Filter {
|
func NewBlockFilter(backend Backend, criteria filters.FilterCriteria) *Filter {
|
||||||
// Create a generic filter and convert it into a block filter
|
// Create a generic filter and convert it into a block filter
|
||||||
return newFilter(backend, criteria, nil)
|
return newFilter(backend, criteria, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRangeFilter creates a new filter which uses a bloom filter on blocks to
|
// NewRangeFilter creates a new filter which uses a bloom filter on blocks to
|
||||||
// figure out whether a particular block is interesting or not.
|
// figure out whether a particular block is interesting or not.
|
||||||
func NewRangeFilter(backend FiltersBackend, begin, end int64, addresses []common.Address, topics [][]common.Hash) *Filter {
|
func NewRangeFilter(backend Backend, begin, end int64, addresses []common.Address, topics [][]common.Hash) *Filter {
|
||||||
// Flatten the address and topic filter clauses into a single bloombits filter
|
// Flatten the address and topic filter clauses into a single bloombits filter
|
||||||
// system. Since the bloombits are not positional, nil topics are permitted,
|
// system. Since the bloombits are not positional, nil topics are permitted,
|
||||||
// which get flattened into a nil byte slice.
|
// which get flattened into a nil byte slice.
|
||||||
@ -62,7 +64,7 @@ func NewRangeFilter(backend FiltersBackend, begin, end int64, addresses []common
|
|||||||
}
|
}
|
||||||
|
|
||||||
// newFilter returns a new Filter
|
// newFilter returns a new Filter
|
||||||
func newFilter(backend FiltersBackend, criteria filters.FilterCriteria, matcher *bloombits.Matcher) *Filter {
|
func newFilter(backend Backend, criteria filters.FilterCriteria, matcher *bloombits.Matcher) *Filter {
|
||||||
return &Filter{
|
return &Filter{
|
||||||
backend: backend,
|
backend: backend,
|
||||||
criteria: criteria,
|
criteria: criteria,
|
||||||
@ -89,7 +91,7 @@ func (f *Filter) Logs(_ context.Context) ([]*ethtypes.Log, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Figure out the limits of the filter range
|
// Figure out the limits of the filter range
|
||||||
header, err := f.backend.HeaderByNumber(LatestBlockNumber)
|
header, err := f.backend.HeaderByNumber(rpctypes.LatestBlockNumber)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -107,7 +109,7 @@ func (f *Filter) Logs(_ context.Context) ([]*ethtypes.Log, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i := f.criteria.FromBlock.Int64(); i <= f.criteria.ToBlock.Int64(); i++ {
|
for i := f.criteria.FromBlock.Int64(); i <= f.criteria.ToBlock.Int64(); i++ {
|
||||||
block, err := f.backend.GetBlockByNumber(BlockNumber(i), true)
|
block, err := f.backend.GetBlockByNumber(rpctypes.BlockNumber(i), true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return logs, err
|
return logs, err
|
||||||
}
|
}
|
||||||
@ -139,7 +141,7 @@ func (f *Filter) blockLogs(header *ethtypes.Header) ([]*ethtypes.Log, error) {
|
|||||||
for _, logs := range logsList {
|
for _, logs := range logsList {
|
||||||
unfiltered = append(unfiltered, logs...)
|
unfiltered = append(unfiltered, logs...)
|
||||||
}
|
}
|
||||||
logs := filterLogs(unfiltered, nil, nil, f.criteria.Addresses, f.criteria.Topics)
|
logs := FilterLogs(unfiltered, nil, nil, f.criteria.Addresses, f.criteria.Topics)
|
||||||
if len(logs) == 0 {
|
if len(logs) == 0 {
|
||||||
return []*ethtypes.Log{}, nil
|
return []*ethtypes.Log{}, nil
|
||||||
}
|
}
|
||||||
@ -162,81 +164,5 @@ func (f *Filter) checkMatches(transactions []common.Hash) []*ethtypes.Log {
|
|||||||
unfiltered = append(unfiltered, logs...)
|
unfiltered = append(unfiltered, logs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return filterLogs(unfiltered, f.criteria.FromBlock, f.criteria.ToBlock, f.criteria.Addresses, f.criteria.Topics)
|
return FilterLogs(unfiltered, f.criteria.FromBlock, f.criteria.ToBlock, f.criteria.Addresses, f.criteria.Topics)
|
||||||
}
|
|
||||||
|
|
||||||
// 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:
|
|
||||||
for _, log := range logs {
|
|
||||||
if fromBlock != nil && fromBlock.Int64() >= 0 && fromBlock.Uint64() > log.BlockNumber {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if toBlock != nil && toBlock.Int64() >= 0 && toBlock.Uint64() < log.BlockNumber {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if len(addresses) > 0 && !includes(addresses, log.Address) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// If the to filtered topics is greater than the amount of topics in logs, skip.
|
|
||||||
if len(topics) > len(log.Topics) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for i, sub := range topics {
|
|
||||||
match := len(sub) == 0 // empty rule set == wildcard
|
|
||||||
for _, topic := range sub {
|
|
||||||
if log.Topics[i] == topic {
|
|
||||||
match = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !match {
|
|
||||||
continue Logs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ret = append(ret, log)
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func includes(addresses []common.Address, a common.Address) bool {
|
|
||||||
for _, addr := range addresses {
|
|
||||||
if addr == a {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func bloomFilter(bloom ethtypes.Bloom, addresses []common.Address, topics [][]common.Hash) bool {
|
|
||||||
var included bool
|
|
||||||
if len(addresses) > 0 {
|
|
||||||
for _, addr := range addresses {
|
|
||||||
if ethtypes.BloomLookup(bloom, addr) {
|
|
||||||
included = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !included {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, sub := range topics {
|
|
||||||
included = len(sub) == 0 // empty rule set == wildcard
|
|
||||||
for _, topic := range sub {
|
|
||||||
if ethtypes.BloomLookup(bloom, topic) {
|
|
||||||
included = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return included
|
|
||||||
}
|
}
|
71
rpc/namespaces/eth/filters/subscription.go
Normal file
71
rpc/namespaces/eth/filters/subscription.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package filters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/eth/filters"
|
||||||
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
coretypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Subscription defines a wrapper for the private subscription
|
||||||
|
type Subscription struct {
|
||||||
|
id rpc.ID
|
||||||
|
typ filters.Type
|
||||||
|
event string
|
||||||
|
created time.Time
|
||||||
|
logsCrit filters.FilterCriteria
|
||||||
|
logs chan []*ethtypes.Log
|
||||||
|
hashes chan []common.Hash
|
||||||
|
headers chan *ethtypes.Header
|
||||||
|
installed chan struct{} // closed when the filter is installed
|
||||||
|
eventCh <-chan coretypes.ResultEvent
|
||||||
|
err chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID returns the underlying subscription RPC identifier.
|
||||||
|
func (s Subscription) ID() rpc.ID {
|
||||||
|
return s.id
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unsubscribe to the current subscription from Tendermint Websocket. It sends an error to the
|
||||||
|
// subscription error channel if unsubscription fails.
|
||||||
|
func (s *Subscription) Unsubscribe(es *EventSystem) {
|
||||||
|
if err := es.client.Unsubscribe(es.ctx, string(s.ID()), s.event); err != nil {
|
||||||
|
s.err <- err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
log.Println("successfully unsubscribed to event", s.event)
|
||||||
|
}()
|
||||||
|
|
||||||
|
uninstallLoop:
|
||||||
|
for {
|
||||||
|
// write uninstall request and consume logs/hashes. This prevents
|
||||||
|
// the eventLoop broadcast method to deadlock when writing to the
|
||||||
|
// filter event channel while the subscription loop is waiting for
|
||||||
|
// this method to return (and thus not reading these events).
|
||||||
|
select {
|
||||||
|
case es.uninstall <- s:
|
||||||
|
break uninstallLoop
|
||||||
|
case <-s.logs:
|
||||||
|
case <-s.hashes:
|
||||||
|
case <-s.headers:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err returns the error channel
|
||||||
|
func (s *Subscription) Err() <-chan error {
|
||||||
|
return s.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Event returns the tendermint result event channel
|
||||||
|
func (s *Subscription) Event() <-chan coretypes.ResultEvent {
|
||||||
|
return s.eventCh
|
||||||
|
}
|
102
rpc/namespaces/eth/filters/utils.go
Normal file
102
rpc/namespaces/eth/filters/utils.go
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
package filters
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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:
|
||||||
|
for _, log := range logs {
|
||||||
|
if fromBlock != nil && fromBlock.Int64() >= 0 && fromBlock.Uint64() > log.BlockNumber {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if toBlock != nil && toBlock.Int64() >= 0 && toBlock.Uint64() < log.BlockNumber {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(addresses) > 0 && !includes(addresses, log.Address) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// If the to filtered topics is greater than the amount of topics in logs, skip.
|
||||||
|
if len(topics) > len(log.Topics) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for i, sub := range topics {
|
||||||
|
match := len(sub) == 0 // empty rule set == wildcard
|
||||||
|
for _, topic := range sub {
|
||||||
|
if log.Topics[i] == topic {
|
||||||
|
match = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !match {
|
||||||
|
continue Logs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret = append(ret, log)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func includes(addresses []common.Address, a common.Address) bool {
|
||||||
|
for _, addr := range addresses {
|
||||||
|
if addr == a {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func bloomFilter(bloom ethtypes.Bloom, addresses []common.Address, topics [][]common.Hash) bool {
|
||||||
|
var included bool
|
||||||
|
if len(addresses) > 0 {
|
||||||
|
for _, addr := range addresses {
|
||||||
|
if ethtypes.BloomLookup(bloom, addr) {
|
||||||
|
included = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !included {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sub := range topics {
|
||||||
|
included = len(sub) == 0 // empty rule set == wildcard
|
||||||
|
for _, topic := range sub {
|
||||||
|
if ethtypes.BloomLookup(bloom, topic) {
|
||||||
|
included = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return included
|
||||||
|
}
|
||||||
|
|
||||||
|
// returnHashes is a helper that will return an empty hash array case the given hash array is nil,
|
||||||
|
// otherwise the given hashes array is returned.
|
||||||
|
func returnHashes(hashes []common.Hash) []common.Hash {
|
||||||
|
if hashes == nil {
|
||||||
|
return []common.Hash{}
|
||||||
|
}
|
||||||
|
return hashes
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
@ -1,12 +1,9 @@
|
|||||||
package rpc
|
package net
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/context"
|
"github.com/cosmos/cosmos-sdk/client/context"
|
||||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
|
||||||
ethermint "github.com/cosmos/ethermint/types"
|
ethermint "github.com/cosmos/ethermint/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,11 +12,10 @@ type PublicNetAPI struct {
|
|||||||
networkVersion uint64
|
networkVersion uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPublicNetAPI creates an instance of the public Net Web3 API.
|
// NewAPI creates an instance of the public Net Web3 API.
|
||||||
func NewPublicNetAPI(_ context.CLIContext) *PublicNetAPI {
|
func NewAPI(clientCtx context.CLIContext) *PublicNetAPI {
|
||||||
chainID := viper.GetString(flags.FlagChainID)
|
|
||||||
// parse the chainID from a integer string
|
// parse the chainID from a integer string
|
||||||
chainIDEpoch, err := ethermint.ParseChainID(chainID)
|
chainIDEpoch, err := ethermint.ParseChainID(clientCtx.ChainID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -30,6 +26,6 @@ func NewPublicNetAPI(_ context.CLIContext) *PublicNetAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Version returns the current ethereum protocol version.
|
// Version returns the current ethereum protocol version.
|
||||||
func (s *PublicNetAPI) Version() string {
|
func (api *PublicNetAPI) Version() string {
|
||||||
return fmt.Sprintf("%d", s.networkVersion)
|
return fmt.Sprintf("%d", api.networkVersion)
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package rpc
|
package personal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -7,12 +7,10 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
"github.com/tendermint/tendermint/libs/log"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keys"
|
"github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||||
"github.com/cosmos/cosmos-sdk/crypto/keys/mintkey"
|
"github.com/cosmos/cosmos-sdk/crypto/keys/mintkey"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
@ -21,58 +19,43 @@ import (
|
|||||||
|
|
||||||
"github.com/cosmos/ethermint/crypto/ethsecp256k1"
|
"github.com/cosmos/ethermint/crypto/ethsecp256k1"
|
||||||
"github.com/cosmos/ethermint/crypto/hd"
|
"github.com/cosmos/ethermint/crypto/hd"
|
||||||
params "github.com/cosmos/ethermint/rpc/args"
|
"github.com/cosmos/ethermint/rpc/namespaces/eth"
|
||||||
|
rpctypes "github.com/cosmos/ethermint/rpc/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PersonalEthAPI is the personal_ prefixed set of APIs in the Web3 JSON-RPC spec.
|
// PrivateAccountAPI is the personal_ prefixed set of APIs in the Web3 JSON-RPC spec.
|
||||||
type PersonalEthAPI struct {
|
type PrivateAccountAPI struct {
|
||||||
ethAPI *PublicEthAPI
|
ethAPI *eth.PublicEthereumAPI
|
||||||
|
logger log.Logger
|
||||||
keyInfos []keys.Info // all keys, both locked and unlocked. unlocked keys are stored in ethAPI.keys
|
keyInfos []keys.Info // all keys, both locked and unlocked. unlocked keys are stored in ethAPI.keys
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPersonalEthAPI creates an instance of the public Personal Eth API.
|
// NewAPI creates an instance of the public Personal Eth API.
|
||||||
func NewPersonalEthAPI(ethAPI *PublicEthAPI) *PersonalEthAPI {
|
func NewAPI(ethAPI *eth.PublicEthereumAPI) *PrivateAccountAPI {
|
||||||
api := &PersonalEthAPI{
|
api := &PrivateAccountAPI{
|
||||||
ethAPI: ethAPI,
|
ethAPI: ethAPI,
|
||||||
|
logger: log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "json-rpc", "namespace", "personal"),
|
||||||
}
|
}
|
||||||
|
|
||||||
infos, err := api.getKeybaseInfo()
|
err := api.ethAPI.GetKeyringInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return api
|
return api
|
||||||
}
|
}
|
||||||
|
|
||||||
api.keyInfos = infos
|
api.keyInfos, err = api.ethAPI.ClientCtx().Keybase.List()
|
||||||
return api
|
if err != nil {
|
||||||
}
|
return api
|
||||||
|
|
||||||
func (e *PersonalEthAPI) getKeybaseInfo() ([]keys.Info, error) {
|
|
||||||
e.ethAPI.keybaseLock.Lock()
|
|
||||||
defer e.ethAPI.keybaseLock.Unlock()
|
|
||||||
|
|
||||||
if e.ethAPI.cliCtx.Keybase == nil {
|
|
||||||
keybase, err := keys.NewKeyring(
|
|
||||||
sdk.KeyringServiceName(),
|
|
||||||
viper.GetString(flags.FlagKeyringBackend),
|
|
||||||
viper.GetString(flags.FlagHome),
|
|
||||||
e.ethAPI.cliCtx.Input,
|
|
||||||
hd.EthSecp256k1Options()...,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
e.ethAPI.cliCtx.Keybase = keybase
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return e.ethAPI.cliCtx.Keybase.List()
|
return api
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImportRawKey armors and encrypts a given raw hex encoded ECDSA key and stores it into the key directory.
|
// ImportRawKey armors and encrypts a given raw hex encoded ECDSA key and stores it into the key directory.
|
||||||
// The name of the key will have the format "personal_<length-keys>", where <length-keys> is the total number of
|
// The name of the key will have the format "personal_<length-keys>", where <length-keys> is the total number of
|
||||||
// keys stored on the keyring.
|
// keys stored on the keyring.
|
||||||
// NOTE: The key will be both armored and encrypted using the same passphrase.
|
// NOTE: The key will be both armored and encrypted using the same passphrase.
|
||||||
func (e *PersonalEthAPI) ImportRawKey(privkey, password string) (common.Address, error) {
|
func (api *PrivateAccountAPI) ImportRawKey(privkey, password string) (common.Address, error) {
|
||||||
e.ethAPI.logger.Debug("personal_importRawKey")
|
api.logger.Debug("personal_importRawKey")
|
||||||
priv, err := crypto.HexToECDSA(privkey)
|
priv, err := crypto.HexToECDSA(privkey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Address{}, err
|
return common.Address{}, err
|
||||||
@ -83,33 +66,33 @@ func (e *PersonalEthAPI) ImportRawKey(privkey, password string) (common.Address,
|
|||||||
armor := mintkey.EncryptArmorPrivKey(privKey, password, ethsecp256k1.KeyType)
|
armor := mintkey.EncryptArmorPrivKey(privKey, password, ethsecp256k1.KeyType)
|
||||||
|
|
||||||
// ignore error as we only care about the length of the list
|
// ignore error as we only care about the length of the list
|
||||||
list, _ := e.ethAPI.cliCtx.Keybase.List()
|
list, _ := api.ethAPI.ClientCtx().Keybase.List()
|
||||||
privKeyName := fmt.Sprintf("personal_%d", len(list))
|
privKeyName := fmt.Sprintf("personal_%d", len(list))
|
||||||
|
|
||||||
if err := e.ethAPI.cliCtx.Keybase.ImportPrivKey(privKeyName, armor, password); err != nil {
|
if err := api.ethAPI.ClientCtx().Keybase.ImportPrivKey(privKeyName, armor, password); err != nil {
|
||||||
return common.Address{}, err
|
return common.Address{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
addr := common.BytesToAddress(privKey.PubKey().Address().Bytes())
|
addr := common.BytesToAddress(privKey.PubKey().Address().Bytes())
|
||||||
|
|
||||||
info, err := e.ethAPI.cliCtx.Keybase.Get(privKeyName)
|
info, err := api.ethAPI.ClientCtx().Keybase.Get(privKeyName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Address{}, err
|
return common.Address{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// append key and info to be able to lock and list the account
|
// append key and info to be able to lock and list the account
|
||||||
//e.ethAPI.keys = append(e.ethAPI.keys, privKey)
|
//api.ethAPI.keys = append(api.ethAPI.keys, privKey)
|
||||||
e.keyInfos = append(e.keyInfos, info)
|
api.keyInfos = append(api.keyInfos, info)
|
||||||
e.ethAPI.logger.Info("key successfully imported", "name", privKeyName, "address", addr.String())
|
api.logger.Info("key successfully imported", "name", privKeyName, "address", addr.String())
|
||||||
|
|
||||||
return addr, nil
|
return addr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListAccounts will return a list of addresses for accounts this node manages.
|
// ListAccounts will return a list of addresses for accounts this node manages.
|
||||||
func (e *PersonalEthAPI) ListAccounts() ([]common.Address, error) {
|
func (api *PrivateAccountAPI) ListAccounts() ([]common.Address, error) {
|
||||||
e.ethAPI.logger.Debug("personal_listAccounts")
|
api.logger.Debug("personal_listAccounts")
|
||||||
addrs := []common.Address{}
|
addrs := []common.Address{}
|
||||||
for _, info := range e.keyInfos {
|
for _, info := range api.keyInfos {
|
||||||
addressBytes := info.GetPubKey().Address().Bytes()
|
addressBytes := info.GetPubKey().Address().Bytes()
|
||||||
addrs = append(addrs, common.BytesToAddress(addressBytes))
|
addrs = append(addrs, common.BytesToAddress(addressBytes))
|
||||||
}
|
}
|
||||||
@ -119,20 +102,21 @@ func (e *PersonalEthAPI) ListAccounts() ([]common.Address, error) {
|
|||||||
|
|
||||||
// LockAccount will lock the account associated with the given address when it's unlocked.
|
// LockAccount will lock the account associated with the given address when it's unlocked.
|
||||||
// It removes the key corresponding to the given address from the API's local keys.
|
// It removes the key corresponding to the given address from the API's local keys.
|
||||||
func (e *PersonalEthAPI) LockAccount(address common.Address) bool {
|
func (api *PrivateAccountAPI) LockAccount(address common.Address) bool {
|
||||||
e.ethAPI.logger.Debug("personal_lockAccount", "address", address.String())
|
api.logger.Debug("personal_lockAccount", "address", address.String())
|
||||||
|
|
||||||
for i, key := range e.ethAPI.keys {
|
keys := api.ethAPI.GetKeys()
|
||||||
|
for i, key := range keys {
|
||||||
if !bytes.Equal(key.PubKey().Address().Bytes(), address.Bytes()) {
|
if !bytes.Equal(key.PubKey().Address().Bytes(), address.Bytes()) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
tmp := make([]ethsecp256k1.PrivKey, len(e.ethAPI.keys)-1)
|
tmp := make([]ethsecp256k1.PrivKey, len(keys)-1)
|
||||||
copy(tmp[:i], e.ethAPI.keys[:i])
|
copy(tmp[:i], keys[:i])
|
||||||
copy(tmp[i:], e.ethAPI.keys[i+1:])
|
copy(tmp[i:], keys[i+1:])
|
||||||
e.ethAPI.keys = tmp
|
api.ethAPI.SetKeys(tmp)
|
||||||
|
|
||||||
e.ethAPI.logger.Debug("account unlocked", "address", address.String())
|
api.logger.Debug("account unlocked", "address", address.String())
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,25 +124,21 @@ func (e *PersonalEthAPI) LockAccount(address common.Address) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewAccount will create a new account and returns the address for the new account.
|
// NewAccount will create a new account and returns the address for the new account.
|
||||||
func (e *PersonalEthAPI) NewAccount(password string) (common.Address, error) {
|
func (api *PrivateAccountAPI) NewAccount(password string) (common.Address, error) {
|
||||||
e.ethAPI.logger.Debug("personal_newAccount")
|
api.logger.Debug("personal_newAccount")
|
||||||
_, err := e.getKeybaseInfo()
|
|
||||||
if err != nil {
|
|
||||||
return common.Address{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
name := "key_" + time.Now().UTC().Format(time.RFC3339)
|
name := "key_" + time.Now().UTC().Format(time.RFC3339)
|
||||||
info, _, err := e.ethAPI.cliCtx.Keybase.CreateMnemonic(name, keys.English, password, hd.EthSecp256k1)
|
info, _, err := api.ethAPI.ClientCtx().Keybase.CreateMnemonic(name, keys.English, password, hd.EthSecp256k1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return common.Address{}, err
|
return common.Address{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
e.keyInfos = append(e.keyInfos, info)
|
api.keyInfos = append(api.keyInfos, info)
|
||||||
|
|
||||||
addr := common.BytesToAddress(info.GetPubKey().Address().Bytes())
|
addr := common.BytesToAddress(info.GetPubKey().Address().Bytes())
|
||||||
e.ethAPI.logger.Info("Your new key was generated", "address", addr.String())
|
api.logger.Info("Your new key was generated", "address", addr.String())
|
||||||
e.ethAPI.logger.Info("Please backup your key file!", "path", os.Getenv("HOME")+"/.ethermintcli/"+name)
|
api.logger.Info("Please backup your key file!", "path", os.Getenv("HOME")+"/.ethermintd/"+name)
|
||||||
e.ethAPI.logger.Info("Please remember your password!")
|
api.logger.Info("Please remember your password!")
|
||||||
return addr, nil
|
return addr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,13 +146,13 @@ func (e *PersonalEthAPI) NewAccount(password string) (common.Address, error) {
|
|||||||
// the given password for duration seconds. If duration is nil it will use a
|
// the given password for duration seconds. If duration is nil it will use a
|
||||||
// default of 300 seconds. It returns an indication if the account was unlocked.
|
// default of 300 seconds. It returns an indication if the account was unlocked.
|
||||||
// It exports the private key corresponding to the given address from the keyring and stores it in the API's local keys.
|
// It exports the private key corresponding to the given address from the keyring and stores it in the API's local keys.
|
||||||
func (e *PersonalEthAPI) UnlockAccount(_ context.Context, addr common.Address, password string, _ *uint64) (bool, error) { // nolint: interfacer
|
func (api *PrivateAccountAPI) UnlockAccount(_ context.Context, addr common.Address, password string, _ *uint64) (bool, error) { // nolint: interfacer
|
||||||
e.ethAPI.logger.Debug("personal_unlockAccount", "address", addr.String())
|
api.logger.Debug("personal_unlockAccount", "address", addr.String())
|
||||||
// TODO: use duration
|
// TODO: use duration
|
||||||
|
|
||||||
var keyInfo keys.Info
|
var keyInfo keys.Info
|
||||||
|
|
||||||
for _, info := range e.keyInfos {
|
for _, info := range api.keyInfos {
|
||||||
addressBytes := info.GetPubKey().Address().Bytes()
|
addressBytes := info.GetPubKey().Address().Bytes()
|
||||||
if bytes.Equal(addressBytes, addr[:]) {
|
if bytes.Equal(addressBytes, addr[:]) {
|
||||||
keyInfo = info
|
keyInfo = info
|
||||||
@ -184,31 +164,26 @@ func (e *PersonalEthAPI) UnlockAccount(_ context.Context, addr common.Address, p
|
|||||||
return false, fmt.Errorf("cannot find key with given address %s", addr.String())
|
return false, fmt.Errorf("cannot find key with given address %s", addr.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// exporting private key only works on local keys
|
privKey, err := api.ethAPI.ClientCtx().Keybase.ExportPrivateKeyObject(keyInfo.GetName(), password)
|
||||||
if keyInfo.GetType() != keys.TypeLocal {
|
|
||||||
return false, fmt.Errorf("key type must be %s, got %s", keys.TypeLedger.String(), keyInfo.GetType().String())
|
|
||||||
}
|
|
||||||
|
|
||||||
privKey, err := e.ethAPI.cliCtx.Keybase.ExportPrivateKeyObject(keyInfo.GetName(), password)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
emintKey, ok := privKey.(ethsecp256k1.PrivKey)
|
ethermintPrivKey, ok := privKey.(ethsecp256k1.PrivKey)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false, fmt.Errorf("invalid private key type: %T", privKey)
|
return false, fmt.Errorf("invalid private key type %T, expected %T", privKey, ðsecp256k1.PrivKey{})
|
||||||
}
|
}
|
||||||
|
|
||||||
e.ethAPI.keys = append(e.ethAPI.keys, emintKey)
|
api.ethAPI.SetKeys(append(api.ethAPI.GetKeys(), ethermintPrivKey))
|
||||||
e.ethAPI.logger.Debug("account unlocked", "address", addr.String())
|
api.logger.Debug("account unlocked", "address", addr.String())
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendTransaction will create a transaction from the given arguments and
|
// SendTransaction will create a transaction from the given arguments and
|
||||||
// tries to sign it with the key associated with args.To. If the given password isn't
|
// tries to sign it with the key associated with args.To. If the given password isn't
|
||||||
// able to decrypt the key it fails.
|
// able to decrypt the key it fails.
|
||||||
func (e *PersonalEthAPI) SendTransaction(_ context.Context, args params.SendTxArgs, _ string) (common.Hash, error) {
|
func (api *PrivateAccountAPI) SendTransaction(_ context.Context, args rpctypes.SendTxArgs, _ string) (common.Hash, error) {
|
||||||
return e.ethAPI.SendTransaction(args)
|
return api.ethAPI.SendTransaction(args)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign calculates an Ethereum ECDSA signature for:
|
// Sign calculates an Ethereum ECDSA signature for:
|
||||||
@ -220,10 +195,10 @@ func (e *PersonalEthAPI) SendTransaction(_ context.Context, args params.SendTxAr
|
|||||||
// The key used to calculate the signature is decrypted with the given password.
|
// The key used to calculate the signature is decrypted with the given password.
|
||||||
//
|
//
|
||||||
// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_sign
|
// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_sign
|
||||||
func (e *PersonalEthAPI) Sign(_ context.Context, data hexutil.Bytes, addr common.Address, _ string) (hexutil.Bytes, error) {
|
func (api *PrivateAccountAPI) Sign(_ context.Context, data hexutil.Bytes, addr common.Address, _ string) (hexutil.Bytes, error) {
|
||||||
e.ethAPI.logger.Debug("personal_sign", "data", data, "address", addr.String())
|
api.logger.Debug("personal_sign", "data", data, "address", addr.String())
|
||||||
|
|
||||||
key, ok := checkKeyInKeyring(e.ethAPI.keys, addr)
|
key, ok := rpctypes.GetKeyByAddress(api.ethAPI.GetKeys(), addr)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("cannot find key with address %s", addr.String())
|
return nil, fmt.Errorf("cannot find key with address %s", addr.String())
|
||||||
}
|
}
|
||||||
@ -247,8 +222,8 @@ func (e *PersonalEthAPI) Sign(_ context.Context, data hexutil.Bytes, addr common
|
|||||||
// the V value must be 27 or 28 for legacy reasons.
|
// the V value must be 27 or 28 for legacy reasons.
|
||||||
//
|
//
|
||||||
// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecove
|
// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecove
|
||||||
func (e *PersonalEthAPI) EcRecover(_ context.Context, data, sig hexutil.Bytes) (common.Address, error) {
|
func (api *PrivateAccountAPI) EcRecover(_ context.Context, data, sig hexutil.Bytes) (common.Address, error) {
|
||||||
e.ethAPI.logger.Debug("personal_ecRecover", "data", data, "sig", sig)
|
api.logger.Debug("personal_ecRecover", "data", data, "sig", sig)
|
||||||
|
|
||||||
if len(sig) != crypto.SignatureLength {
|
if len(sig) != crypto.SignatureLength {
|
||||||
return common.Address{}, fmt.Errorf("signature must be %d bytes long", crypto.SignatureLength)
|
return common.Address{}, fmt.Errorf("signature must be %d bytes long", crypto.SignatureLength)
|
@ -1,4 +1,4 @@
|
|||||||
package rpc
|
package web3
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/cosmos/ethermint/version"
|
"github.com/cosmos/ethermint/version"
|
||||||
@ -10,17 +10,17 @@ import (
|
|||||||
// PublicWeb3API is the web3_ prefixed set of APIs in the Web3 JSON-RPC spec.
|
// PublicWeb3API is the web3_ prefixed set of APIs in the Web3 JSON-RPC spec.
|
||||||
type PublicWeb3API struct{}
|
type PublicWeb3API struct{}
|
||||||
|
|
||||||
// NewPublicWeb3API creates an instance of the Web3 API.
|
// New creates an instance of the Web3 API.
|
||||||
func NewPublicWeb3API() *PublicWeb3API {
|
func NewAPI() *PublicWeb3API {
|
||||||
return &PublicWeb3API{}
|
return &PublicWeb3API{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClientVersion returns the client version in the Web3 user agent format.
|
// ClientVersion returns the client version in the Web3 user agent format.
|
||||||
func (a *PublicWeb3API) ClientVersion() string {
|
func (PublicWeb3API) ClientVersion() string {
|
||||||
return version.ClientVersion()
|
return version.ClientVersion()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sha3 returns the keccak-256 hash of the passed-in input.
|
// Sha3 returns the keccak-256 hash of the passed-in input.
|
||||||
func (a *PublicWeb3API) Sha3(input hexutil.Bytes) hexutil.Bytes {
|
func (PublicWeb3API) Sha3(input hexutil.Bytes) hexutil.Bytes {
|
||||||
return crypto.Keccak256(input)
|
return crypto.Keccak256(input)
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package rpc
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
@ -1,4 +1,4 @@
|
|||||||
package rpc
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -63,5 +63,16 @@ func (bn *BlockNumber) UnmarshalJSON(data []byte) error {
|
|||||||
|
|
||||||
// Int64 converts block number to primitive type
|
// Int64 converts block number to primitive type
|
||||||
func (bn BlockNumber) Int64() int64 {
|
func (bn BlockNumber) Int64() int64 {
|
||||||
return (int64)(bn)
|
return int64(bn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TmHeight is a util function used for the Tendermint RPC client. It returns
|
||||||
|
// nil if the block number is "latest". Otherwise, it returns the pointer of the
|
||||||
|
// int64 value of the height.
|
||||||
|
func (bn BlockNumber) TmHeight() *int64 {
|
||||||
|
if bn == LatestBlockNumber {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
height := bn.Int64()
|
||||||
|
return &height
|
||||||
}
|
}
|
85
rpc/types/types.go
Normal file
85
rpc/types/types.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Copied the Account and StorageResult types since they are registered under an
|
||||||
|
// internal pkg on geth.
|
||||||
|
|
||||||
|
// AccountResult struct for account proof
|
||||||
|
type AccountResult struct {
|
||||||
|
Address common.Address `json:"address"`
|
||||||
|
AccountProof []string `json:"accountProof"`
|
||||||
|
Balance *hexutil.Big `json:"balance"`
|
||||||
|
CodeHash common.Hash `json:"codeHash"`
|
||||||
|
Nonce hexutil.Uint64 `json:"nonce"`
|
||||||
|
StorageHash common.Hash `json:"storageHash"`
|
||||||
|
StorageProof []StorageResult `json:"storageProof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// StorageResult defines the format for storage proof return
|
||||||
|
type StorageResult struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
Value *hexutil.Big `json:"value"`
|
||||||
|
Proof []string `json:"proof"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transaction represents a transaction returned to RPC clients.
|
||||||
|
type Transaction struct {
|
||||||
|
BlockHash *common.Hash `json:"blockHash"`
|
||||||
|
BlockNumber *hexutil.Big `json:"blockNumber"`
|
||||||
|
From common.Address `json:"from"`
|
||||||
|
Gas hexutil.Uint64 `json:"gas"`
|
||||||
|
GasPrice *hexutil.Big `json:"gasPrice"`
|
||||||
|
Hash common.Hash `json:"hash"`
|
||||||
|
Input hexutil.Bytes `json:"input"`
|
||||||
|
Nonce hexutil.Uint64 `json:"nonce"`
|
||||||
|
To *common.Address `json:"to"`
|
||||||
|
TransactionIndex *hexutil.Uint64 `json:"transactionIndex"`
|
||||||
|
Value *hexutil.Big `json:"value"`
|
||||||
|
V *hexutil.Big `json:"v"`
|
||||||
|
R *hexutil.Big `json:"r"`
|
||||||
|
S *hexutil.Big `json:"s"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendTxArgs represents the arguments to submit a new transaction into the transaction pool.
|
||||||
|
// Duplicate struct definition since geth struct is in internal package
|
||||||
|
// Ref: https://github.com/ethereum/go-ethereum/blob/release/1.9/internal/ethapi/api.go#L1346
|
||||||
|
type SendTxArgs struct {
|
||||||
|
From common.Address `json:"from"`
|
||||||
|
To *common.Address `json:"to"`
|
||||||
|
Gas *hexutil.Uint64 `json:"gas"`
|
||||||
|
GasPrice *hexutil.Big `json:"gasPrice"`
|
||||||
|
Value *hexutil.Big `json:"value"`
|
||||||
|
Nonce *hexutil.Uint64 `json:"nonce"`
|
||||||
|
// We accept "data" and "input" for backwards-compatibility reasons. "input" is the
|
||||||
|
// newer name and should be preferred by clients.
|
||||||
|
Data *hexutil.Bytes `json:"data"`
|
||||||
|
Input *hexutil.Bytes `json:"input"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CallArgs represents the arguments for a call.
|
||||||
|
type CallArgs struct {
|
||||||
|
From *common.Address `json:"from"`
|
||||||
|
To *common.Address `json:"to"`
|
||||||
|
Gas *hexutil.Uint64 `json:"gas"`
|
||||||
|
GasPrice *hexutil.Big `json:"gasPrice"`
|
||||||
|
Value *hexutil.Big `json:"value"`
|
||||||
|
Data *hexutil.Bytes `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Account indicates the overriding fields of account during the execution of
|
||||||
|
// a message call.
|
||||||
|
// NOTE: state and stateDiff can't be specified at the same time. If state is
|
||||||
|
// set, message execution will only use the data in the given state. Otherwise
|
||||||
|
// if statDiff is set, all diff will be applied first and then execute the call
|
||||||
|
// message.
|
||||||
|
type Account struct {
|
||||||
|
Nonce *hexutil.Uint64 `json:"nonce"`
|
||||||
|
Code *hexutil.Bytes `json:"code"`
|
||||||
|
Balance **hexutil.Big `json:"balance"`
|
||||||
|
State *map[common.Hash]common.Hash `json:"state"`
|
||||||
|
StateDiff *map[common.Hash]common.Hash `json:"stateDiff"`
|
||||||
|
}
|
191
rpc/types/utils.go
Normal file
191
rpc/types/utils.go
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
tmbytes "github.com/tendermint/tendermint/libs/bytes"
|
||||||
|
tmtypes "github.com/tendermint/tendermint/types"
|
||||||
|
|
||||||
|
clientcontext "github.com/cosmos/cosmos-sdk/client/context"
|
||||||
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
|
|
||||||
|
"github.com/cosmos/ethermint/crypto/ethsecp256k1"
|
||||||
|
evmtypes "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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RawTxToEthTx returns a evm MsgEthereum transaction from raw tx bytes.
|
||||||
|
func RawTxToEthTx(clientCtx clientcontext.CLIContext, bz []byte) (*evmtypes.MsgEthereumTx, error) {
|
||||||
|
tx, err := evmtypes.TxDecoder(clientCtx.Codec)(bz)
|
||||||
|
if err != nil {
|
||||||
|
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
ethTx, ok := tx.(evmtypes.MsgEthereumTx)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid transaction type %T, expected %T", tx, &evmtypes.MsgEthereumTx{})
|
||||||
|
}
|
||||||
|
return ðTx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTransaction returns a transaction that will serialize to the RPC
|
||||||
|
// representation, with the given location metadata set (if available).
|
||||||
|
func NewTransaction(tx *evmtypes.MsgEthereumTx, txHash, blockHash common.Hash, blockNumber, index uint64) (*Transaction, error) {
|
||||||
|
// Verify signature and retrieve sender address
|
||||||
|
from, err := tx.VerifySig(tx.ChainID())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rpcTx := &Transaction{
|
||||||
|
From: from,
|
||||||
|
Gas: hexutil.Uint64(tx.Data.GasLimit),
|
||||||
|
GasPrice: (*hexutil.Big)(tx.Data.Price),
|
||||||
|
Hash: txHash,
|
||||||
|
Input: hexutil.Bytes(tx.Data.Payload),
|
||||||
|
Nonce: hexutil.Uint64(tx.Data.AccountNonce),
|
||||||
|
To: tx.To(),
|
||||||
|
Value: (*hexutil.Big)(tx.Data.Amount),
|
||||||
|
V: (*hexutil.Big)(tx.Data.V),
|
||||||
|
R: (*hexutil.Big)(tx.Data.R),
|
||||||
|
S: (*hexutil.Big)(tx.Data.S),
|
||||||
|
}
|
||||||
|
|
||||||
|
if blockHash != (common.Hash{}) {
|
||||||
|
rpcTx.BlockHash = &blockHash
|
||||||
|
rpcTx.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber))
|
||||||
|
rpcTx.TransactionIndex = (*hexutil.Uint64)(&index)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rpcTx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EthBlockFromTendermint returns a JSON-RPC compatible Ethereum blockfrom a given Tendermint block.
|
||||||
|
func EthBlockFromTendermint(clientCtx clientcontext.CLIContext, block *tmtypes.Block) (map[string]interface{}, error) {
|
||||||
|
gasLimit, err := BlockMaxGasFromConsensusParams(context.Background(), clientCtx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
transactions, gasUsed, err := EthTransactionsFromTendermint(clientCtx, block.Txs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res, _, err := clientCtx.Query(fmt.Sprintf("custom/%s/%s/%d", evmtypes.ModuleName, evmtypes.QueryBloom, block.Height))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var bloomRes evmtypes.QueryBloomFilter
|
||||||
|
clientCtx.Codec.MustUnmarshalJSON(res, &bloomRes)
|
||||||
|
|
||||||
|
bloom := bloomRes.Bloom
|
||||||
|
|
||||||
|
return formatBlock(block.Header, block.Size(), gasLimit, gasUsed, transactions, bloom), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EthHeaderFromTendermint is an util function that returns an Ethereum Header
|
||||||
|
// from a tendermint Header.
|
||||||
|
func EthHeaderFromTendermint(header tmtypes.Header) *ethtypes.Header {
|
||||||
|
return ðtypes.Header{
|
||||||
|
ParentHash: common.BytesToHash(header.LastBlockID.Hash.Bytes()),
|
||||||
|
UncleHash: common.Hash{},
|
||||||
|
Coinbase: common.Address{},
|
||||||
|
Root: common.BytesToHash(header.AppHash),
|
||||||
|
TxHash: common.BytesToHash(header.DataHash),
|
||||||
|
ReceiptHash: common.Hash{},
|
||||||
|
Difficulty: nil,
|
||||||
|
Number: big.NewInt(header.Height),
|
||||||
|
Time: uint64(header.Time.Unix()),
|
||||||
|
Extra: nil,
|
||||||
|
MixDigest: common.Hash{},
|
||||||
|
Nonce: ethtypes.BlockNonce{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EthTransactionsFromTendermint returns a slice of ethereum transaction hashes and the total gas usage from a set of
|
||||||
|
// tendermint block transactions.
|
||||||
|
func EthTransactionsFromTendermint(clientCtx clientcontext.CLIContext, txs []tmtypes.Tx) ([]common.Hash, *big.Int, error) {
|
||||||
|
transactionHashes := []common.Hash{}
|
||||||
|
gasUsed := big.NewInt(0)
|
||||||
|
|
||||||
|
for _, tx := range txs {
|
||||||
|
ethTx, err := RawTxToEthTx(clientCtx, tx)
|
||||||
|
if err != nil {
|
||||||
|
// continue to next transaction in case it's not a MsgEthereumTx
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// TODO: Remove gas usage calculation if saving gasUsed per block
|
||||||
|
gasUsed.Add(gasUsed, ethTx.Fee())
|
||||||
|
transactionHashes = append(transactionHashes, common.BytesToHash(tx.Hash()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return transactionHashes, gasUsed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockMaxGasFromConsensusParams returns the gas limit for the latest block from the chain consensus params.
|
||||||
|
func BlockMaxGasFromConsensusParams(_ context.Context, clientCtx clientcontext.CLIContext) (int64, error) {
|
||||||
|
resConsParams, err := clientCtx.Client.ConsensusParams(nil)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
gasLimit := resConsParams.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))
|
||||||
|
}
|
||||||
|
|
||||||
|
return gasLimit, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatBlock(
|
||||||
|
header tmtypes.Header, size int, gasLimit int64,
|
||||||
|
gasUsed *big.Int, transactions interface{}, bloom ethtypes.Bloom,
|
||||||
|
) map[string]interface{} {
|
||||||
|
if len(header.DataHash) == 0 {
|
||||||
|
header.DataHash = tmbytes.HexBytes(common.Hash{}.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]interface{}{
|
||||||
|
"number": hexutil.Uint64(header.Height),
|
||||||
|
"hash": hexutil.Bytes(header.Hash()),
|
||||||
|
"parentHash": hexutil.Bytes(header.LastBlockID.Hash),
|
||||||
|
"nonce": hexutil.Uint64(0), // PoW specific
|
||||||
|
"sha3Uncles": common.Hash{}, // No uncles in Tendermint
|
||||||
|
"logsBloom": bloom,
|
||||||
|
"transactionsRoot": hexutil.Bytes(header.DataHash),
|
||||||
|
"stateRoot": hexutil.Bytes(header.AppHash),
|
||||||
|
"miner": common.Address{},
|
||||||
|
"mixHash": common.Hash{},
|
||||||
|
"difficulty": 0,
|
||||||
|
"totalDifficulty": 0,
|
||||||
|
"extraData": hexutil.Uint64(0),
|
||||||
|
"size": hexutil.Uint64(size),
|
||||||
|
"gasLimit": hexutil.Uint64(gasLimit), // Static gas limit
|
||||||
|
"gasUsed": (*hexutil.Big)(gasUsed),
|
||||||
|
"timestamp": hexutil.Uint64(header.Time.Unix()),
|
||||||
|
"transactions": transactions.([]common.Hash),
|
||||||
|
"uncles": []string{},
|
||||||
|
"receiptsRoot": common.Hash{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetKeyByAddress returns the private key matching the given address. If not found it returns false.
|
||||||
|
func GetKeyByAddress(keys []ethsecp256k1.PrivKey, address common.Address) (key *ethsecp256k1.PrivKey, exist bool) {
|
||||||
|
for _, key := range keys {
|
||||||
|
if bytes.Equal(key.PubKey().Address().Bytes(), address.Bytes()) {
|
||||||
|
return &key, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
@ -1,542 +0,0 @@
|
|||||||
package rpc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"math/big"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
|
|
||||||
"github.com/tendermint/tendermint/libs/log"
|
|
||||||
coretypes "github.com/tendermint/tendermint/rpc/core/types"
|
|
||||||
tmtypes "github.com/tendermint/tendermint/types"
|
|
||||||
|
|
||||||
evmtypes "github.com/cosmos/ethermint/x/evm/types"
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/eth/filters"
|
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
|
||||||
|
|
||||||
context "github.com/cosmos/cosmos-sdk/client/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SubscriptionResponseJSON struct {
|
|
||||||
Jsonrpc string `json:"jsonrpc"`
|
|
||||||
Result interface{} `json:"result"`
|
|
||||||
ID float64 `json:"id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SubscriptionNotification struct {
|
|
||||||
Jsonrpc string `json:"jsonrpc"`
|
|
||||||
Method string `json:"method"`
|
|
||||||
Params *SubscriptionResult `json:"params"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SubscriptionResult struct {
|
|
||||||
Subscription rpc.ID `json:"subscription"`
|
|
||||||
Result interface{} `json:"result"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ErrorResponseJSON struct {
|
|
||||||
Jsonrpc string `json:"jsonrpc"`
|
|
||||||
Error *ErrorMessageJSON `json:"error"`
|
|
||||||
ID *big.Int `json:"id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ErrorMessageJSON struct {
|
|
||||||
Code *big.Int `json:"code"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type websocketsServer struct {
|
|
||||||
rpcAddr string // listen address of rest-server
|
|
||||||
wsAddr string // listen address of ws server
|
|
||||||
api *pubSubAPI
|
|
||||||
logger log.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func newWebsocketsServer(cliCtx context.CLIContext, wsAddr string) *websocketsServer {
|
|
||||||
return &websocketsServer{
|
|
||||||
rpcAddr: viper.GetString("laddr"),
|
|
||||||
wsAddr: wsAddr,
|
|
||||||
api: newPubSubAPI(cliCtx),
|
|
||||||
logger: log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "websocket-server"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *websocketsServer) start() {
|
|
||||||
ws := mux.NewRouter()
|
|
||||||
ws.Handle("/", s)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
err := http.ListenAndServe(fmt.Sprintf(":%s", s.wsAddr), ws)
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("http error:", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *websocketsServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var upgrader = websocket.Upgrader{
|
|
||||||
CheckOrigin: func(r *http.Request) bool {
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
wsConn, err := upgrader.Upgrade(w, r, nil)
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("websocket upgrade failed; error:", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
s.readLoop(wsConn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *websocketsServer) sendErrResponse(conn *websocket.Conn, msg string) {
|
|
||||||
res := &ErrorResponseJSON{
|
|
||||||
Jsonrpc: "2.0",
|
|
||||||
Error: &ErrorMessageJSON{
|
|
||||||
Code: big.NewInt(-32600),
|
|
||||||
Message: msg,
|
|
||||||
},
|
|
||||||
ID: nil,
|
|
||||||
}
|
|
||||||
err := conn.WriteJSON(res)
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("websocket failed write message", "error", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *websocketsServer) readLoop(wsConn *websocket.Conn) {
|
|
||||||
for {
|
|
||||||
_, mb, err := wsConn.ReadMessage()
|
|
||||||
if err != nil {
|
|
||||||
_ = wsConn.Close()
|
|
||||||
s.logger.Error("failed to read message; error", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var msg map[string]interface{}
|
|
||||||
err = json.Unmarshal(mb, &msg)
|
|
||||||
if err != nil {
|
|
||||||
s.sendErrResponse(wsConn, "invalid request")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if method == eth_subscribe or eth_unsubscribe
|
|
||||||
method := msg["method"]
|
|
||||||
if method.(string) == "eth_subscribe" {
|
|
||||||
params := msg["params"].([]interface{})
|
|
||||||
if len(params) == 0 {
|
|
||||||
s.sendErrResponse(wsConn, "invalid parameters")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
id, err := s.api.subscribe(wsConn, params)
|
|
||||||
if err != nil {
|
|
||||||
s.sendErrResponse(wsConn, err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
res := &SubscriptionResponseJSON{
|
|
||||||
Jsonrpc: "2.0",
|
|
||||||
ID: 1,
|
|
||||||
Result: id,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = wsConn.WriteJSON(res)
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("failed to write json response", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
|
||||||
} else if method.(string) == "eth_unsubscribe" {
|
|
||||||
ids, ok := msg["params"].([]interface{})
|
|
||||||
if _, idok := ids[0].(string); !ok || !idok {
|
|
||||||
s.sendErrResponse(wsConn, "invalid parameters")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
ok = s.api.unsubscribe(rpc.ID(ids[0].(string)))
|
|
||||||
res := &SubscriptionResponseJSON{
|
|
||||||
Jsonrpc: "2.0",
|
|
||||||
ID: 1,
|
|
||||||
Result: ok,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = wsConn.WriteJSON(res)
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("failed to write json response", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// otherwise, call the usual rpc server to respond
|
|
||||||
err = s.tcpGetAndSendResponse(wsConn, mb)
|
|
||||||
if err != nil {
|
|
||||||
s.sendErrResponse(wsConn, err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// tcpGetAndSendResponse connects to the rest-server over tcp, posts a JSON-RPC request, and sends the response
|
|
||||||
// to the client over websockets
|
|
||||||
func (s *websocketsServer) tcpGetAndSendResponse(conn *websocket.Conn, mb []byte) error {
|
|
||||||
addr := strings.Split(s.rpcAddr, "tcp://")
|
|
||||||
if len(addr) != 2 {
|
|
||||||
return fmt.Errorf("invalid laddr %s", s.rpcAddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
tcpConn, err := net.Dial("tcp", addr[1])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("cannot connect to %s; %s", s.rpcAddr, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
_, err = buf.Write(mb)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to write message; %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", s.rpcAddr, buf)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to request; %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Set("Content-Type", "application/json;")
|
|
||||||
err = req.Write(tcpConn)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to write to rest-server; %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
respBytes, err := ioutil.ReadAll(tcpConn)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error reading response from rest-server; %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
respbuf := &bytes.Buffer{}
|
|
||||||
respbuf.Write(respBytes)
|
|
||||||
resp, err := http.ReadResponse(bufio.NewReader(respbuf), req)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not read response; %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not read body from response; %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var wsSend interface{}
|
|
||||||
err = json.Unmarshal(body, &wsSend)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to unmarshal rest-server response; %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return conn.WriteJSON(wsSend)
|
|
||||||
}
|
|
||||||
|
|
||||||
type wsSubscription struct {
|
|
||||||
sub *Subscription
|
|
||||||
unsubscribed chan struct{} // closed when unsubscribing
|
|
||||||
conn *websocket.Conn
|
|
||||||
}
|
|
||||||
|
|
||||||
// pubSubAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec
|
|
||||||
type pubSubAPI struct {
|
|
||||||
cliCtx context.CLIContext
|
|
||||||
events *EventSystem
|
|
||||||
filtersMu sync.Mutex
|
|
||||||
filters map[rpc.ID]*wsSubscription
|
|
||||||
logger log.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
// newPubSubAPI creates an instance of the ethereum PubSub API.
|
|
||||||
func newPubSubAPI(cliCtx context.CLIContext) *pubSubAPI {
|
|
||||||
return &pubSubAPI{
|
|
||||||
cliCtx: cliCtx,
|
|
||||||
events: NewEventSystem(cliCtx.Client),
|
|
||||||
filters: make(map[rpc.ID]*wsSubscription),
|
|
||||||
logger: log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "websocket-client"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *pubSubAPI) subscribe(conn *websocket.Conn, params []interface{}) (rpc.ID, error) {
|
|
||||||
method, ok := params[0].(string)
|
|
||||||
if !ok {
|
|
||||||
return "0", fmt.Errorf("invalid parameters")
|
|
||||||
}
|
|
||||||
|
|
||||||
switch method {
|
|
||||||
case "newHeads":
|
|
||||||
// TODO: handle extra params
|
|
||||||
return api.subscribeNewHeads(conn)
|
|
||||||
case "logs":
|
|
||||||
if len(params) > 1 {
|
|
||||||
return api.subscribeLogs(conn, params[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
return api.subscribeLogs(conn, nil)
|
|
||||||
case "newPendingTransactions":
|
|
||||||
return api.subscribePendingTransactions(conn)
|
|
||||||
case "syncing":
|
|
||||||
return api.subscribeSyncing(conn)
|
|
||||||
default:
|
|
||||||
return "0", fmt.Errorf("unsupported method %s", method)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *pubSubAPI) unsubscribe(id rpc.ID) bool {
|
|
||||||
api.filtersMu.Lock()
|
|
||||||
defer api.filtersMu.Unlock()
|
|
||||||
|
|
||||||
if api.filters[id] == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
close(api.filters[id].unsubscribed)
|
|
||||||
delete(api.filters, id)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *pubSubAPI) subscribeNewHeads(conn *websocket.Conn) (rpc.ID, error) {
|
|
||||||
sub, _, err := api.events.SubscribeNewHeads()
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("error creating block filter: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
unsubscribed := make(chan struct{})
|
|
||||||
api.filtersMu.Lock()
|
|
||||||
api.filters[sub.ID()] = &wsSubscription{
|
|
||||||
sub: sub,
|
|
||||||
conn: conn,
|
|
||||||
unsubscribed: unsubscribed,
|
|
||||||
}
|
|
||||||
api.filtersMu.Unlock()
|
|
||||||
|
|
||||||
go func(headersCh <-chan coretypes.ResultEvent, errCh <-chan error) {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case event := <-headersCh:
|
|
||||||
data, _ := event.Data.(tmtypes.EventDataNewBlockHeader)
|
|
||||||
header := EthHeaderFromTendermint(data.Header)
|
|
||||||
|
|
||||||
api.filtersMu.Lock()
|
|
||||||
if f, found := api.filters[sub.ID()]; found {
|
|
||||||
// write to ws conn
|
|
||||||
res := &SubscriptionNotification{
|
|
||||||
Jsonrpc: "2.0",
|
|
||||||
Method: "eth_subscription",
|
|
||||||
Params: &SubscriptionResult{
|
|
||||||
Subscription: sub.ID(),
|
|
||||||
Result: header,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
err = f.conn.WriteJSON(res)
|
|
||||||
if err != nil {
|
|
||||||
api.logger.Error("error writing header")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
api.filtersMu.Unlock()
|
|
||||||
case <-errCh:
|
|
||||||
api.filtersMu.Lock()
|
|
||||||
delete(api.filters, sub.ID())
|
|
||||||
api.filtersMu.Unlock()
|
|
||||||
return
|
|
||||||
case <-unsubscribed:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}(sub.eventCh, sub.Err())
|
|
||||||
|
|
||||||
return sub.ID(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *pubSubAPI) subscribeLogs(conn *websocket.Conn, extra interface{}) (rpc.ID, error) {
|
|
||||||
crit := filters.FilterCriteria{}
|
|
||||||
|
|
||||||
if extra != nil {
|
|
||||||
params, ok := extra.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return "", fmt.Errorf("invalid criteria")
|
|
||||||
}
|
|
||||||
|
|
||||||
if params["address"] != nil {
|
|
||||||
address, ok := params["address"].(string)
|
|
||||||
addresses, sok := params["address"].([]interface{})
|
|
||||||
if !ok && !sok {
|
|
||||||
return "", fmt.Errorf("invalid address; must be address or array of addresses")
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok {
|
|
||||||
crit.Addresses = []common.Address{common.HexToAddress(address)}
|
|
||||||
}
|
|
||||||
|
|
||||||
if sok {
|
|
||||||
crit.Addresses = []common.Address{}
|
|
||||||
for _, addr := range addresses {
|
|
||||||
address, ok := addr.(string)
|
|
||||||
if !ok {
|
|
||||||
return "", fmt.Errorf("invalid address")
|
|
||||||
}
|
|
||||||
|
|
||||||
crit.Addresses = append(crit.Addresses, common.HexToAddress(address))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if params["topics"] != nil {
|
|
||||||
topics, ok := params["topics"].([]interface{})
|
|
||||||
if !ok {
|
|
||||||
return "", fmt.Errorf("invalid topics")
|
|
||||||
}
|
|
||||||
|
|
||||||
crit.Topics = [][]common.Hash{}
|
|
||||||
for _, topic := range topics {
|
|
||||||
tstr, ok := topic.(string)
|
|
||||||
if !ok {
|
|
||||||
return "", fmt.Errorf("invalid topics")
|
|
||||||
}
|
|
||||||
|
|
||||||
h := common.HexToHash(tstr)
|
|
||||||
crit.Topics = append(crit.Topics, []common.Hash{h})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sub, _, err := api.events.SubscribeLogs(crit)
|
|
||||||
if err != nil {
|
|
||||||
return rpc.ID(""), err
|
|
||||||
}
|
|
||||||
|
|
||||||
unsubscribed := make(chan struct{})
|
|
||||||
api.filtersMu.Lock()
|
|
||||||
api.filters[sub.ID()] = &wsSubscription{
|
|
||||||
sub: sub,
|
|
||||||
conn: conn,
|
|
||||||
unsubscribed: unsubscribed,
|
|
||||||
}
|
|
||||||
api.filtersMu.Unlock()
|
|
||||||
|
|
||||||
go func(ch <-chan coretypes.ResultEvent, errCh <-chan error) {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case event := <-ch:
|
|
||||||
dataTx, ok := event.Data.(tmtypes.EventDataTx)
|
|
||||||
if !ok {
|
|
||||||
err = fmt.Errorf("invalid event data %T, expected EventDataTx", event.Data)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var resultData evmtypes.ResultData
|
|
||||||
resultData, err = evmtypes.DecodeResultData(dataTx.TxResult.Result.Data)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
logs := filterLogs(resultData.Logs, crit.FromBlock, crit.ToBlock, crit.Addresses, crit.Topics)
|
|
||||||
|
|
||||||
api.filtersMu.Lock()
|
|
||||||
if f, found := api.filters[sub.ID()]; found {
|
|
||||||
// write to ws conn
|
|
||||||
res := &SubscriptionNotification{
|
|
||||||
Jsonrpc: "2.0",
|
|
||||||
Method: "eth_subscription",
|
|
||||||
Params: &SubscriptionResult{
|
|
||||||
Subscription: sub.ID(),
|
|
||||||
Result: logs,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
err = f.conn.WriteJSON(res)
|
|
||||||
}
|
|
||||||
api.filtersMu.Unlock()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("failed to write header: %w", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case <-errCh:
|
|
||||||
api.filtersMu.Lock()
|
|
||||||
delete(api.filters, sub.ID())
|
|
||||||
api.filtersMu.Unlock()
|
|
||||||
return
|
|
||||||
case <-unsubscribed:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}(sub.eventCh, sub.Err())
|
|
||||||
|
|
||||||
return sub.ID(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *pubSubAPI) subscribePendingTransactions(conn *websocket.Conn) (rpc.ID, error) {
|
|
||||||
sub, _, err := api.events.SubscribePendingTxs()
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("error creating block filter: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
unsubscribed := make(chan struct{})
|
|
||||||
api.filtersMu.Lock()
|
|
||||||
api.filters[sub.ID()] = &wsSubscription{
|
|
||||||
sub: sub,
|
|
||||||
conn: conn,
|
|
||||||
unsubscribed: unsubscribed,
|
|
||||||
}
|
|
||||||
api.filtersMu.Unlock()
|
|
||||||
|
|
||||||
go func(txsCh <-chan coretypes.ResultEvent, errCh <-chan error) {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case ev := <-txsCh:
|
|
||||||
data, _ := ev.Data.(tmtypes.EventDataTx)
|
|
||||||
txHash := common.BytesToHash(data.Tx.Hash())
|
|
||||||
|
|
||||||
api.filtersMu.Lock()
|
|
||||||
if f, found := api.filters[sub.ID()]; found {
|
|
||||||
// write to ws conn
|
|
||||||
res := &SubscriptionNotification{
|
|
||||||
Jsonrpc: "2.0",
|
|
||||||
Method: "eth_subscription",
|
|
||||||
Params: &SubscriptionResult{
|
|
||||||
Subscription: sub.ID(),
|
|
||||||
Result: txHash,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
err = f.conn.WriteJSON(res)
|
|
||||||
}
|
|
||||||
api.filtersMu.Unlock()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
err = fmt.Errorf("failed to write header: %w", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case <-errCh:
|
|
||||||
api.filtersMu.Lock()
|
|
||||||
delete(api.filters, sub.ID())
|
|
||||||
api.filtersMu.Unlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}(sub.eventCh, sub.Err())
|
|
||||||
|
|
||||||
return sub.ID(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *pubSubAPI) subscribeSyncing(conn *websocket.Conn) (rpc.ID, error) {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
309
rpc/websockets/pubsub_api.go
Normal file
309
rpc/websockets/pubsub_api.go
Normal file
@ -0,0 +1,309 @@
|
|||||||
|
package websockets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
|
||||||
|
"github.com/tendermint/tendermint/libs/log"
|
||||||
|
coretypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||||
|
tmtypes "github.com/tendermint/tendermint/types"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/eth/filters"
|
||||||
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
|
||||||
|
context "github.com/cosmos/cosmos-sdk/client/context"
|
||||||
|
|
||||||
|
rpcfilters "github.com/cosmos/ethermint/rpc/namespaces/eth/filters"
|
||||||
|
rpctypes "github.com/cosmos/ethermint/rpc/types"
|
||||||
|
evmtypes "github.com/cosmos/ethermint/x/evm/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PubSubAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec
|
||||||
|
type PubSubAPI struct {
|
||||||
|
clientCtx context.CLIContext
|
||||||
|
events *rpcfilters.EventSystem
|
||||||
|
filtersMu sync.Mutex
|
||||||
|
filters map[rpc.ID]*wsSubscription
|
||||||
|
logger log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAPI creates an instance of the ethereum PubSub API.
|
||||||
|
func NewAPI(clientCtx context.CLIContext) *PubSubAPI {
|
||||||
|
return &PubSubAPI{
|
||||||
|
clientCtx: clientCtx,
|
||||||
|
events: rpcfilters.NewEventSystem(clientCtx.Client),
|
||||||
|
filters: make(map[rpc.ID]*wsSubscription),
|
||||||
|
logger: log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "websocket-client"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *PubSubAPI) subscribe(conn *websocket.Conn, params []interface{}) (rpc.ID, error) {
|
||||||
|
method, ok := params[0].(string)
|
||||||
|
if !ok {
|
||||||
|
return "0", fmt.Errorf("invalid parameters")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch method {
|
||||||
|
case "newHeads":
|
||||||
|
// TODO: handle extra params
|
||||||
|
return api.subscribeNewHeads(conn)
|
||||||
|
case "logs":
|
||||||
|
if len(params) > 1 {
|
||||||
|
return api.subscribeLogs(conn, params[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
return api.subscribeLogs(conn, nil)
|
||||||
|
case "newPendingTransactions":
|
||||||
|
return api.subscribePendingTransactions(conn)
|
||||||
|
case "syncing":
|
||||||
|
return api.subscribeSyncing(conn)
|
||||||
|
default:
|
||||||
|
return "0", fmt.Errorf("unsupported method %s", method)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *PubSubAPI) unsubscribe(id rpc.ID) bool {
|
||||||
|
api.filtersMu.Lock()
|
||||||
|
defer api.filtersMu.Unlock()
|
||||||
|
|
||||||
|
if api.filters[id] == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
close(api.filters[id].unsubscribed)
|
||||||
|
delete(api.filters, id)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *PubSubAPI) subscribeNewHeads(conn *websocket.Conn) (rpc.ID, error) {
|
||||||
|
sub, _, err := api.events.SubscribeNewHeads()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error creating block filter: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
unsubscribed := make(chan struct{})
|
||||||
|
api.filtersMu.Lock()
|
||||||
|
api.filters[sub.ID()] = &wsSubscription{
|
||||||
|
sub: sub,
|
||||||
|
conn: conn,
|
||||||
|
unsubscribed: unsubscribed,
|
||||||
|
}
|
||||||
|
api.filtersMu.Unlock()
|
||||||
|
|
||||||
|
go func(headersCh <-chan coretypes.ResultEvent, errCh <-chan error) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event := <-headersCh:
|
||||||
|
data, _ := event.Data.(tmtypes.EventDataNewBlockHeader)
|
||||||
|
header := rpctypes.EthHeaderFromTendermint(data.Header)
|
||||||
|
|
||||||
|
api.filtersMu.Lock()
|
||||||
|
if f, found := api.filters[sub.ID()]; found {
|
||||||
|
// write to ws conn
|
||||||
|
res := &SubscriptionNotification{
|
||||||
|
Jsonrpc: "2.0",
|
||||||
|
Method: "eth_subscription",
|
||||||
|
Params: &SubscriptionResult{
|
||||||
|
Subscription: sub.ID(),
|
||||||
|
Result: header,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = f.conn.WriteJSON(res)
|
||||||
|
if err != nil {
|
||||||
|
api.logger.Error("error writing header")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
api.filtersMu.Unlock()
|
||||||
|
case <-errCh:
|
||||||
|
api.filtersMu.Lock()
|
||||||
|
delete(api.filters, sub.ID())
|
||||||
|
api.filtersMu.Unlock()
|
||||||
|
return
|
||||||
|
case <-unsubscribed:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(sub.Event(), sub.Err())
|
||||||
|
|
||||||
|
return sub.ID(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *PubSubAPI) subscribeLogs(conn *websocket.Conn, extra interface{}) (rpc.ID, error) {
|
||||||
|
crit := filters.FilterCriteria{}
|
||||||
|
|
||||||
|
if extra != nil {
|
||||||
|
params, ok := extra.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("invalid criteria")
|
||||||
|
}
|
||||||
|
|
||||||
|
if params["address"] != nil {
|
||||||
|
address, ok := params["address"].(string)
|
||||||
|
addresses, sok := params["address"].([]interface{})
|
||||||
|
if !ok && !sok {
|
||||||
|
return "", fmt.Errorf("invalid address; must be address or array of addresses")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
crit.Addresses = []common.Address{common.HexToAddress(address)}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sok {
|
||||||
|
crit.Addresses = []common.Address{}
|
||||||
|
for _, addr := range addresses {
|
||||||
|
address, ok := addr.(string)
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("invalid address")
|
||||||
|
}
|
||||||
|
|
||||||
|
crit.Addresses = append(crit.Addresses, common.HexToAddress(address))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if params["topics"] != nil {
|
||||||
|
topics, ok := params["topics"].([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("invalid topics")
|
||||||
|
}
|
||||||
|
|
||||||
|
crit.Topics = [][]common.Hash{}
|
||||||
|
for _, topic := range topics {
|
||||||
|
tstr, ok := topic.(string)
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("invalid topics")
|
||||||
|
}
|
||||||
|
|
||||||
|
h := common.HexToHash(tstr)
|
||||||
|
crit.Topics = append(crit.Topics, []common.Hash{h})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub, _, err := api.events.SubscribeLogs(crit)
|
||||||
|
if err != nil {
|
||||||
|
return rpc.ID(""), err
|
||||||
|
}
|
||||||
|
|
||||||
|
unsubscribed := make(chan struct{})
|
||||||
|
api.filtersMu.Lock()
|
||||||
|
api.filters[sub.ID()] = &wsSubscription{
|
||||||
|
sub: sub,
|
||||||
|
conn: conn,
|
||||||
|
unsubscribed: unsubscribed,
|
||||||
|
}
|
||||||
|
api.filtersMu.Unlock()
|
||||||
|
|
||||||
|
go func(ch <-chan coretypes.ResultEvent, errCh <-chan error) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case event := <-ch:
|
||||||
|
dataTx, ok := event.Data.(tmtypes.EventDataTx)
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("invalid event data %T, expected EventDataTx", event.Data)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var resultData evmtypes.ResultData
|
||||||
|
resultData, err = evmtypes.DecodeResultData(dataTx.TxResult.Result.Data)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logs := rpcfilters.FilterLogs(resultData.Logs, crit.FromBlock, crit.ToBlock, crit.Addresses, crit.Topics)
|
||||||
|
|
||||||
|
api.filtersMu.Lock()
|
||||||
|
if f, found := api.filters[sub.ID()]; found {
|
||||||
|
// write to ws conn
|
||||||
|
res := &SubscriptionNotification{
|
||||||
|
Jsonrpc: "2.0",
|
||||||
|
Method: "eth_subscription",
|
||||||
|
Params: &SubscriptionResult{
|
||||||
|
Subscription: sub.ID(),
|
||||||
|
Result: logs,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = f.conn.WriteJSON(res)
|
||||||
|
}
|
||||||
|
api.filtersMu.Unlock()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to write header: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case <-errCh:
|
||||||
|
api.filtersMu.Lock()
|
||||||
|
delete(api.filters, sub.ID())
|
||||||
|
api.filtersMu.Unlock()
|
||||||
|
return
|
||||||
|
case <-unsubscribed:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(sub.Event(), sub.Err())
|
||||||
|
|
||||||
|
return sub.ID(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *PubSubAPI) subscribePendingTransactions(conn *websocket.Conn) (rpc.ID, error) {
|
||||||
|
sub, _, err := api.events.SubscribePendingTxs()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("error creating block filter: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
unsubscribed := make(chan struct{})
|
||||||
|
api.filtersMu.Lock()
|
||||||
|
api.filters[sub.ID()] = &wsSubscription{
|
||||||
|
sub: sub,
|
||||||
|
conn: conn,
|
||||||
|
unsubscribed: unsubscribed,
|
||||||
|
}
|
||||||
|
api.filtersMu.Unlock()
|
||||||
|
|
||||||
|
go func(txsCh <-chan coretypes.ResultEvent, errCh <-chan error) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case ev := <-txsCh:
|
||||||
|
data, _ := ev.Data.(tmtypes.EventDataTx)
|
||||||
|
txHash := common.BytesToHash(data.Tx.Hash())
|
||||||
|
|
||||||
|
api.filtersMu.Lock()
|
||||||
|
if f, found := api.filters[sub.ID()]; found {
|
||||||
|
// write to ws conn
|
||||||
|
res := &SubscriptionNotification{
|
||||||
|
Jsonrpc: "2.0",
|
||||||
|
Method: "eth_subscription",
|
||||||
|
Params: &SubscriptionResult{
|
||||||
|
Subscription: sub.ID(),
|
||||||
|
Result: txHash,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = f.conn.WriteJSON(res)
|
||||||
|
}
|
||||||
|
api.filtersMu.Unlock()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("failed to write header: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case <-errCh:
|
||||||
|
api.filtersMu.Lock()
|
||||||
|
delete(api.filters, sub.ID())
|
||||||
|
api.filtersMu.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(sub.Event(), sub.Err())
|
||||||
|
|
||||||
|
return sub.ID(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *PubSubAPI) subscribeSyncing(conn *websocket.Conn) (rpc.ID, error) {
|
||||||
|
return "", nil
|
||||||
|
}
|
219
rpc/websockets/server.go
Normal file
219
rpc/websockets/server.go
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
package websockets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"math/big"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
|
"github.com/tendermint/tendermint/libs/log"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
|
||||||
|
context "github.com/cosmos/cosmos-sdk/client/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Server defines a server that handles Ethereum websockets.
|
||||||
|
type Server struct {
|
||||||
|
rpcAddr string // listen address of rest-server
|
||||||
|
wsAddr string // listen address of ws server
|
||||||
|
api *PubSubAPI
|
||||||
|
logger log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServer creates a new websocket server instance.
|
||||||
|
func NewServer(clientCtx context.CLIContext, wsAddr string) *Server {
|
||||||
|
return &Server{
|
||||||
|
rpcAddr: viper.GetString("laddr"),
|
||||||
|
wsAddr: wsAddr,
|
||||||
|
api: NewAPI(clientCtx),
|
||||||
|
logger: log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "websocket-server"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start runs the websocket server
|
||||||
|
func (s *Server) Start() {
|
||||||
|
ws := mux.NewRouter()
|
||||||
|
ws.Handle("/", s)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
err := http.ListenAndServe(fmt.Sprintf(":%s", s.wsAddr), ws)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("http error:", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var upgrader = websocket.Upgrader{
|
||||||
|
CheckOrigin: func(r *http.Request) bool {
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
wsConn, err := upgrader.Upgrade(w, r, nil)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("websocket upgrade failed; error:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
s.readLoop(wsConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) sendErrResponse(conn *websocket.Conn, msg string) {
|
||||||
|
res := &ErrorResponseJSON{
|
||||||
|
Jsonrpc: "2.0",
|
||||||
|
Error: &ErrorMessageJSON{
|
||||||
|
Code: big.NewInt(-32600),
|
||||||
|
Message: msg,
|
||||||
|
},
|
||||||
|
ID: nil,
|
||||||
|
}
|
||||||
|
err := conn.WriteJSON(res)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("websocket failed write message", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) readLoop(wsConn *websocket.Conn) {
|
||||||
|
for {
|
||||||
|
_, mb, err := wsConn.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
_ = wsConn.Close()
|
||||||
|
s.logger.Error("failed to read message; error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var msg map[string]interface{}
|
||||||
|
err = json.Unmarshal(mb, &msg)
|
||||||
|
if err != nil {
|
||||||
|
s.sendErrResponse(wsConn, "invalid request")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if method == eth_subscribe or eth_unsubscribe
|
||||||
|
method := msg["method"]
|
||||||
|
if method.(string) == "eth_subscribe" {
|
||||||
|
params := msg["params"].([]interface{})
|
||||||
|
if len(params) == 0 {
|
||||||
|
s.sendErrResponse(wsConn, "invalid parameters")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err := s.api.subscribe(wsConn, params)
|
||||||
|
if err != nil {
|
||||||
|
s.sendErrResponse(wsConn, err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
res := &SubscriptionResponseJSON{
|
||||||
|
Jsonrpc: "2.0",
|
||||||
|
ID: 1,
|
||||||
|
Result: id,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = wsConn.WriteJSON(res)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("failed to write json response", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
} else if method.(string) == "eth_unsubscribe" {
|
||||||
|
ids, ok := msg["params"].([]interface{})
|
||||||
|
if _, idok := ids[0].(string); !ok || !idok {
|
||||||
|
s.sendErrResponse(wsConn, "invalid parameters")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ok = s.api.unsubscribe(rpc.ID(ids[0].(string)))
|
||||||
|
res := &SubscriptionResponseJSON{
|
||||||
|
Jsonrpc: "2.0",
|
||||||
|
ID: 1,
|
||||||
|
Result: ok,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = wsConn.WriteJSON(res)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("failed to write json response", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, call the usual rpc server to respond
|
||||||
|
err = s.tcpGetAndSendResponse(wsConn, mb)
|
||||||
|
if err != nil {
|
||||||
|
s.sendErrResponse(wsConn, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tcpGetAndSendResponse connects to the rest-server over tcp, posts a JSON-RPC request, and sends the response
|
||||||
|
// to the client over websockets
|
||||||
|
func (s *Server) tcpGetAndSendResponse(conn *websocket.Conn, mb []byte) error {
|
||||||
|
addr := strings.Split(s.rpcAddr, "tcp://")
|
||||||
|
if len(addr) != 2 {
|
||||||
|
return fmt.Errorf("invalid laddr %s", s.rpcAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
tcpConn, err := net.Dial("tcp", addr[1])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot connect to %s; %s", s.rpcAddr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
_, err = buf.Write(mb)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to write message; %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", s.rpcAddr, buf)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to request; %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Content-Type", "application/json;")
|
||||||
|
err = req.Write(tcpConn)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to write to rest-server; %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
respBytes, err := ioutil.ReadAll(tcpConn)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error reading response from rest-server; %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
respbuf := &bytes.Buffer{}
|
||||||
|
respbuf.Write(respBytes)
|
||||||
|
resp, err := http.ReadResponse(bufio.NewReader(respbuf), req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not read response; %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("could not read body from response; %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var wsSend interface{}
|
||||||
|
err = json.Unmarshal(body, &wsSend)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to unmarshal rest-server response; %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return conn.WriteJSON(wsSend)
|
||||||
|
}
|
45
rpc/websockets/types.go
Normal file
45
rpc/websockets/types.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package websockets
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
|
||||||
|
rpcfilters "github.com/cosmos/ethermint/rpc/namespaces/eth/filters"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SubscriptionResponseJSON struct {
|
||||||
|
Jsonrpc string `json:"jsonrpc"`
|
||||||
|
Result interface{} `json:"result"`
|
||||||
|
ID float64 `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubscriptionNotification struct {
|
||||||
|
Jsonrpc string `json:"jsonrpc"`
|
||||||
|
Method string `json:"method"`
|
||||||
|
Params *SubscriptionResult `json:"params"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubscriptionResult struct {
|
||||||
|
Subscription rpc.ID `json:"subscription"`
|
||||||
|
Result interface{} `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrorResponseJSON struct {
|
||||||
|
Jsonrpc string `json:"jsonrpc"`
|
||||||
|
Error *ErrorMessageJSON `json:"error"`
|
||||||
|
ID *big.Int `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrorMessageJSON struct {
|
||||||
|
Code *big.Int `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type wsSubscription struct {
|
||||||
|
sub *rpcfilters.Subscription
|
||||||
|
unsubscribed chan struct{} // closed when unsubscribing
|
||||||
|
conn *websocket.Conn
|
||||||
|
}
|
@ -14,7 +14,6 @@ import (
|
|||||||
"math/big"
|
"math/big"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -24,9 +23,8 @@ 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/cosmos/ethermint/rpc"
|
rpctypes "github.com/cosmos/ethermint/rpc/types"
|
||||||
"github.com/cosmos/ethermint/version"
|
"github.com/cosmos/ethermint/version"
|
||||||
"github.com/cosmos/ethermint/x/evm/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -191,7 +189,14 @@ func TestEth_GetLogs_NoLogs(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)
|
||||||
param[0]["topics"] = []string{}
|
param[0]["topics"] = []string{}
|
||||||
call(t, "eth_getLogs", param)
|
rpcRes := call(t, "eth_getLogs", param)
|
||||||
|
require.NotNil(t, rpcRes)
|
||||||
|
require.Nil(t, rpcRes.Error)
|
||||||
|
|
||||||
|
var logs []*ethtypes.Log
|
||||||
|
err := json.Unmarshal(rpcRes.Result, &logs)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, logs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEth_GetLogs_Topics_AB(t *testing.T) {
|
func TestEth_GetLogs_Topics_AB(t *testing.T) {
|
||||||
@ -332,7 +337,7 @@ func TestEth_GetProof(t *testing.T) {
|
|||||||
rpcRes := call(t, "eth_getProof", params)
|
rpcRes := call(t, "eth_getProof", params)
|
||||||
require.NotNil(t, rpcRes)
|
require.NotNil(t, rpcRes)
|
||||||
|
|
||||||
var accRes rpc.AccountResult
|
var accRes rpctypes.AccountResult
|
||||||
err := json.Unmarshal(rpcRes.Result, &accRes)
|
err := json.Unmarshal(rpcRes.Result, &accRes)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotEmpty(t, accRes.AccountProof)
|
require.NotEmpty(t, accRes.AccountProof)
|
||||||
@ -779,68 +784,6 @@ func TestEth_EstimateGas_ContractDeployment(t *testing.T) {
|
|||||||
require.Equal(t, "0x1c2c4", gas.String())
|
require.Equal(t, "0x1c2c4", gas.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEth_ExportAccount(t *testing.T) {
|
|
||||||
param := []string{}
|
|
||||||
param = append(param, "0x1122334455667788990011223344556677889901")
|
|
||||||
param = append(param, "latest")
|
|
||||||
rpcRes := call(t, "eth_exportAccount", param)
|
|
||||||
|
|
||||||
var res string
|
|
||||||
err := json.Unmarshal(rpcRes.Result, &res)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var account types.GenesisAccount
|
|
||||||
err = json.Unmarshal([]byte(res), &account)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, "0x1122334455667788990011223344556677889901", account.Address.Hex())
|
|
||||||
require.Equal(t, big.NewInt(0), account.Balance)
|
|
||||||
require.Equal(t, hexutil.Bytes(nil), account.Code)
|
|
||||||
require.Equal(t, types.Storage(nil), account.Storage)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEth_ExportAccount_WithStorage(t *testing.T) {
|
|
||||||
hash := deployTestContractWithFunction(t)
|
|
||||||
receipt := waitForReceipt(t, hash)
|
|
||||||
addr := receipt["contractAddress"].(string)
|
|
||||||
|
|
||||||
// call function to set storage
|
|
||||||
calldata := "0xeb8ac92100000000000000000000000000000000000000000000000000000000000000630000000000000000000000000000000000000000000000000000000000000000"
|
|
||||||
|
|
||||||
param := make([]map[string]string, 1)
|
|
||||||
param[0] = make(map[string]string)
|
|
||||||
param[0]["from"] = "0x" + fmt.Sprintf("%x", from)
|
|
||||||
param[0]["to"] = addr
|
|
||||||
param[0]["data"] = calldata
|
|
||||||
rpcRes := call(t, "eth_sendTransaction", param)
|
|
||||||
|
|
||||||
var txhash hexutil.Bytes
|
|
||||||
err := json.Unmarshal(rpcRes.Result, &txhash)
|
|
||||||
require.NoError(t, err)
|
|
||||||
waitForReceipt(t, txhash)
|
|
||||||
|
|
||||||
// get exported account
|
|
||||||
eap := []string{}
|
|
||||||
eap = append(eap, addr)
|
|
||||||
eap = append(eap, "latest")
|
|
||||||
rpcRes = call(t, "eth_exportAccount", eap)
|
|
||||||
|
|
||||||
var res string
|
|
||||||
err = json.Unmarshal(rpcRes.Result, &res)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
var account types.GenesisAccount
|
|
||||||
err = json.Unmarshal([]byte(res), &account)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// deployed bytecode
|
|
||||||
bytecode := "0x6080604052348015600f57600080fd5b506004361060285760003560e01c8063eb8ac92114602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506062565b005b8160008190555080827ff3ca124a697ba07e8c5e80bebcfcc48991fc16a63170e8a9206e30508960d00360405160405180910390a3505056fea265627a7a723158201d94d2187aaf3a6790527b615fcc40970febf0385fa6d72a2344848ebd0df3e964736f6c63430005110032"
|
|
||||||
require.Equal(t, addr, strings.ToLower(account.Address.Hex()))
|
|
||||||
require.Equal(t, big.NewInt(0), account.Balance)
|
|
||||||
require.Equal(t, bytecode, account.Code.String())
|
|
||||||
require.NotEqual(t, types.Storage(nil), account.Storage)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEth_GetBlockByNumber(t *testing.T) {
|
func TestEth_GetBlockByNumber(t *testing.T) {
|
||||||
param := []interface{}{"0x1", false}
|
param := []interface{}{"0x1", false}
|
||||||
rpcRes := call(t, "eth_getBlockByNumber", param)
|
rpcRes := call(t, "eth_getBlockByNumber", param)
|
||||||
|
@ -37,7 +37,7 @@ func GetCmdGetStorageAt(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
|||||||
Short: "Gets storage for an account at a given key",
|
Short: "Gets storage for an account at a given key",
|
||||||
Args: cobra.ExactArgs(2),
|
Args: cobra.ExactArgs(2),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
clientCtx := context.NewCLIContext().WithCodec(cdc)
|
||||||
|
|
||||||
account, err := accountToHex(args[0])
|
account, err := accountToHex(args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -46,7 +46,7 @@ func GetCmdGetStorageAt(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
|||||||
|
|
||||||
key := formatKeyToHash(args[1])
|
key := formatKeyToHash(args[1])
|
||||||
|
|
||||||
res, _, err := cliCtx.Query(
|
res, _, err := clientCtx.Query(
|
||||||
fmt.Sprintf("custom/%s/storage/%s/%s", queryRoute, account, key))
|
fmt.Sprintf("custom/%s/storage/%s/%s", queryRoute, account, key))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -54,7 +54,7 @@ func GetCmdGetStorageAt(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
|||||||
}
|
}
|
||||||
var out types.QueryResStorage
|
var out types.QueryResStorage
|
||||||
cdc.MustUnmarshalJSON(res, &out)
|
cdc.MustUnmarshalJSON(res, &out)
|
||||||
return cliCtx.PrintOutput(out)
|
return clientCtx.PrintOutput(out)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -66,14 +66,14 @@ func GetCmdGetCode(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
|||||||
Short: "Gets code from an account",
|
Short: "Gets code from an account",
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
clientCtx := context.NewCLIContext().WithCodec(cdc)
|
||||||
|
|
||||||
account, err := accountToHex(args[0])
|
account, err := accountToHex(args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "could not parse account address")
|
return errors.Wrap(err, "could not parse account address")
|
||||||
}
|
}
|
||||||
|
|
||||||
res, _, err := cliCtx.Query(
|
res, _, err := clientCtx.Query(
|
||||||
fmt.Sprintf("custom/%s/code/%s", queryRoute, account))
|
fmt.Sprintf("custom/%s/code/%s", queryRoute, account))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -82,7 +82,7 @@ func GetCmdGetCode(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
|||||||
|
|
||||||
var out types.QueryResCode
|
var out types.QueryResCode
|
||||||
cdc.MustUnmarshalJSON(res, &out)
|
cdc.MustUnmarshalJSON(res, &out)
|
||||||
return cliCtx.PrintOutput(out)
|
return clientCtx.PrintOutput(out)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,164 +0,0 @@
|
|||||||
package cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
||||||
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/client"
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/context"
|
|
||||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
|
||||||
authclient "github.com/cosmos/cosmos-sdk/x/auth/client/utils"
|
|
||||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
|
||||||
|
|
||||||
emint "github.com/cosmos/ethermint/types"
|
|
||||||
"github.com/cosmos/ethermint/x/evm/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetTxCmd defines the CLI commands regarding evm module transactions
|
|
||||||
func GetTxCmd(cdc *codec.Codec) *cobra.Command {
|
|
||||||
evmTxCmd := &cobra.Command{
|
|
||||||
Use: types.ModuleName,
|
|
||||||
Short: "EVM transaction subcommands",
|
|
||||||
DisableFlagParsing: true,
|
|
||||||
SuggestionsMinimumDistance: 2,
|
|
||||||
RunE: client.ValidateCmd,
|
|
||||||
}
|
|
||||||
|
|
||||||
evmTxCmd.AddCommand(flags.PostCommands(
|
|
||||||
GetCmdSendTx(cdc),
|
|
||||||
GetCmdGenCreateTx(cdc),
|
|
||||||
)...)
|
|
||||||
|
|
||||||
return evmTxCmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCmdSendTx generates an Ethermint transaction (excludes create operations)
|
|
||||||
func GetCmdSendTx(cdc *codec.Codec) *cobra.Command {
|
|
||||||
return &cobra.Command{
|
|
||||||
Use: "send [to_address] [amount (in aphotons)] [<data>]",
|
|
||||||
Short: "send transaction to address (call operations included)",
|
|
||||||
Args: cobra.RangeArgs(2, 3),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
|
||||||
inBuf := bufio.NewReader(cmd.InOrStdin())
|
|
||||||
|
|
||||||
txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(authclient.GetTxEncoder(cdc))
|
|
||||||
|
|
||||||
toAddr, err := cosmosAddressFromArg(args[0])
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "must provide a valid Bech32 address for to_address")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ambiguously decode amount from any base
|
|
||||||
amount, err := strconv.ParseInt(args[1], 0, 64)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var data []byte
|
|
||||||
if len(args) > 2 {
|
|
||||||
payload := args[2]
|
|
||||||
if !strings.HasPrefix(payload, "0x") {
|
|
||||||
payload = "0x" + payload
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err = hexutil.Decode(payload)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
from := cliCtx.GetFromAddress()
|
|
||||||
|
|
||||||
_, seq, err := authtypes.NewAccountRetriever(cliCtx).GetAccountNumberSequence(from)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "Could not retrieve account sequence")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Potentially allow overriding of gas price and gas limit
|
|
||||||
msg := types.NewMsgEthermint(seq, &toAddr, sdk.NewInt(amount), txBldr.Gas(),
|
|
||||||
sdk.NewInt(emint.DefaultGasPrice), data, from)
|
|
||||||
|
|
||||||
err = msg.ValidateBasic()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return authclient.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCmdGenCreateTx generates an Ethermint transaction (excludes create operations)
|
|
||||||
func GetCmdGenCreateTx(cdc *codec.Codec) *cobra.Command {
|
|
||||||
return &cobra.Command{
|
|
||||||
Use: "create [contract bytecode] [<amount (in aphotons)>]",
|
|
||||||
Short: "create contract through the evm using compiled bytecode",
|
|
||||||
Args: cobra.RangeArgs(1, 2),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
|
||||||
inBuf := bufio.NewReader(cmd.InOrStdin())
|
|
||||||
|
|
||||||
txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(authclient.GetTxEncoder(cdc))
|
|
||||||
|
|
||||||
payload := args[0]
|
|
||||||
if !strings.HasPrefix(payload, "0x") {
|
|
||||||
payload = "0x" + payload
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := hexutil.Decode(payload)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var amount int64
|
|
||||||
if len(args) > 1 {
|
|
||||||
// Ambiguously decode amount from any base
|
|
||||||
amount, err = strconv.ParseInt(args[1], 0, 64)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "invalid amount")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
from := cliCtx.GetFromAddress()
|
|
||||||
|
|
||||||
_, seq, err := authtypes.NewAccountRetriever(cliCtx).GetAccountNumberSequence(from)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "Could not retrieve account sequence")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Potentially allow overriding of gas price and gas limit
|
|
||||||
msg := types.NewMsgEthermint(seq, nil, sdk.NewInt(amount), txBldr.Gas(),
|
|
||||||
sdk.NewInt(emint.DefaultGasPrice), data, from)
|
|
||||||
|
|
||||||
err = msg.ValidateBasic()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = authclient.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
contractAddr := ethcrypto.CreateAddress(common.BytesToAddress(from.Bytes()), seq)
|
|
||||||
fmt.Printf(
|
|
||||||
"Contract will be deployed to: \nHex: %s\nCosmos Address: %s\n",
|
|
||||||
contractAddr.Hex(),
|
|
||||||
sdk.AccAddress(contractAddr.Bytes()),
|
|
||||||
)
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
@ -45,19 +45,3 @@ func formatKeyToHash(key string) string {
|
|||||||
|
|
||||||
return ethkey.Hex()
|
return ethkey.Hex()
|
||||||
}
|
}
|
||||||
|
|
||||||
func cosmosAddressFromArg(addr string) (sdk.AccAddress, error) {
|
|
||||||
if strings.HasPrefix(addr, sdk.GetConfig().GetBech32AccountAddrPrefix()) {
|
|
||||||
// Check to see if address is Cosmos bech32 formatted
|
|
||||||
toAddr, err := sdk.AccAddressFromBech32(addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "invalid bech32 formatted address")
|
|
||||||
}
|
|
||||||
return toAddr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strip 0x prefix if exists
|
|
||||||
addr = strings.TrimPrefix(addr, "0x")
|
|
||||||
|
|
||||||
return sdk.AccAddressFromHex(addr)
|
|
||||||
}
|
|
||||||
|
@ -61,24 +61,3 @@ func TestCosmosToEthereumTypes(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, hexString, ethDecoded)
|
require.Equal(t, hexString, ethDecoded)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddressToCosmosAddress(t *testing.T) {
|
|
||||||
baseAddr, err := sdk.AccAddressFromHex("6A98D72760f7bbA69d62Ed6F48278451251948E7")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Test cosmos string back to address
|
|
||||||
cosmosFormatted, err := cosmosAddressFromArg(baseAddr.String())
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, baseAddr, cosmosFormatted)
|
|
||||||
|
|
||||||
// Test account address from Ethereum address
|
|
||||||
ethAddr := common.BytesToAddress(baseAddr.Bytes())
|
|
||||||
ethFormatted, err := cosmosAddressFromArg(ethAddr.Hex())
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, baseAddr, ethFormatted)
|
|
||||||
|
|
||||||
// Test encoding without the 0x prefix
|
|
||||||
ethFormatted, err = cosmosAddressFromArg(ethAddr.Hex()[2:])
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, baseAddr, ethFormatted)
|
|
||||||
}
|
|
||||||
|
@ -52,7 +52,6 @@ func (AppModuleBasic) ValidateGenesis(bz json.RawMessage) error {
|
|||||||
|
|
||||||
// RegisterRESTRoutes Registers rest routes
|
// RegisterRESTRoutes Registers rest routes
|
||||||
func (AppModuleBasic) RegisterRESTRoutes(ctx context.CLIContext, rtr *mux.Router) {
|
func (AppModuleBasic) RegisterRESTRoutes(ctx context.CLIContext, rtr *mux.Router) {
|
||||||
//rpc.RegisterRoutes(ctx, rtr, StoreKey)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetQueryCmd Gets the root query command of this module
|
// GetQueryCmd Gets the root query command of this module
|
||||||
@ -62,7 +61,7 @@ func (AppModuleBasic) GetQueryCmd(cdc *codec.Codec) *cobra.Command {
|
|||||||
|
|
||||||
// GetTxCmd Gets the root tx command of this module
|
// GetTxCmd Gets the root tx command of this module
|
||||||
func (AppModuleBasic) GetTxCmd(cdc *codec.Codec) *cobra.Command {
|
func (AppModuleBasic) GetTxCmd(cdc *codec.Codec) *cobra.Command {
|
||||||
return cli.GetTxCmd(cdc)
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//____________________________________________________________________________
|
//____________________________________________________________________________
|
||||||
|
@ -37,17 +37,17 @@ func GetCmdFunded(cdc *codec.Codec) *cobra.Command {
|
|||||||
Short: "Gets storage for an account at a given key",
|
Short: "Gets storage for an account at a given key",
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
clientCtx := context.NewCLIContext().WithCodec(cdc)
|
||||||
|
|
||||||
res, height, err := cliCtx.Query(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryFunded))
|
res, height, err := clientCtx.Query(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryFunded))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var out sdk.Coins
|
var out sdk.Coins
|
||||||
cdc.MustUnmarshalJSON(res, &out)
|
cdc.MustUnmarshalJSON(res, &out)
|
||||||
cliCtx = cliCtx.WithHeight(height)
|
clientCtx = clientCtx.WithHeight(height)
|
||||||
return cliCtx.PrintOutput(out)
|
return clientCtx.PrintOutput(out)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ func GetCmdRequest(cdc *codec.Codec) *cobra.Command {
|
|||||||
Args: cobra.RangeArgs(1, 2),
|
Args: cobra.RangeArgs(1, 2),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
inBuf := bufio.NewReader(cmd.InOrStdin())
|
inBuf := bufio.NewReader(cmd.InOrStdin())
|
||||||
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
clientCtx := context.NewCLIContext().WithCodec(cdc)
|
||||||
txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(authclient.GetTxEncoder(cdc))
|
txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(authclient.GetTxEncoder(cdc))
|
||||||
|
|
||||||
amount, err := sdk.ParseCoins(args[0])
|
amount, err := sdk.ParseCoins(args[0])
|
||||||
@ -51,7 +51,7 @@ func GetCmdRequest(cdc *codec.Codec) *cobra.Command {
|
|||||||
|
|
||||||
var recipient sdk.AccAddress
|
var recipient sdk.AccAddress
|
||||||
if len(args) == 1 {
|
if len(args) == 1 {
|
||||||
recipient = cliCtx.GetFromAddress()
|
recipient = clientCtx.GetFromAddress()
|
||||||
} else {
|
} else {
|
||||||
recipient, err = sdk.AccAddressFromBech32(args[1])
|
recipient, err = sdk.AccAddressFromBech32(args[1])
|
||||||
}
|
}
|
||||||
@ -60,12 +60,12 @@ func GetCmdRequest(cdc *codec.Codec) *cobra.Command {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := types.NewMsgFund(amount, cliCtx.GetFromAddress(), recipient)
|
msg := types.NewMsgFund(amount, clientCtx.GetFromAddress(), recipient)
|
||||||
if err := msg.ValidateBasic(); err != nil {
|
if err := msg.ValidateBasic(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return authclient.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
|
return authclient.GenerateOrBroadcastMsgs(clientCtx, txBldr, []sdk.Msg{msg})
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,9 +15,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// RegisterRoutes register REST endpoints for the faucet module
|
// RegisterRoutes register REST endpoints for the faucet module
|
||||||
func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) {
|
func RegisterRoutes(clientCtx context.CLIContext, r *mux.Router) {
|
||||||
r.HandleFunc(fmt.Sprintf("/%s/request", types.ModuleName), requestHandler(cliCtx)).Methods("POST")
|
r.HandleFunc(fmt.Sprintf("/%s/request", types.ModuleName), requestHandler(clientCtx)).Methods("POST")
|
||||||
r.HandleFunc(fmt.Sprintf("/%s/funded", types.ModuleName), fundedHandlerFn(cliCtx)).Methods("GET")
|
r.HandleFunc(fmt.Sprintf("/%s/funded", types.ModuleName), fundedHandlerFn(clientCtx)).Methods("GET")
|
||||||
}
|
}
|
||||||
|
|
||||||
// PostRequestBody defines fund request's body.
|
// PostRequestBody defines fund request's body.
|
||||||
@ -27,10 +27,10 @@ type PostRequestBody struct {
|
|||||||
Recipient string `json:"receipient" yaml:"receipient"`
|
Recipient string `json:"receipient" yaml:"receipient"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func requestHandler(cliCtx context.CLIContext) http.HandlerFunc {
|
func requestHandler(clientCtx context.CLIContext) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
var req PostRequestBody
|
var req PostRequestBody
|
||||||
if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) {
|
if !rest.ReadRESTReq(w, r, clientCtx.Codec, &req) {
|
||||||
rest.WriteErrorResponse(w, http.StatusBadRequest, "failed to parse request")
|
rest.WriteErrorResponse(w, http.StatusBadRequest, "failed to parse request")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -65,19 +65,19 @@ func requestHandler(cliCtx context.CLIContext) http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
authclient.WriteGenerateStdTxResponse(w, cliCtx, baseReq, []sdk.Msg{msg})
|
authclient.WriteGenerateStdTxResponse(w, clientCtx, baseReq, []sdk.Msg{msg})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func fundedHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
func fundedHandlerFn(clientCtx context.CLIContext) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, _ *http.Request) {
|
return func(w http.ResponseWriter, _ *http.Request) {
|
||||||
res, height, err := cliCtx.Query(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryFunded))
|
res, height, err := clientCtx.Query(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryFunded))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cliCtx = cliCtx.WithHeight(height)
|
clientCtx = clientCtx.WithHeight(height)
|
||||||
rest.PostProcessResponse(w, cliCtx, res)
|
rest.PostProcessResponse(w, clientCtx, res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user