260 lines
7.7 KiB
Go
260 lines
7.7 KiB
Go
|
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
|
||
|
}
|