20e9b2ede3
* rpc: event websocket subscription * rpc: use tendermint event subscriptions * new log events * filter evm transactions * filter logs * wip: refactor filters * remove custom BlockNumber * wip: refactor rpc * HeaderByNumber and HeaderByHash * update Tendermint event system * update Filter * update EventSystem * fix lint issues * update rpc filters * upgrade to tendermint v0.33.4 * update filters * fix unsubscription * updates wip * initialize channels * cleanup go routines * pass ResultEvent channel on subscription * error channel * add block filter changes test * add eventCh loop * pass funcs in select go func, block filter working * cleanup * lint * NewFilter and GetFilterChanges working * eth_getLogs working * lint * lint * cleanup * remove logs and minor fixes * changelog * address @noot comments * revert BlockNumber removal Co-authored-by: noot <elizabethjbinks@gmail.com>
325 lines
9.7 KiB
Go
325 lines
9.7 KiB
Go
package rpc
|
|
|
|
import (
|
|
"fmt"
|
|
"math/big"
|
|
"strconv"
|
|
|
|
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
|
|
gasLimit int64
|
|
}
|
|
|
|
// NewEthermintBackend creates a new EthermintBackend instance
|
|
func NewEthermintBackend(cliCtx context.CLIContext) *EthermintBackend {
|
|
return &EthermintBackend{
|
|
cliCtx: cliCtx,
|
|
gasLimit: int64(^uint32(0)),
|
|
}
|
|
}
|
|
|
|
// BlockNumber returns the current block number.
|
|
func (e *EthermintBackend) BlockNumber() (hexutil.Uint64, error) {
|
|
res, 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{},
|
|
}
|
|
}
|