forked from cerc-io/laconicd-deprecated
9f03ca713d
* wip * rename GetTendermintBlockByNumber to TendermintBlockByNumber * rename GetTendermintBlockResultByNumber to TendermintBlockResultByNumber * rename GetTendermintBlockByHash to TendermintBlockByHash * rename BlockByNumber to EthBlockByNumber * rename BlockByHash to EthBlockByHash * rename GetBlockNumberByHash to BlockNumberFromTendermintByHash * rename GetBlockNumber to BlockNumberFromTendermint * rename GetEthereumMsgsFromTendermintBlock to EthMsgsFromTendermintBlock * rename GetEthBlockFromTendermint to BlockFromTendermintBlock * rename EthBlockFromTendermint to EthBlockFromTendermintBlock * add TestEthBlockFromTendermintBlock with no transactions. Note that this endpoint is breaking when querying a block with transactions * add block transaction count tests * add TendermintBlockByHash test' * add TestBlockNumberFromTendermint tests * add HeaderByHash and HeaderByNumber tests * add EthBlockFromTendermintBlock test * add TestEthBlockByNumber tests * Specificy that the endpoints are getting Etherum transactions in comments * Refactor shared logic into GetBlockTransactionCount * rename BlockFromTendermintBlock to RPCBlockFromTendermintBlock * add CHangelog
207 lines
5.5 KiB
Go
207 lines
5.5 KiB
Go
package backend
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"math/big"
|
|
|
|
sdkmath "cosmossdk.io/math"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
rpctypes "github.com/evmos/ethermint/rpc/types"
|
|
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// GetCode returns the contract code at the given address and block number.
|
|
func (b *Backend) GetCode(address common.Address, blockNrOrHash rpctypes.BlockNumberOrHash) (hexutil.Bytes, error) {
|
|
blockNum, err := b.BlockNumberFromTendermint(blockNrOrHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req := &evmtypes.QueryCodeRequest{
|
|
Address: address.String(),
|
|
}
|
|
|
|
res, err := b.queryClient.Code(rpctypes.ContextWithHeight(blockNum.Int64()), req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return res.Code, nil
|
|
}
|
|
|
|
// GetProof returns an account object with proof and any storage proofs
|
|
func (b *Backend) GetProof(address common.Address, storageKeys []string, blockNrOrHash rpctypes.BlockNumberOrHash) (*rpctypes.AccountResult, error) {
|
|
blockNum, err := b.BlockNumberFromTendermint(blockNrOrHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
height := blockNum.Int64()
|
|
_, err = b.TendermintBlockByNumber(blockNum)
|
|
if err != nil {
|
|
// the error message imitates geth behavior
|
|
return nil, errors.New("header not found")
|
|
}
|
|
ctx := rpctypes.ContextWithHeight(height)
|
|
|
|
// if the height is equal to zero, meaning the query condition of the block is either "pending" or "latest"
|
|
if height == 0 {
|
|
bn, err := b.BlockNumber()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if bn > math.MaxInt64 {
|
|
return nil, fmt.Errorf("not able to query block number greater than MaxInt64")
|
|
}
|
|
|
|
height = int64(bn)
|
|
}
|
|
|
|
clientCtx := b.clientCtx.WithHeight(height)
|
|
|
|
// query storage proofs
|
|
storageProofs := make([]rpctypes.StorageResult, len(storageKeys))
|
|
|
|
for i, key := range storageKeys {
|
|
hexKey := common.HexToHash(key)
|
|
valueBz, proof, err := b.queryClient.GetProof(clientCtx, evmtypes.StoreKey, evmtypes.StateKey(address, hexKey.Bytes()))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// check for proof
|
|
var proofStr string
|
|
if proof != nil {
|
|
proofStr = proof.String()
|
|
}
|
|
|
|
storageProofs[i] = rpctypes.StorageResult{
|
|
Key: key,
|
|
Value: (*hexutil.Big)(new(big.Int).SetBytes(valueBz)),
|
|
Proof: []string{proofStr},
|
|
}
|
|
}
|
|
|
|
// query EVM account
|
|
req := &evmtypes.QueryAccountRequest{
|
|
Address: address.String(),
|
|
}
|
|
|
|
res, err := b.queryClient.Account(ctx, req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// query account proofs
|
|
accountKey := authtypes.AddressStoreKey(sdk.AccAddress(address.Bytes()))
|
|
_, proof, err := b.queryClient.GetProof(clientCtx, authtypes.StoreKey, accountKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// check for proof
|
|
var accProofStr string
|
|
if proof != nil {
|
|
accProofStr = proof.String()
|
|
}
|
|
|
|
balance, ok := sdkmath.NewIntFromString(res.Balance)
|
|
if !ok {
|
|
return nil, errors.New("invalid balance")
|
|
}
|
|
|
|
return &rpctypes.AccountResult{
|
|
Address: address,
|
|
AccountProof: []string{accProofStr},
|
|
Balance: (*hexutil.Big)(balance.BigInt()),
|
|
CodeHash: common.HexToHash(res.CodeHash),
|
|
Nonce: hexutil.Uint64(res.Nonce),
|
|
StorageHash: common.Hash{}, // NOTE: Ethermint doesn't have a storage hash. TODO: implement?
|
|
StorageProof: storageProofs,
|
|
}, nil
|
|
}
|
|
|
|
// GetStorageAt returns the contract storage at the given address, block number, and key.
|
|
func (b *Backend) GetStorageAt(address common.Address, key string, blockNrOrHash rpctypes.BlockNumberOrHash) (hexutil.Bytes, error) {
|
|
blockNum, err := b.BlockNumberFromTendermint(blockNrOrHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req := &evmtypes.QueryStorageRequest{
|
|
Address: address.String(),
|
|
Key: key,
|
|
}
|
|
|
|
res, err := b.queryClient.Storage(rpctypes.ContextWithHeight(blockNum.Int64()), req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
value := common.HexToHash(res.Value)
|
|
return value.Bytes(), nil
|
|
}
|
|
|
|
// GetBalance returns the provided account's balance up to the provided block number.
|
|
func (b *Backend) GetBalance(address common.Address, blockNrOrHash rpctypes.BlockNumberOrHash) (*hexutil.Big, error) {
|
|
blockNum, err := b.BlockNumberFromTendermint(blockNrOrHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req := &evmtypes.QueryBalanceRequest{
|
|
Address: address.String(),
|
|
}
|
|
|
|
_, err = b.TendermintBlockByNumber(blockNum)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
res, err := b.queryClient.Balance(rpctypes.ContextWithHeight(blockNum.Int64()), req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
val, ok := sdkmath.NewIntFromString(res.Balance)
|
|
if !ok {
|
|
return nil, errors.New("invalid balance")
|
|
}
|
|
|
|
// balance can only be negative in case of pruned node
|
|
if val.IsNegative() {
|
|
return nil, errors.New("couldn't fetch balance. Node state is pruned")
|
|
}
|
|
|
|
return (*hexutil.Big)(val.BigInt()), nil
|
|
}
|
|
|
|
// GetTransactionCount returns the number of transactions at the given address up to the given block number.
|
|
func (b *Backend) GetTransactionCount(address common.Address, blockNum rpctypes.BlockNumber) (*hexutil.Uint64, error) {
|
|
// Get nonce (sequence) from account
|
|
from := sdk.AccAddress(address.Bytes())
|
|
accRet := b.clientCtx.AccountRetriever
|
|
|
|
err := accRet.EnsureExists(b.clientCtx, from)
|
|
if err != nil {
|
|
// account doesn't exist yet, return 0
|
|
n := hexutil.Uint64(0)
|
|
return &n, nil
|
|
}
|
|
|
|
includePending := blockNum == rpctypes.EthPendingBlockNumber
|
|
nonce, err := b.getAccountNonce(address, includePending, blockNum.Int64(), b.logger)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
n := hexutil.Uint64(nonce)
|
|
return &n, nil
|
|
}
|