additions

This commit is contained in:
Federico Kunze
2021-04-18 17:54:18 +02:00
parent 5a3d514ba0
commit 614e62fb7e
138 changed files with 11348 additions and 2906 deletions
+12 -8
View File
@@ -3,29 +3,29 @@ package keeper
import (
"math/big"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/cosmos/ethermint/metrics"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
abci "github.com/tendermint/tendermint/abci/types"
)
// BeginBlock sets the block height -> header hash map for the previous block height
// BeginBlock sets the block hash -> block height map for the previous block height
// and resets the Bloom filter and the transaction count to 0.
func (k *Keeper) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) {
if req.Header.LastBlockId.GetHash() == nil || req.Header.GetHeight() < 1 {
if req.Header.Height < 1 {
return
}
// Gas costs are handled within msg handler so costs should be ignored
ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter())
// Set the hash -> height and height -> hash mapping.
currentHash := req.Hash
height := req.Header.GetHeight()
k.SetBlockHash(ctx, req.Hash, req.Header.Height)
k.SetBlockHeightToHash(ctx, req.Hash, req.Header.Height)
k.SetHeightHash(ctx, uint64(height), common.BytesToHash(hash))
// special setter for csdb
k.SetHeightHash(ctx, uint64(req.Header.Height), common.BytesToHash(req.Hash))
// reset counters that are used on CommitStateDB.Prepare
k.Bloom = big.NewInt(0)
@@ -37,6 +37,10 @@ func (k *Keeper) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) {
// the store. The EVM end block logic doesn't update the validator set, thus it returns
// an empty slice.
func (k Keeper) EndBlock(ctx sdk.Context, req abci.RequestEndBlock) []abci.ValidatorUpdate {
metrics.ReportFuncCall(k.svcTags)
doneFn := metrics.ReportFuncTiming(k.svcTags)
defer doneFn()
// Gas costs are handled within msg handler so costs should be ignored
ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter())
-32
View File
@@ -1,32 +0,0 @@
package keeper_test
import (
abci "github.com/tendermint/tendermint/abci/types"
)
func (suite *KeeperTestSuite) TestBeginBlock() {
// update the counters
suite.app.EvmKeeper.Bloom.SetInt64(10)
suite.app.EvmKeeper.TxCount = 10
suite.app.EvmKeeper.BeginBlock(suite.ctx, abci.RequestBeginBlock{})
suite.Require().NotZero(suite.app.EvmKeeper.Bloom.Int64())
suite.Require().NotZero(suite.app.EvmKeeper.TxCount)
}
func (suite *KeeperTestSuite) TestEndBlock() {
// update the counters
suite.app.EvmKeeper.Bloom.SetInt64(10)
// set gas limit to 1 to ensure no gas is consumed during the operation
initialConsumed := suite.ctx.GasMeter().GasConsumed()
_ = suite.app.EvmKeeper.EndBlock(suite.ctx, abci.RequestEndBlock{Height: 100})
suite.Require().Equal(int64(initialConsumed), int64(suite.ctx.GasMeter().GasConsumed()))
bloom, found := suite.app.EvmKeeper.GetBlockBloom(suite.ctx, 100)
suite.Require().True(found)
suite.Require().Equal(int64(10), bloom.Big().Int64())
}
+263 -32
View File
@@ -2,14 +2,17 @@ package keeper
import (
"context"
"math/big"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
sdk "github.com/cosmos/cosmos-sdk/types"
tmtypes "github.com/tendermint/tendermint/types"
ethcmn "github.com/ethereum/go-ethereum/common"
"github.com/cosmos/ethermint/metrics"
ethermint "github.com/cosmos/ethermint/types"
"github.com/cosmos/ethermint/x/evm/types"
)
@@ -17,12 +20,18 @@ import (
var _ types.QueryServer = Keeper{}
// Account implements the Query/Account gRPC method
func (q Keeper) Account(c context.Context, req *types.QueryAccountRequest) (*types.QueryAccountResponse, error) {
func (k Keeper) Account(c context.Context, req *types.QueryAccountRequest) (*types.QueryAccountResponse, error) {
metrics.ReportFuncCall(k.svcTags)
doneFn := metrics.ReportFuncTiming(k.svcTags)
defer doneFn()
if req == nil {
metrics.ReportFuncError(k.svcTags)
return nil, status.Error(codes.InvalidArgument, "empty request")
}
if len(req.Address) == 0 {
if types.IsZeroAddress(req.Address) {
metrics.ReportFuncError(k.svcTags)
return nil, status.Error(
codes.InvalidArgument,
types.ErrZeroAddress.Error(),
@@ -30,9 +39,10 @@ func (q Keeper) Account(c context.Context, req *types.QueryAccountRequest) (*typ
}
ctx := sdk.UnwrapSDKContext(c)
so := q.GetOrNewStateObject(ctx, ethcmn.HexToAddress(req.Address))
so := k.GetOrNewStateObject(ctx, ethcmn.HexToAddress(req.Address))
balance, err := ethermint.MarshalBigInt(so.Balance())
if err != nil {
metrics.ReportFuncError(k.svcTags)
return nil, err
}
@@ -43,13 +53,18 @@ func (q Keeper) Account(c context.Context, req *types.QueryAccountRequest) (*typ
}, nil
}
// Balance implements the Query/Balance gRPC method
func (q Keeper) Balance(c context.Context, req *types.QueryBalanceRequest) (*types.QueryBalanceResponse, error) {
func (k Keeper) CosmosAccount(c context.Context, req *types.QueryCosmosAccountRequest) (*types.QueryCosmosAccountResponse, error) {
metrics.ReportFuncCall(k.svcTags)
doneFn := metrics.ReportFuncTiming(k.svcTags)
defer doneFn()
if req == nil {
metrics.ReportFuncError(k.svcTags)
return nil, status.Error(codes.InvalidArgument, "empty request")
}
if len(req.Address) == 0 {
if types.IsZeroAddress(req.Address) {
metrics.ReportFuncError(k.svcTags)
return nil, status.Error(
codes.InvalidArgument,
types.ErrZeroAddress.Error(),
@@ -58,9 +73,48 @@ func (q Keeper) Balance(c context.Context, req *types.QueryBalanceRequest) (*typ
ctx := sdk.UnwrapSDKContext(c)
balanceInt := q.GetBalance(ctx, ethcmn.HexToAddress(req.Address))
ethStr := req.Address
ethAddr := ethcmn.FromHex(ethStr)
ethToCosmosAddr := sdk.AccAddress(ethAddr[:]).String()
cosmosToEthAddr, _ := sdk.AccAddressFromBech32(ethToCosmosAddr)
acc := k.accountKeeper.GetAccount(ctx, cosmosToEthAddr)
res := types.QueryCosmosAccountResponse{
CosmosAddress: cosmosToEthAddr.String(),
}
if acc != nil {
res.Sequence = acc.GetSequence()
res.AccountNumber = acc.GetAccountNumber()
}
return &res, nil
}
// Balance implements the Query/Balance gRPC method
func (k Keeper) Balance(c context.Context, req *types.QueryBalanceRequest) (*types.QueryBalanceResponse, error) {
metrics.ReportFuncCall(k.svcTags)
doneFn := metrics.ReportFuncTiming(k.svcTags)
defer doneFn()
if req == nil {
metrics.ReportFuncError(k.svcTags)
return nil, status.Error(codes.InvalidArgument, "empty request")
}
if types.IsZeroAddress(req.Address) {
metrics.ReportFuncError(k.svcTags)
return nil, status.Error(
codes.InvalidArgument,
types.ErrZeroAddress.Error(),
)
}
ctx := sdk.UnwrapSDKContext(c)
balanceInt := k.GetBalance(ctx, ethcmn.HexToAddress(req.Address))
balance, err := ethermint.MarshalBigInt(balanceInt)
if err != nil {
metrics.ReportFuncError(k.svcTags)
return nil, status.Error(
codes.Internal,
"failed to marshal big.Int to string",
@@ -73,31 +127,30 @@ func (q Keeper) Balance(c context.Context, req *types.QueryBalanceRequest) (*typ
}
// Storage implements the Query/Storage gRPC method
func (q Keeper) Storage(c context.Context, req *types.QueryStorageRequest) (*types.QueryStorageResponse, error) {
func (k Keeper) Storage(c context.Context, req *types.QueryStorageRequest) (*types.QueryStorageResponse, error) {
metrics.ReportFuncCall(k.svcTags)
doneFn := metrics.ReportFuncTiming(k.svcTags)
defer doneFn()
if req == nil {
metrics.ReportFuncError(k.svcTags)
return nil, status.Error(codes.InvalidArgument, "empty request")
}
if len(req.Address) == 0 {
if types.IsZeroAddress(req.Address) {
metrics.ReportFuncError(k.svcTags)
return nil, status.Error(
codes.InvalidArgument,
types.ErrZeroAddress.Error(),
)
}
if len(req.Key) == 0 {
return nil, status.Error(
codes.InvalidArgument,
types.ErrEmptyHash.Error(),
)
}
ctx := sdk.UnwrapSDKContext(c)
address := ethcmn.HexToAddress(req.Address)
key := ethcmn.HexToHash(req.Key)
state := q.GetState(ctx, address, key)
state := k.GetState(ctx, address, key)
return &types.QueryStorageResponse{
Value: state.String(),
@@ -105,12 +158,18 @@ func (q Keeper) Storage(c context.Context, req *types.QueryStorageRequest) (*typ
}
// Code implements the Query/Code gRPC method
func (q Keeper) Code(c context.Context, req *types.QueryCodeRequest) (*types.QueryCodeResponse, error) {
func (k Keeper) Code(c context.Context, req *types.QueryCodeRequest) (*types.QueryCodeResponse, error) {
metrics.ReportFuncCall(k.svcTags)
doneFn := metrics.ReportFuncTiming(k.svcTags)
defer doneFn()
if req == nil {
metrics.ReportFuncError(k.svcTags)
return nil, status.Error(codes.InvalidArgument, "empty request")
}
if len(req.Address) == 0 {
if types.IsZeroAddress(req.Address) {
metrics.ReportFuncError(k.svcTags)
return nil, status.Error(
codes.InvalidArgument,
types.ErrZeroAddress.Error(),
@@ -120,7 +179,7 @@ func (q Keeper) Code(c context.Context, req *types.QueryCodeRequest) (*types.Que
ctx := sdk.UnwrapSDKContext(c)
address := ethcmn.HexToAddress(req.Address)
code := q.GetCode(ctx, address)
code := k.GetCode(ctx, address)
return &types.QueryCodeResponse{
Code: code,
@@ -128,12 +187,18 @@ func (q Keeper) Code(c context.Context, req *types.QueryCodeRequest) (*types.Que
}
// TxLogs implements the Query/TxLogs gRPC method
func (q Keeper) TxLogs(c context.Context, req *types.QueryTxLogsRequest) (*types.QueryTxLogsResponse, error) {
func (k Keeper) TxLogs(c context.Context, req *types.QueryTxLogsRequest) (*types.QueryTxLogsResponse, error) {
metrics.ReportFuncCall(k.svcTags)
doneFn := metrics.ReportFuncTiming(k.svcTags)
defer doneFn()
if req == nil {
metrics.ReportFuncError(k.svcTags)
return nil, status.Error(codes.InvalidArgument, "empty request")
}
if types.IsEmptyHash(req.Hash) {
metrics.ReportFuncError(k.svcTags)
return nil, status.Error(
codes.InvalidArgument,
types.ErrEmptyHash.Error(),
@@ -143,8 +208,9 @@ func (q Keeper) TxLogs(c context.Context, req *types.QueryTxLogsRequest) (*types
ctx := sdk.UnwrapSDKContext(c)
hash := ethcmn.HexToHash(req.Hash)
logs, err := q.GetLogs(ctx, hash)
logs, err := k.GetLogs(ctx, hash)
if err != nil {
metrics.ReportFuncError(k.svcTags)
return nil, status.Error(
codes.Internal,
err.Error(),
@@ -156,13 +222,19 @@ func (q Keeper) TxLogs(c context.Context, req *types.QueryTxLogsRequest) (*types
}, nil
}
// BlockLogs implements the Query/BlockLogs gRPC method
func (q Keeper) BlockLogs(c context.Context, req *types.QueryBlockLogsRequest) (*types.QueryBlockLogsResponse, error) {
// TxReceipt implements the Query/TxReceipt gRPC method
func (k Keeper) TxReceipt(c context.Context, req *types.QueryTxReceiptRequest) (*types.QueryTxReceiptResponse, error) {
metrics.ReportFuncCall(k.svcTags)
doneFn := metrics.ReportFuncTiming(k.svcTags)
defer doneFn()
if req == nil {
metrics.ReportFuncError(k.svcTags)
return nil, status.Error(codes.InvalidArgument, "empty request")
}
if types.IsEmptyHash(req.Hash) {
metrics.ReportFuncError(k.svcTags)
return nil, status.Error(
codes.InvalidArgument,
types.ErrEmptyHash.Error(),
@@ -171,7 +243,90 @@ func (q Keeper) BlockLogs(c context.Context, req *types.QueryBlockLogsRequest) (
ctx := sdk.UnwrapSDKContext(c)
txLogs := q.GetAllTxLogs(ctx)
hash := ethcmn.HexToHash(req.Hash)
receipt, found := k.GetTxReceiptFromHash(ctx, hash)
if !found {
metrics.ReportFuncError(k.svcTags)
return nil, status.Error(
codes.NotFound, types.ErrTxReceiptNotFound.Error(),
)
}
return &types.QueryTxReceiptResponse{
Receipt: receipt,
}, nil
}
// TxReceiptsByBlockHeight implements the Query/TxReceiptsByBlockHeight gRPC method
func (k Keeper) TxReceiptsByBlockHeight(c context.Context, req *types.QueryTxReceiptsByBlockHeightRequest) (*types.QueryTxReceiptsByBlockHeightResponse, error) {
metrics.ReportFuncCall(k.svcTags)
doneFn := metrics.ReportFuncTiming(k.svcTags)
defer doneFn()
if req == nil {
metrics.ReportFuncError(k.svcTags)
return nil, status.Error(codes.InvalidArgument, "empty request")
}
ctx := sdk.UnwrapSDKContext(c)
receipts := k.GetTxReceiptsByBlockHeight(ctx, req.Height)
return &types.QueryTxReceiptsByBlockHeightResponse{
Receipts: receipts,
}, nil
}
// TxReceiptsByBlockHash implements the Query/TxReceiptsByBlockHash gRPC method
func (k Keeper) TxReceiptsByBlockHash(c context.Context, req *types.QueryTxReceiptsByBlockHashRequest) (*types.QueryTxReceiptsByBlockHashResponse, error) {
metrics.ReportFuncCall(k.svcTags)
doneFn := metrics.ReportFuncTiming(k.svcTags)
defer doneFn()
if req == nil {
metrics.ReportFuncError(k.svcTags)
return nil, status.Error(codes.InvalidArgument, "empty request")
}
if types.IsEmptyHash(req.Hash) {
metrics.ReportFuncError(k.svcTags)
return nil, status.Error(
codes.InvalidArgument,
types.ErrEmptyHash.Error(),
)
}
ctx := sdk.UnwrapSDKContext(c)
hash := ethcmn.HexToHash(req.Hash)
receipts := k.GetTxReceiptsByBlockHash(ctx, hash)
return &types.QueryTxReceiptsByBlockHashResponse{
Receipts: receipts,
}, nil
}
// BlockLogs implements the Query/BlockLogs gRPC method
func (k Keeper) BlockLogs(c context.Context, req *types.QueryBlockLogsRequest) (*types.QueryBlockLogsResponse, error) {
metrics.ReportFuncCall(k.svcTags)
doneFn := metrics.ReportFuncTiming(k.svcTags)
defer doneFn()
if req == nil {
metrics.ReportFuncError(k.svcTags)
return nil, status.Error(codes.InvalidArgument, "empty request")
}
if types.IsEmptyHash(req.Hash) {
metrics.ReportFuncError(k.svcTags)
return nil, status.Error(
codes.InvalidArgument,
types.ErrEmptyHash.Error(),
)
}
ctx := sdk.UnwrapSDKContext(c)
txLogs := k.GetAllTxLogs(ctx)
return &types.QueryBlockLogsResponse{
TxLogs: txLogs,
@@ -179,14 +334,28 @@ func (q Keeper) BlockLogs(c context.Context, req *types.QueryBlockLogsRequest) (
}
// BlockBloom implements the Query/BlockBloom gRPC method
func (q Keeper) BlockBloom(c context.Context, _ *types.QueryBlockBloomRequest) (*types.QueryBlockBloomResponse, error) {
func (k Keeper) BlockBloom(c context.Context, req *types.QueryBlockBloomRequest) (*types.QueryBlockBloomResponse, error) {
metrics.ReportFuncCall(k.svcTags)
doneFn := metrics.ReportFuncTiming(k.svcTags)
defer doneFn()
if req == nil {
metrics.ReportFuncError(k.svcTags)
return nil, status.Error(codes.InvalidArgument, "empty request")
}
ctx := sdk.UnwrapSDKContext(c)
// use block height provided through the gRPC header
bloom, found := q.GetBlockBloom(ctx, ctx.BlockHeight())
height := ctx.BlockHeight()
if setHeight := req.Height; setHeight > 0 {
height = setHeight
}
bloom, found := k.GetBlockBloom(ctx, height)
if !found {
return nil, status.Errorf(
codes.NotFound, "%s: height %d", types.ErrBloomNotFound.Error(), ctx.BlockHeight(),
metrics.ReportFuncError(k.svcTags)
return nil, status.Error(
codes.NotFound, types.ErrBloomNotFound.Error(),
)
}
@@ -196,11 +365,73 @@ func (q Keeper) BlockBloom(c context.Context, _ *types.QueryBlockBloomRequest) (
}
// Params implements the Query/Params gRPC method
func (q Keeper) Params(c context.Context, _ *types.QueryParamsRequest) (*types.QueryParamsResponse, error) {
func (k Keeper) Params(c context.Context, req *types.QueryParamsRequest) (*types.QueryParamsResponse, error) {
metrics.ReportFuncCall(k.svcTags)
doneFn := metrics.ReportFuncTiming(k.svcTags)
defer doneFn()
if req == nil {
metrics.ReportFuncError(k.svcTags)
return nil, status.Error(codes.InvalidArgument, "empty request")
}
ctx := sdk.UnwrapSDKContext(c)
params := q.GetParams(ctx)
params := k.GetParams(ctx)
return &types.QueryParamsResponse{
Params: params,
}, nil
}
// StaticCall implements Query/StaticCall gRPCP method
func (k Keeper) StaticCall(c context.Context, req *types.QueryStaticCallRequest) (*types.QueryStaticCallResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "empty request")
}
ctx := sdk.UnwrapSDKContext(c)
// parse the chainID from a string to a base-10 integer
chainIDEpoch, err := ethermint.ParseChainID(ctx.ChainID())
if err != nil {
return nil, err
}
txHash := tmtypes.Tx(ctx.TxBytes()).Hash()
ethHash := ethcmn.BytesToHash(txHash)
var recipient *ethcmn.Address
if len(req.Address) > 0 {
addr := ethcmn.HexToAddress(req.Address)
recipient = &addr
}
so := k.GetOrNewStateObject(ctx, *recipient)
sender := ethcmn.HexToAddress("0xaDd00275E3d9d213654Ce5223f0FADE8b106b707")
st := &types.StateTransition{
AccountNonce: so.Nonce(),
Price: new(big.Int).SetBytes(big.NewInt(0).Bytes()),
GasLimit: 100000000,
Recipient: recipient,
Amount: new(big.Int).SetBytes(big.NewInt(0).Bytes()),
Payload: req.Input,
Csdb: k.CommitStateDB.WithContext(ctx),
ChainID: chainIDEpoch,
TxHash: &ethHash,
Sender: sender,
Simulate: ctx.IsCheckTx(),
}
config, found := k.GetChainConfig(ctx)
if !found {
return nil, types.ErrChainConfigNotFound
}
ret, err := st.StaticCall(ctx, config)
if err != nil {
return nil, err
}
return &types.QueryStaticCallResponse{Data: ret}, nil
}
-499
View File
@@ -1,499 +0,0 @@
package keeper_test
import (
"fmt"
"google.golang.org/grpc/metadata"
ethcmn "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
sdk "github.com/cosmos/cosmos-sdk/types"
ethermint "github.com/cosmos/ethermint/types"
"github.com/cosmos/ethermint/x/evm/types"
grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
)
func (suite *KeeperTestSuite) TestQueryAccount() {
var (
req *types.QueryAccountRequest
expAccount *types.QueryAccountResponse
)
testCases := []struct {
msg string
malleate func()
expPass bool
}{
{"zero address",
func() {
suite.app.BankKeeper.SetBalance(suite.ctx, suite.address.Bytes(), ethermint.NewPhotonCoinInt64(0))
expAccount = &types.QueryAccountResponse{
Balance: "0",
CodeHash: ethcrypto.Keccak256(nil),
Nonce: 0,
}
req = &types.QueryAccountRequest{
Address: ethcmn.Address{}.String(),
}
},
true,
},
{
"success",
func() {
suite.app.BankKeeper.SetBalance(suite.ctx, suite.address.Bytes(), ethermint.NewPhotonCoinInt64(100))
expAccount = &types.QueryAccountResponse{
Balance: "100",
CodeHash: ethcrypto.Keccak256(nil),
Nonce: 0,
}
req = &types.QueryAccountRequest{
Address: suite.address.String(),
}
},
true,
},
}
for _, tc := range testCases {
suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
suite.SetupTest() // reset
tc.malleate()
ctx := sdk.WrapSDKContext(suite.ctx)
res, err := suite.queryClient.Account(ctx, req)
if tc.expPass {
suite.Require().NoError(err)
suite.Require().NotNil(res)
suite.Require().Equal(expAccount, res)
} else {
suite.Require().Error(err)
}
})
}
}
func (suite *KeeperTestSuite) TestQueryBalance() {
var (
req *types.QueryBalanceRequest
expBalance string
)
testCases := []struct {
msg string
malleate func()
expPass bool
}{
{"zero address",
func() {
suite.app.BankKeeper.SetBalance(suite.ctx, suite.address.Bytes(), ethermint.NewPhotonCoinInt64(0))
expBalance = "0"
req = &types.QueryBalanceRequest{
Address: ethcmn.Address{}.String(),
}
},
true,
},
{
"success",
func() {
suite.app.BankKeeper.SetBalance(suite.ctx, suite.address.Bytes(), ethermint.NewPhotonCoinInt64(100))
expBalance = "100"
req = &types.QueryBalanceRequest{
Address: suite.address.String(),
}
},
true,
},
}
for _, tc := range testCases {
suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
suite.SetupTest() // reset
tc.malleate()
ctx := sdk.WrapSDKContext(suite.ctx)
res, err := suite.queryClient.Balance(ctx, req)
if tc.expPass {
suite.Require().NoError(err)
suite.Require().NotNil(res)
suite.Require().Equal(expBalance, res.Balance)
} else {
suite.Require().Error(err)
}
})
}
}
func (suite *KeeperTestSuite) TestQueryStorage() {
var (
req *types.QueryStorageRequest
expValue string
)
testCases := []struct {
msg string
malleate func()
expPass bool
}{
{"zero address",
func() {
req = &types.QueryStorageRequest{
Address: ethcmn.Address{}.String(),
}
},
false,
},
{"empty hash",
func() {
req = &types.QueryStorageRequest{
Address: suite.address.String(),
Key: ethcmn.Hash{}.String(),
}
exp := &types.QueryStorageResponse{Value: "0x0000000000000000000000000000000000000000000000000000000000000000"}
expValue = exp.Value
},
true,
},
{
"success",
func() {
key := ethcmn.BytesToHash([]byte("key"))
value := ethcmn.BytesToHash([]byte("value"))
expValue = value.String()
suite.app.EvmKeeper.SetState(suite.ctx, suite.address, key, value)
req = &types.QueryStorageRequest{
Address: suite.address.String(),
Key: key.String(),
}
},
true,
},
}
for _, tc := range testCases {
suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
suite.SetupTest() // reset
tc.malleate()
ctx := sdk.WrapSDKContext(suite.ctx)
res, err := suite.queryClient.Storage(ctx, req)
if tc.expPass {
suite.Require().NoError(err)
suite.Require().NotNil(res)
suite.Require().Equal(expValue, res.Value)
} else {
suite.Require().Error(err)
}
})
}
}
func (suite *KeeperTestSuite) TestQueryCode() {
var (
req *types.QueryCodeRequest
expCode []byte
)
testCases := []struct {
msg string
malleate func()
expPass bool
}{
{"zero address",
func() {
req = &types.QueryCodeRequest{
Address: ethcmn.Address{}.String(),
}
exp := &types.QueryCodeResponse{}
expCode = exp.Code
},
true,
},
{
"success",
func() {
expCode = []byte("code")
suite.app.EvmKeeper.SetCode(suite.ctx, suite.address, expCode)
req = &types.QueryCodeRequest{
Address: suite.address.String(),
}
},
true,
},
}
for _, tc := range testCases {
suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
suite.SetupTest() // reset
tc.malleate()
ctx := sdk.WrapSDKContext(suite.ctx)
res, err := suite.queryClient.Code(ctx, req)
if tc.expPass {
suite.Require().NoError(err)
suite.Require().NotNil(res)
suite.Require().Equal(expCode, res.Code)
} else {
suite.Require().Error(err)
}
})
}
}
func (suite *KeeperTestSuite) TestQueryTxLogs() {
var (
req *types.QueryTxLogsRequest
expLogs []*types.Log
)
testCases := []struct {
msg string
malleate func()
expPass bool
}{
{"empty hash",
func() {
req = &types.QueryTxLogsRequest{
Hash: ethcmn.Hash{}.String(),
}
},
false,
},
{"logs not found",
func() {
hash := ethcmn.BytesToHash([]byte("hash"))
req = &types.QueryTxLogsRequest{
Hash: hash.String(),
}
},
true,
},
{
"success",
func() {
hash := ethcmn.BytesToHash([]byte("tx_hash"))
expLogs = []*types.Log{
{
Address: suite.address.String(),
Topics: []string{ethcmn.BytesToHash([]byte("topic")).String()},
Data: []byte("data"),
BlockNumber: 1,
TxHash: hash.String(),
TxIndex: 1,
BlockHash: ethcmn.BytesToHash([]byte("block_hash")).String(),
Index: 0,
Removed: false,
},
}
suite.app.EvmKeeper.SetLogs(suite.ctx, hash, types.LogsToEthereum(expLogs))
req = &types.QueryTxLogsRequest{
Hash: hash.String(),
}
},
true,
},
}
for _, tc := range testCases {
suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
suite.SetupTest() // reset
tc.malleate()
ctx := sdk.WrapSDKContext(suite.ctx)
res, err := suite.queryClient.TxLogs(ctx, req)
if tc.expPass {
suite.Require().NoError(err)
suite.Require().NotNil(res)
suite.Require().Equal(expLogs, res.Logs)
} else {
suite.Require().Error(err)
}
})
}
}
func (suite *KeeperTestSuite) TestQueryBlockLogs() {
var (
req *types.QueryBlockLogsRequest
expLogs []types.TransactionLogs
)
testCases := []struct {
msg string
malleate func()
expPass bool
}{
{"empty hash",
func() {
req = &types.QueryBlockLogsRequest{
Hash: ethcmn.Hash{}.String(),
}
},
false,
},
{"logs not found",
func() {
hash := ethcmn.BytesToHash([]byte("hash"))
req = &types.QueryBlockLogsRequest{
Hash: hash.String(),
}
},
true,
},
{
"success",
func() {
hash := ethcmn.BytesToHash([]byte("block_hash"))
expLogs = []types.TransactionLogs{
{
Hash: ethcmn.BytesToHash([]byte("tx_hash_0")).String(),
Logs: []*types.Log{
{
Address: suite.address.String(),
Topics: []string{ethcmn.BytesToHash([]byte("topic")).String()},
Data: []byte("data"),
BlockNumber: 1,
TxHash: ethcmn.BytesToHash([]byte("tx_hash_0")).String(),
TxIndex: 1,
BlockHash: ethcmn.BytesToHash([]byte("block_hash")).String(),
Index: 0,
Removed: false,
},
},
},
{
Hash: ethcmn.BytesToHash([]byte("tx_hash_1")).String(),
Logs: []*types.Log{
{
Address: suite.address.String(),
Topics: []string{ethcmn.BytesToHash([]byte("topic")).String()},
Data: []byte("data"),
BlockNumber: 1,
TxHash: ethcmn.BytesToHash([]byte("tx_hash_1")).String(),
TxIndex: 1,
BlockHash: ethcmn.BytesToHash([]byte("block_hash")).String(),
Index: 0,
Removed: false,
},
{
Address: suite.address.String(),
Topics: []string{ethcmn.BytesToHash([]byte("topic_1")).String()},
Data: []byte("data_1"),
BlockNumber: 1,
TxHash: ethcmn.BytesToHash([]byte("tx_hash_1")).String(),
TxIndex: 1,
BlockHash: ethcmn.BytesToHash([]byte("block_hash")).String(),
Index: 0,
Removed: false,
},
},
},
}
suite.app.EvmKeeper.SetLogs(suite.ctx, ethcmn.BytesToHash([]byte("tx_hash_0")), types.LogsToEthereum(expLogs[0].Logs))
suite.app.EvmKeeper.SetLogs(suite.ctx, ethcmn.BytesToHash([]byte("tx_hash_1")), types.LogsToEthereum(expLogs[1].Logs))
req = &types.QueryBlockLogsRequest{
Hash: hash.String(),
}
},
true,
},
}
for _, tc := range testCases {
suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
suite.SetupTest() // reset
tc.malleate()
ctx := sdk.WrapSDKContext(suite.ctx)
res, err := suite.queryClient.BlockLogs(ctx, req)
if tc.expPass {
suite.Require().NoError(err)
suite.Require().NotNil(res)
suite.Require().Equal(expLogs, res.TxLogs)
} else {
suite.Require().Error(err)
}
})
}
}
func (suite *KeeperTestSuite) TestQueryBlockBloom() {
var (
req *types.QueryBlockBloomRequest
expBloom []byte
)
testCases := []struct {
msg string
malleate func()
expPass bool
}{
{"bloom bytes not found for height",
func() {},
false,
},
{
"success",
func() {
req = &types.QueryBlockBloomRequest{}
bloom := ethtypes.BytesToBloom([]byte("bloom"))
expBloom = bloom.Bytes()
suite.ctx = suite.ctx.WithBlockHeight(1)
suite.app.EvmKeeper.SetBlockBloom(suite.ctx, 1, bloom)
},
true,
},
}
for _, tc := range testCases {
suite.Run(fmt.Sprintf("Case %s", tc.msg), func() {
suite.SetupTest() // reset
tc.malleate()
ctx := sdk.WrapSDKContext(suite.ctx)
ctx = metadata.AppendToOutgoingContext(ctx, grpctypes.GRPCBlockHeightHeader, fmt.Sprintf("%d", suite.ctx.BlockHeight()))
res, err := suite.queryClient.BlockBloom(ctx, req)
if tc.expPass {
suite.Require().NoError(err)
suite.Require().NotNil(res)
suite.Require().Equal(expBloom, res.Bloom)
} else {
suite.Require().Error(err)
}
})
}
}
func (suite *KeeperTestSuite) TestQueryParams() {
ctx := sdk.WrapSDKContext(suite.ctx)
expParams := types.DefaultParams()
res, err := suite.queryClient.Params(ctx, &types.QueryParamsRequest{})
suite.Require().NoError(err)
suite.Require().Equal(expParams, res.Params)
}
+2 -4
View File
@@ -4,10 +4,8 @@ import (
"math/big"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/ethermint/crypto/ethsecp256k1"
ethermint "github.com/cosmos/ethermint/types"
ethcmn "github.com/ethereum/go-ethereum/common"
)
@@ -27,7 +25,7 @@ func (suite *KeeperTestSuite) TestBalanceInvariant() {
func() {
acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, address.Bytes())
suite.Require().NotNil(acc)
suite.app.BankKeeper.SetBalance(suite.ctx, acc.GetAddress(), ethermint.NewPhotonCoinInt64(1))
suite.app.BankKeeper.SetBalance(suite.ctx, acc.GetAddress(), ethermint.NewInjectiveCoinInt64(1))
suite.Require().NoError(err)
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
@@ -40,7 +38,7 @@ func (suite *KeeperTestSuite) TestBalanceInvariant() {
func() {
acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, address.Bytes())
suite.Require().NotNil(acc)
suite.app.BankKeeper.SetBalance(suite.ctx, acc.GetAddress(), ethermint.NewPhotonCoinInt64(1))
suite.app.BankKeeper.SetBalance(suite.ctx, acc.GetAddress(), ethermint.NewInjectiveCoinInt64(1))
suite.Require().NoError(err)
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
+235 -28
View File
@@ -1,20 +1,17 @@
package keeper
import (
"fmt"
"math/big"
"github.com/tendermint/tendermint/libs/log"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
"github.com/cosmos/ethermint/x/evm/types"
"github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/tendermint/tendermint/libs/log"
"github.com/cosmos/ethermint/metrics"
"github.com/cosmos/ethermint/x/evm/types"
)
// Keeper wraps the CommitStateDB, allowing us to pass in SDK context while adhering
@@ -27,11 +24,12 @@ type Keeper struct {
// - storing Account's Code
// - storing transaction Logs
// - storing block height -> bloom filter map. Needed for the Web3 API.
// - storing block hash -> block height map. Needed for the Web3 API.
// - storing block hash -> block height map. Needed for the Web3 API. TODO: remove
storeKey sdk.StoreKey
// Account Keeper for fetching accounts
accountKeeper types.AccountKeeper
bankKeeper types.BankKeeper
// Ethermint concrete implementation on the EVM StateDB interface
CommitStateDB *types.CommitStateDB
// Transaction counter in a block. Used on StateSB's Prepare function.
@@ -39,6 +37,12 @@ type Keeper struct {
// on the KVStore or adding it as a field on the EVM genesis state.
TxCount int
Bloom *big.Int
// LogsCache keeps mapping of contract address -> eth logs emitted
// during EVM execution in the current block.
LogsCache map[common.Address][]*ethtypes.Log
svcTags metrics.Tags
}
// NewKeeper generates new evm module keeper
@@ -53,6 +57,10 @@ func NewKeeper(
// NOTE: we pass in the parameter space to the CommitStateDB in order to use custom denominations for the EVM operations
return &Keeper{
svcTags: metrics.Tags{
"svc": "evm_k",
},
cdc: cdc,
accountKeeper: ak,
bankKeeper: bankKeeper,
@@ -60,19 +68,116 @@ func NewKeeper(
CommitStateDB: types.NewCommitStateDB(sdk.Context{}, storeKey, paramSpace, ak, bankKeeper),
TxCount: 0,
Bloom: big.NewInt(0),
LogsCache: map[common.Address][]*ethtypes.Log{},
}
}
// Logger returns a module-specific logger.
func (k Keeper) Logger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName))
return ctx.Logger().With("module", types.ModuleName)
}
// ----------------------------------------------------------------------------
// Epoch Height -> hash mapping functions
// Required by EVM context's GetHashFunc
// Block bloom bits mapping functions
// Required by Web3 API.
// ----------------------------------------------------------------------------
// GetBlockBloom gets bloombits from block height
func (k Keeper) GetBlockBloom(ctx sdk.Context, height int64) (ethtypes.Bloom, bool) {
metrics.ReportFuncCall(k.svcTags)
doneFn := metrics.ReportFuncTiming(k.svcTags)
defer doneFn()
store := ctx.KVStore(k.storeKey)
key := types.BloomKey(height)
has := store.Has(key)
if !has {
return ethtypes.Bloom{}, true // sometimes bloom not found, fix this
}
bz := store.Get(key)
return ethtypes.BytesToBloom(bz), true
}
// SetBlockBloom sets the mapping from block height to bloom bits
func (k Keeper) SetBlockBloom(ctx sdk.Context, height int64, bloom ethtypes.Bloom) {
metrics.ReportFuncCall(k.svcTags)
doneFn := metrics.ReportFuncTiming(k.svcTags)
defer doneFn()
store := ctx.KVStore(k.storeKey)
key := types.BloomKey(height)
store.Set(key, bloom.Bytes())
}
// GetBlockHash gets block height from block consensus hash
func (k Keeper) GetBlockHashFromHeight(ctx sdk.Context, height int64) ([]byte, bool) {
metrics.ReportFuncCall(k.svcTags)
doneFn := metrics.ReportFuncTiming(k.svcTags)
defer doneFn()
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.KeyBlockHeightHash(uint64(height)))
if len(bz) == 0 {
return common.Hash{}.Bytes(), false
}
return common.BytesToHash(bz).Bytes(), true
}
// SetBlockHash sets the mapping from block consensus hash to block height
func (k Keeper) SetBlockHash(ctx sdk.Context, hash []byte, height int64) {
metrics.ReportFuncCall(k.svcTags)
doneFn := metrics.ReportFuncTiming(k.svcTags)
defer doneFn()
store := ctx.KVStore(k.storeKey)
bz := sdk.Uint64ToBigEndian(uint64(height))
store.Set(types.KeyBlockHash(common.BytesToHash(hash)), bz)
}
// GetBlockHash gets block height from block consensus hash
func (k Keeper) GetBlockHeightByHash(ctx sdk.Context, hash common.Hash) (int64, bool) {
metrics.ReportFuncCall(k.svcTags)
doneFn := metrics.ReportFuncTiming(k.svcTags)
defer doneFn()
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.KeyBlockHash(hash))
if len(bz) == 0 {
return 0, false
}
height := sdk.BigEndianToUint64(bz)
return int64(height), true
}
// SetBlockHash sets the mapping from block consensus hash to block height
func (k Keeper) SetBlockHeightToHash(ctx sdk.Context, hash []byte, height int64) {
metrics.ReportFuncCall(k.svcTags)
doneFn := metrics.ReportFuncTiming(k.svcTags)
defer doneFn()
store := ctx.KVStore(k.storeKey)
store.Set(types.KeyBlockHeightHash(uint64(height)), hash)
}
// SetTxReceiptToHash sets the mapping from tx hash to tx receipt
func (k Keeper) SetTxReceiptToHash(ctx sdk.Context, hash common.Hash, receipt *types.TxReceipt) {
metrics.ReportFuncCall(k.svcTags)
doneFn := metrics.ReportFuncTiming(k.svcTags)
defer doneFn()
ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter())
data := k.cdc.MustMarshalBinaryBare(receipt)
store := ctx.KVStore(k.storeKey)
store.Set(types.KeyHashTxReceipt(hash), data)
}
// GetHeightHash returns the block header hash associated with a given block height and chain epoch number.
func (k Keeper) GetHeightHash(ctx sdk.Context, height uint64) common.Hash {
return k.CommitStateDB.WithContext(ctx).GetHeightHash(height)
@@ -83,31 +188,121 @@ func (k Keeper) SetHeightHash(ctx sdk.Context, height uint64, hash common.Hash)
k.CommitStateDB.WithContext(ctx).SetHeightHash(height, hash)
}
// ----------------------------------------------------------------------------
// Block bloom bits mapping functions
// Required by Web3 API.
// ----------------------------------------------------------------------------
// GetTxReceiptFromHash gets tx receipt by tx hash.
func (k Keeper) GetTxReceiptFromHash(ctx sdk.Context, hash common.Hash) (*types.TxReceipt, bool) {
metrics.ReportFuncCall(k.svcTags)
doneFn := metrics.ReportFuncTiming(k.svcTags)
defer doneFn()
// GetBlockBloom gets bloombits from block height
func (k Keeper) GetBlockBloom(ctx sdk.Context, height int64) (ethtypes.Bloom, bool) {
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixBloom)
has := store.Has(types.BloomKey(height))
if !has {
return ethtypes.Bloom{}, false
store := ctx.KVStore(k.storeKey)
data := store.Get(types.KeyHashTxReceipt(hash))
if data == nil || len(data) == 0 {
return nil, false
}
bz := store.Get(types.BloomKey(height))
return ethtypes.BytesToBloom(bz), true
var receipt types.TxReceipt
k.cdc.MustUnmarshalBinaryBare(data, &receipt)
return &receipt, true
}
// SetBlockBloom sets the mapping from block height to bloom bits
func (k Keeper) SetBlockBloom(ctx sdk.Context, height int64, bloom ethtypes.Bloom) {
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixBloom)
store.Set(types.BloomKey(height), bloom.Bytes())
// AddTxHashToBlock stores tx hash in the list of tx for the block.
func (k Keeper) AddTxHashToBlock(ctx sdk.Context, blockHeight int64, txHash common.Hash) {
metrics.ReportFuncCall(k.svcTags)
doneFn := metrics.ReportFuncTiming(k.svcTags)
defer doneFn()
key := types.KeyBlockHeightTxs(uint64(blockHeight))
list := types.BytesList{}
store := ctx.KVStore(k.storeKey)
data := store.Get(key)
if len(data) > 0 {
k.cdc.MustUnmarshalBinaryBare(data, &list)
}
list.Bytes = append(list.Bytes, txHash.Bytes())
data = k.cdc.MustMarshalBinaryBare(&list)
store.Set(key, data)
}
// GetTxsFromBlock returns list of tx hash in the block by height.
func (k Keeper) GetTxsFromBlock(ctx sdk.Context, blockHeight int64) []common.Hash {
metrics.ReportFuncCall(k.svcTags)
doneFn := metrics.ReportFuncTiming(k.svcTags)
defer doneFn()
key := types.KeyBlockHeightTxs(uint64(blockHeight))
store := ctx.KVStore(k.storeKey)
data := store.Get(key)
if len(data) > 0 {
list := types.BytesList{}
k.cdc.MustUnmarshalBinaryBare(data, &list)
txs := make([]common.Hash, 0, len(list.Bytes))
for _, b := range list.Bytes {
txs = append(txs, common.BytesToHash(b))
}
return txs
}
return nil
}
// GetTxReceiptsByBlockHeight gets tx receipts by block height.
func (k Keeper) GetTxReceiptsByBlockHeight(ctx sdk.Context, blockHeight int64) []*types.TxReceipt {
metrics.ReportFuncCall(k.svcTags)
doneFn := metrics.ReportFuncTiming(k.svcTags)
defer doneFn()
txs := k.GetTxsFromBlock(ctx, blockHeight)
if len(txs) == 0 {
return nil
}
store := ctx.KVStore(k.storeKey)
receipts := make([]*types.TxReceipt, 0, len(txs))
for idx, txHash := range txs {
data := store.Get(types.KeyHashTxReceipt(txHash))
if data == nil || len(data) == 0 {
continue
}
var receipt types.TxReceipt
k.cdc.MustUnmarshalBinaryBare(data, &receipt)
receipt.Index = uint64(idx)
receipts = append(receipts, &receipt)
}
return receipts
}
// GetTxReceiptsByBlockHash gets tx receipts by block hash.
func (k Keeper) GetTxReceiptsByBlockHash(ctx sdk.Context, hash common.Hash) []*types.TxReceipt {
metrics.ReportFuncCall(k.svcTags)
doneFn := metrics.ReportFuncTiming(k.svcTags)
defer doneFn()
blockHeight, ok := k.GetBlockHeightByHash(ctx, hash)
if !ok {
return nil
}
return k.GetTxReceiptsByBlockHeight(ctx, blockHeight)
}
// GetAllTxLogs return all the transaction logs from the store.
func (k Keeper) GetAllTxLogs(ctx sdk.Context) []types.TransactionLogs {
metrics.ReportFuncCall(k.svcTags)
doneFn := metrics.ReportFuncTiming(k.svcTags)
defer doneFn()
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, types.KeyPrefixLogs)
defer iterator.Close()
@@ -125,6 +320,10 @@ func (k Keeper) GetAllTxLogs(ctx sdk.Context) []types.TransactionLogs {
// GetAccountStorage return state storage associated with an account
func (k Keeper) GetAccountStorage(ctx sdk.Context, address common.Address) (types.Storage, error) {
metrics.ReportFuncCall(k.svcTags)
doneFn := metrics.ReportFuncTiming(k.svcTags)
defer doneFn()
storage := types.Storage{}
err := k.ForEachStorage(ctx, address, func(key, value common.Hash) bool {
@@ -140,6 +339,10 @@ func (k Keeper) GetAccountStorage(ctx sdk.Context, address common.Address) (type
// GetChainConfig gets block height from block consensus hash
func (k Keeper) GetChainConfig(ctx sdk.Context) (types.ChainConfig, bool) {
metrics.ReportFuncCall(k.svcTags)
doneFn := metrics.ReportFuncTiming(k.svcTags)
defer doneFn()
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.KeyPrefixChainConfig)
if len(bz) == 0 {
@@ -153,6 +356,10 @@ func (k Keeper) GetChainConfig(ctx sdk.Context) (types.ChainConfig, bool) {
// SetChainConfig sets the mapping from block consensus hash to block height
func (k Keeper) SetChainConfig(ctx sdk.Context, config types.ChainConfig) {
metrics.ReportFuncCall(k.svcTags)
doneFn := metrics.ReportFuncTiming(k.svcTags)
defer doneFn()
store := ctx.KVStore(k.storeKey)
bz := k.cdc.MustMarshalBinaryBare(&config)
store.Set(types.KeyPrefixChainConfig, bz)
+8 -18
View File
@@ -7,7 +7,6 @@ import (
"github.com/stretchr/testify/suite"
"github.com/cosmos/cosmos-sdk/baseapp"
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
@@ -32,24 +31,20 @@ var (
type KeeperTestSuite struct {
suite.Suite
ctx sdk.Context
app *app.EthermintApp
queryClient types.QueryClient
address ethcmn.Address
ctx sdk.Context
querier sdk.Querier
app *app.InjectiveApp
address ethcmn.Address
}
func (suite *KeeperTestSuite) SetupTest() {
checkTx := false
suite.app = app.Setup(checkTx)
suite.ctx = suite.app.BaseApp.NewContext(checkTx, tmproto.Header{Height: 1, ChainID: "ethermint-3", Time: time.Now().UTC()})
suite.ctx = suite.app.BaseApp.NewContext(checkTx, tmproto.Header{Height: 1, ChainID: "3", Time: time.Now().UTC()})
suite.address = ethcmn.HexToAddress(addrHex)
queryHelper := baseapp.NewQueryServerTestHelper(suite.ctx, suite.app.InterfaceRegistry())
types.RegisterQueryServer(queryHelper, suite.app.EvmKeeper)
suite.queryClient = types.NewQueryClient(queryHelper)
balance := ethermint.NewPhotonCoin(sdk.ZeroInt())
balance := ethermint.NewInjectiveCoin(sdk.ZeroInt())
acc := &ethermint.EthAccount{
BaseAccount: authtypes.NewBaseAccount(sdk.AccAddress(suite.address.Bytes()), nil, 0, 0),
CodeHash: ethcrypto.Keccak256(nil),
@@ -69,13 +64,11 @@ func (suite *KeeperTestSuite) TestTransactionLogs() {
Address: suite.address,
Data: []byte("log"),
BlockNumber: 10,
Topics: []ethcmn.Hash{},
}
log2 := &ethtypes.Log{
Address: suite.address,
Data: []byte("log2"),
BlockNumber: 11,
Topics: []ethcmn.Hash{},
}
expLogs := []*ethtypes.Log{log}
@@ -90,7 +83,6 @@ func (suite *KeeperTestSuite) TestTransactionLogs() {
// add another log under the zero hash
suite.app.EvmKeeper.AddLog(suite.ctx, log2)
log2.Index = 0
logs = suite.app.EvmKeeper.AllLogs(suite.ctx)
suite.Require().Equal(expLogs, logs)
@@ -99,19 +91,17 @@ func (suite *KeeperTestSuite) TestTransactionLogs() {
Address: suite.address,
Data: []byte("log3"),
BlockNumber: 10,
Topics: []ethcmn.Hash{},
}
suite.app.EvmKeeper.AddLog(suite.ctx, log3)
log3.Index = 0
txLogs := suite.app.EvmKeeper.GetAllTxLogs(suite.ctx)
suite.Require().Equal(2, len(txLogs))
suite.Require().Equal(ethcmn.Hash{}.String(), txLogs[0].Hash)
suite.Require().Equal([]*ethtypes.Log{log2, log3}, txLogs[0].EthLogs())
suite.Require().Equal([]*ethtypes.Log{log2, log3}, txLogs[0].Logs)
suite.Require().Equal(ethHash.String(), txLogs[1].Hash)
suite.Require().Equal([]*ethtypes.Log{log}, txLogs[1].EthLogs())
suite.Require().Equal([]*ethtypes.Log{log}, txLogs[1].Logs)
}
func (suite *KeeperTestSuite) TestDBStorage() {
+288 -36
View File
@@ -2,57 +2,68 @@ package keeper
import (
"context"
"math/big"
"github.com/armon/go-metrics"
"github.com/pkg/errors"
log "github.com/xlab/suplog"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ethereum/go-ethereum/common"
ethcmn "github.com/ethereum/go-ethereum/common"
tmtypes "github.com/tendermint/tendermint/types"
ethcmn "github.com/ethereum/go-ethereum/common"
"github.com/cosmos/cosmos-sdk/telemetry"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/ethermint/metrics"
ethermint "github.com/cosmos/ethermint/types"
"github.com/cosmos/ethermint/x/evm/types"
)
var _ types.MsgServer = Keeper{}
var _ types.MsgServer = &Keeper{}
func (k *Keeper) EthereumTx(goCtx context.Context, msg *types.MsgEthereumTx) (*types.MsgEthereumTxResponse, error) {
metrics.ReportFuncCall(k.svcTags)
doneFn := metrics.ReportFuncTiming(k.svcTags)
defer doneFn()
// EthereumTx implements the Msg/EthereumTx gRPC method.
func (k Keeper) EthereumTx(goCtx context.Context, msg *types.MsgEthereumTx) (*types.MsgEthereumTxResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
// parse the chainID from a string to a base-10 integer
chainIDEpoch, err := ethermint.ParseChainID(ctx.ChainID())
if err != nil {
metrics.ReportFuncError(k.svcTags)
return nil, err
}
// Verify signature and retrieve sender address
sender, err := msg.VerifySig(chainIDEpoch)
if err != nil {
return nil, err
var homesteadErr error
sender, eip155Err := msg.VerifySig(chainIDEpoch)
if eip155Err != nil {
sender, homesteadErr = msg.VerifySigHomestead()
if homesteadErr != nil {
log.WithFields(log.Fields{
"eip155_err": eip155Err.Error(),
"homestead_err": homesteadErr.Error(),
}).Warningln("failed to verify signatures with EIP155 and Homestead signers")
metrics.ReportFuncError(k.svcTags)
return nil, errors.New("no valid signatures")
}
}
txHash := tmtypes.Tx(ctx.TxBytes()).Hash()
ethHash := ethcmn.BytesToHash(txHash)
var recipient *ethcmn.Address
labels := []metrics.Label{telemetry.NewLabel("operation", "create")}
if msg.Data.Recipient != nil {
addr := ethcmn.HexToAddress(msg.Data.Recipient.Address)
if len(msg.Data.Recipient) > 0 {
addr := ethcmn.BytesToAddress(msg.Data.Recipient)
recipient = &addr
labels = []metrics.Label{telemetry.NewLabel("operation", "call")}
}
st := types.StateTransition{
st := &types.StateTransition{
AccountNonce: msg.Data.AccountNonce,
Price: msg.Data.Price.BigInt(),
Price: new(big.Int).SetBytes(msg.Data.Price),
GasLimit: msg.Data.GasLimit,
Recipient: recipient,
Amount: msg.Data.Amount.BigInt(),
Amount: new(big.Int).SetBytes(msg.Data.Amount),
Payload: msg.Data.Payload,
Csdb: k.CommitStateDB.WithContext(ctx),
ChainID: chainIDEpoch,
@@ -66,18 +77,47 @@ func (k Keeper) EthereumTx(goCtx context.Context, msg *types.MsgEthereumTx) (*ty
// other nodes, causing a consensus error
if !st.Simulate {
// Prepare db for logs
blockHash := types.HashFromContext(ctx)
k.Prepare(ctx, ethHash, blockHash, k.TxCount)
hash, _ := k.GetBlockHashFromHeight(ctx, ctx.BlockHeight())
k.Prepare(ctx, ethHash, ethcmn.BytesToHash(hash), k.TxCount)
k.TxCount++
}
config, found := k.GetChainConfig(ctx)
if !found {
metrics.ReportFuncError(k.svcTags)
return nil, types.ErrChainConfigNotFound
}
executionResult, err := st.TransitionDb(ctx, config)
if err != nil {
if err.Error() == "execution reverted" && executionResult != nil {
// keep the execution result for revert reason
executionResult.Response.Reverted = true
metrics.ReportFuncError(k.svcTags)
if !st.Simulate {
blockHash, _ := k.GetBlockHashFromHeight(ctx, ctx.BlockHeight())
k.SetTxReceiptToHash(ctx, ethHash, &types.TxReceipt{
Hash: ethHash.Bytes(),
From: sender.Bytes(),
Data: msg.Data,
BlockHeight: uint64(ctx.BlockHeight()),
BlockHash: blockHash,
Result: &types.TxResult{
ContractAddress: executionResult.Response.ContractAddress,
Bloom: executionResult.Response.Bloom,
TxLogs: executionResult.Response.TxLogs,
Ret: executionResult.Response.Ret,
Reverted: executionResult.Response.Reverted,
GasUsed: executionResult.GasInfo.GasConsumed,
},
})
}
return executionResult.Response, nil
}
metrics.ReportFuncError(k.svcTags)
return nil, err
}
@@ -86,28 +126,42 @@ func (k Keeper) EthereumTx(goCtx context.Context, msg *types.MsgEthereumTx) (*ty
k.Bloom.Or(k.Bloom, executionResult.Bloom)
// update transaction logs in KVStore
err = k.SetLogs(ctx, ethcmn.BytesToHash(txHash), executionResult.Logs)
err = k.SetLogs(ctx, ethHash, executionResult.Logs)
if err != nil {
panic(err)
}
}
// add metrics for the transaction
defer func() {
if st.Amount.IsInt64() {
telemetry.SetGaugeWithLabels(
[]string{"tx", "msg", "ethereum"},
float32(st.Amount.Int64()),
labels,
)
blockHash, _ := k.GetBlockHashFromHeight(ctx, ctx.BlockHeight())
k.SetTxReceiptToHash(ctx, ethHash, &types.TxReceipt{
Hash: ethHash.Bytes(),
From: sender.Bytes(),
Data: msg.Data,
Index: uint64(st.Csdb.TxIndex()),
BlockHeight: uint64(ctx.BlockHeight()),
BlockHash: blockHash,
Result: &types.TxResult{
ContractAddress: executionResult.Response.ContractAddress,
Bloom: executionResult.Response.Bloom,
TxLogs: executionResult.Response.TxLogs,
Ret: executionResult.Response.Ret,
Reverted: executionResult.Response.Reverted,
GasUsed: executionResult.GasInfo.GasConsumed,
},
})
k.AddTxHashToBlock(ctx, ctx.BlockHeight(), ethHash)
for _, ethLog := range executionResult.Logs {
k.LogsCache[ethLog.Address] = append(k.LogsCache[ethLog.Address], ethLog)
}
}()
}
// emit events
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
types.EventTypeEthereumTx,
sdk.NewAttribute(sdk.AttributeKeyAmount, st.Amount.String()),
sdk.NewAttribute(types.AttributeKeyTxHash, ethcmn.BytesToHash(txHash).Hex()),
),
sdk.NewEvent(
sdk.EventTypeMessage,
@@ -116,11 +170,209 @@ func (k Keeper) EthereumTx(goCtx context.Context, msg *types.MsgEthereumTx) (*ty
),
})
if msg.Data.Recipient != nil {
if len(msg.Data.Recipient) > 0 {
ethAddr := ethcmn.BytesToAddress(msg.Data.Recipient)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeEthereumTx,
sdk.NewAttribute(types.AttributeKeyRecipient, msg.Data.Recipient.Address),
sdk.NewAttribute(types.AttributeKeyRecipient, ethAddr.Hex()),
),
)
}
metrics.ReportFuncError(k.svcTags)
return executionResult.Response, nil
}
func (k *Keeper) SendInternalEthereumTx(
ctx sdk.Context,
payload []byte,
senderAddress common.Address,
recipientAddress common.Address,
) (*types.MsgEthereumTxResponse, error) {
accAddress := sdk.AccAddress(senderAddress.Bytes())
acc := k.accountKeeper.GetAccount(ctx, accAddress)
if acc == nil {
acc = k.accountKeeper.NewAccountWithAddress(ctx, accAddress)
k.accountKeeper.SetAccount(ctx, acc)
}
ethAccount, ok := acc.(*ethermint.EthAccount)
if !ok {
return nil, errors.New("could not cast account to EthAccount")
}
if err := ethAccount.SetSequence(ethAccount.GetSequence() + 1); err != nil {
return nil, errors.New("failed to set acc sequence")
}
k.accountKeeper.SetAccount(ctx, ethAccount)
res, err := k.InternalEthereumTx(sdk.WrapSDKContext(ctx), senderAddress, &types.TxData{
AccountNonce: ethAccount.GetSequence(),
Recipient: recipientAddress.Bytes(),
Amount: big.NewInt(0).Bytes(),
Price: big.NewInt(0).Bytes(),
GasLimit: 10000000, // TODO: don't hardcode, maybe set a limit?
Payload: payload,
})
if err != nil {
err = errors.Wrapf(err, "failed to execute InternalEthereumTx at contract %s", recipientAddress.Hex())
return nil, err
}
return res, nil
}
func (k *Keeper) InternalEthereumTx(
goCtx context.Context,
sender common.Address,
tx *types.TxData,
) (*types.MsgEthereumTxResponse, error) {
metrics.ReportFuncCall(k.svcTags)
doneFn := metrics.ReportFuncTiming(k.svcTags)
defer doneFn()
ctx := sdk.UnwrapSDKContext(goCtx)
// parse the chainID from a string to a base-10 integer
chainIDEpoch, err := ethermint.ParseChainID(ctx.ChainID())
if err != nil {
metrics.ReportFuncError(k.svcTags)
return nil, err
}
// true Ethereum tx hash based on its data
ethHash := (&types.MsgEthereumTx{
Data: tx,
}).RLPSignBytes(chainIDEpoch)
var recipient *ethcmn.Address
if len(tx.Recipient) > 0 {
addr := ethcmn.BytesToAddress(tx.Recipient)
recipient = &addr
}
st := &types.StateTransition{
AccountNonce: tx.AccountNonce,
Price: new(big.Int).SetBytes(tx.Price),
GasLimit: tx.GasLimit,
Recipient: recipient,
Amount: new(big.Int).SetBytes(tx.Amount),
Payload: tx.Payload,
Csdb: k.CommitStateDB.WithContext(ctx),
ChainID: chainIDEpoch,
TxHash: &ethHash,
Sender: sender,
Simulate: ctx.IsCheckTx(),
}
// since the txCount is used by the stateDB, and a simulated tx is run only on the node it's submitted to,
// then this will cause the txCount/stateDB of the node that ran the simulated tx to be different than the
// other nodes, causing a consensus error
if !st.Simulate {
// Prepare db for logs
hash, _ := k.GetBlockHashFromHeight(ctx, ctx.BlockHeight())
k.Prepare(ctx, ethHash, ethcmn.BytesToHash(hash), k.TxCount)
k.TxCount++
}
config, found := k.GetChainConfig(ctx)
if !found {
metrics.ReportFuncError(k.svcTags)
return nil, types.ErrChainConfigNotFound
}
executionResult, err := st.TransitionDb(ctx, config)
if err != nil {
if err.Error() == "execution reverted" && executionResult != nil {
// keep the execution result for revert reason
executionResult.Response.Reverted = true
metrics.ReportFuncError(k.svcTags)
if !st.Simulate {
blockHash, _ := k.GetBlockHashFromHeight(ctx, ctx.BlockHeight())
k.SetTxReceiptToHash(ctx, ethHash, &types.TxReceipt{
Hash: ethHash.Bytes(),
From: sender.Bytes(),
Data: tx,
BlockHeight: uint64(ctx.BlockHeight()),
BlockHash: blockHash,
Result: &types.TxResult{
ContractAddress: executionResult.Response.ContractAddress,
Bloom: executionResult.Response.Bloom,
TxLogs: executionResult.Response.TxLogs,
Ret: executionResult.Response.Ret,
Reverted: executionResult.Response.Reverted,
GasUsed: executionResult.GasInfo.GasConsumed,
},
})
}
return executionResult.Response, err
}
metrics.ReportFuncError(k.svcTags)
return nil, err
}
blockHash, _ := k.GetBlockHashFromHeight(ctx, ctx.BlockHeight())
k.SetTxReceiptToHash(ctx, ethHash, &types.TxReceipt{
Hash: ethHash.Bytes(),
From: sender.Bytes(),
Data: tx,
Index: uint64(st.Csdb.TxIndex()),
BlockHeight: uint64(ctx.BlockHeight()),
BlockHash: blockHash,
Result: &types.TxResult{
ContractAddress: executionResult.Response.ContractAddress,
Bloom: executionResult.Response.Bloom,
TxLogs: executionResult.Response.TxLogs,
Ret: executionResult.Response.Ret,
Reverted: executionResult.Response.Reverted,
GasUsed: executionResult.GasInfo.GasConsumed,
},
})
k.AddTxHashToBlock(ctx, ctx.BlockHeight(), ethHash)
if !st.Simulate {
// update block bloom filter
k.Bloom.Or(k.Bloom, executionResult.Bloom)
// update transaction logs in KVStore
err = k.SetLogs(ctx, ethHash, executionResult.Logs)
if err != nil {
panic(err)
}
for _, ethLog := range executionResult.Logs {
k.LogsCache[ethLog.Address] = append(k.LogsCache[ethLog.Address], ethLog)
}
}
// emit events
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
types.EventTypeEthereumTx,
sdk.NewAttribute(sdk.AttributeKeyAmount, st.Amount.String()),
sdk.NewAttribute(types.AttributeKeyTxHash, ethHash.Hex()),
),
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
sdk.NewAttribute(sdk.AttributeKeySender, sender.String()),
),
})
if len(tx.Recipient) > 0 {
ethAddr := ethcmn.BytesToAddress(tx.Recipient)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeEthereumTx,
sdk.NewAttribute(types.AttributeKeyRecipient, ethAddr.Hex()),
),
)
}
+1 -1
View File
@@ -7,7 +7,7 @@ import (
func (suite *KeeperTestSuite) TestParams() {
params := suite.app.EvmKeeper.GetParams(suite.ctx)
suite.Require().Equal(types.DefaultParams(), params)
params.EvmDenom = "ara"
params.EvmDenom = "inj"
suite.app.EvmKeeper.SetParams(suite.ctx, params)
newParams := suite.app.EvmKeeper.GetParams(suite.ctx)
suite.Require().Equal(newParams, params)
+7 -2
View File
@@ -49,6 +49,11 @@ func (k *Keeper) SetCode(ctx sdk.Context, addr ethcmn.Address, code []byte) {
// SetLogs calls CommitStateDB.SetLogs using the passed in context
func (k *Keeper) SetLogs(ctx sdk.Context, hash ethcmn.Hash, logs []*ethtypes.Log) error {
// TODO:@albert
// since SetLogs is only called for non-simulation mode, GasEstimation is quite not correct
// I suggest using ctx.ConsumeGas(XXX) where XXX is max of gas consume for set logs.
ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter())
return k.CommitStateDB.WithContext(ctx).SetLogs(hash, logs)
}
@@ -225,8 +230,8 @@ func (k *Keeper) Reset(ctx sdk.Context, root ethcmn.Hash) error {
}
// Prepare calls CommitStateDB.Prepare using the passed in context
func (k *Keeper) Prepare(ctx sdk.Context, thash ethcmn.Hash, txi int) {
k.CommitStateDB.WithContext(ctx).Prepare(thash, txi)
func (k *Keeper) Prepare(ctx sdk.Context, thash, bhash ethcmn.Hash, txi int) {
k.CommitStateDB.WithContext(ctx).Prepare(thash, bhash, txi)
}
// CreateAccount calls CommitStateDB.CreateAccount using the passed in context
+2 -4
View File
@@ -18,7 +18,7 @@ import (
func (suite *KeeperTestSuite) TestBloomFilter() {
// Prepare db for logs
tHash := ethcmn.BytesToHash([]byte{0x1})
suite.app.EvmKeeper.Prepare(suite.ctx, tHash, 0)
suite.app.EvmKeeper.Prepare(suite.ctx, tHash, ethcmn.Hash{}, 0)
contractAddress := ethcmn.BigToAddress(big.NewInt(1))
log := ethtypes.Log{Address: contractAddress, Topics: []ethcmn.Hash{}}
@@ -230,7 +230,6 @@ func (suite *KeeperTestSuite) TestStateDB_Logs() {
suite.Require().Empty(dbLogs, tc.name)
suite.app.EvmKeeper.AddLog(suite.ctx, tc.log)
tc.log.Index = 0 // reset index
suite.Require().Equal(logs, suite.app.EvmKeeper.AllLogs(suite.ctx), tc.name)
//resets state but checking to see if storekey still persists.
@@ -360,8 +359,7 @@ func (suite *KeeperTestSuite) TestSuiteDB_Prepare() {
bhash := ethcmn.BytesToHash([]byte("bhash"))
txi := 1
suite.app.EvmKeeper.Prepare(suite.ctx, thash, txi)
suite.app.EvmKeeper.CommitStateDB.SetBlockHash(bhash)
suite.app.EvmKeeper.Prepare(suite.ctx, thash, bhash, txi)
suite.Require().Equal(txi, suite.app.EvmKeeper.TxIndex(suite.ctx))
suite.Require().Equal(bhash, suite.app.EvmKeeper.BlockHash(suite.ctx))