laconicd/rpc/backend/backend.go
Daniel Choi cb96bc4ea3
Pending (#571)
* add PendingBlockNumber -1

* increase block times

* update bn

* get pending balance

* additional logic to check for pending state

* add multiple balance query

* pending state for getTransactionCount

* fix lint

* add getBlockTransactionCountByNumber code - commented

* cleanup test

* GetBlockTransactionCountByNumber

* cleanup

* getBlockByNumber

* GetTransactionByBlockNumberAndIndex

* conform to namespace changes

* exportable FormatBlock method

* eth_getTransactionByHash

* eth_getTransactionByBlockNumberAndIndex

* pending nonce

* set nonce for pending and check for invalid

* WIP: doCall

* add pending tx test

* cleanup + refactor

* push first tests and init pending

* pending changes (#600)

* cleanup

* updates

* more fixes

* lint

* update call and send

* comments and minor changes

* add pending tests into sep package

* fix latest case for eth_GetBlockTransactionCountByNumber

* fix repeating null transactions in queue

* remove repeated structs

* latestblock case

* revert init script back

* fix to exportable method

* automate pending tests; add make cmd

* move and comment out pending call test

* fix some golint

* fix unlock issue

* wip: linter stringer fix?

* stringer lint

* set arr instead of append

* instantiate with length

* sep if statement

* edit pendingblocknumber note

* switch statement

* fix and update tests

* move tests-pending into tests dir

* remove commented test

* revert to appending pendingtx

* Update tests/utils.go

Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com>

* Update tests/utils.go

Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com>

* Update tests/utils.go

Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com>

* require no err

* rename var

* check result for eth_sendTransaction

* update changelog

* update

* Update tests/utils.go

Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com>

* Update tests/tests-pending/rpc_pending_test.go

Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com>

* changelog

* remove redundant check

Co-authored-by: noot <elizabethjbinks@gmail.com>
Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com>
Co-authored-by: Federico Kunze <federico.kunze94@gmail.com>
2020-12-15 19:52:09 +00:00

269 lines
7.9 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)
LatestBlockNumber() (int64, 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) {
blockNumber, err := b.LatestBlockNumber()
if err != nil {
return hexutil.Uint64(0), err
}
return hexutil.Uint64(blockNumber), 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, 0)
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
}
// LatestBlockNumber gets the latest block height in int64 format.
func (b *EthermintBackend) LatestBlockNumber() (int64, 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 0, err
}
return info.LastHeight, nil
}