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 }