stargate: migrate types (#670)

* changelog v0.4.0

* stargate: types changes

* msg and handler changes

* validation

* fixes

* more fixes

* more test fixes

* changelog

* changelog

* lint

* rm comment

* lint

* redundant if condition
This commit is contained in:
Federico Kunze 2021-01-06 17:56:40 -03:00 committed by GitHub
parent 64ada18b01
commit 9cbb4dcf6d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 540 additions and 586 deletions

View File

@ -39,6 +39,16 @@ Ref: https://keepachangelog.com/en/1.0.0/
### API Breaking
* (evm) [\#670](https://github.com/cosmos/ethermint/pull/670) Migrate types to the ones defined by the protobuf messages, which are required for the stargate release.
### Bug Fixes
* (evm) [\#674](https://github.com/cosmos/ethermint/issues/674) Reset all cache after account data has been committed in `EndBlock` to make sure every node state consistent
## [v0.4.0] - 2020-12-15
### API Breaking
* (evm) [\#661](https://github.com/cosmos/ethermint/pull/661) `Balance` field has been removed from the evm module's `GenesisState`.
### Features
@ -60,7 +70,6 @@ Ref: https://keepachangelog.com/en/1.0.0/
### Bug Fixes
* (evm) [\#674](https://github.com/cosmos/ethermint/issues/674) Reset all cache after account data has been committed in `EndBlock` to make sure every node state consistent
* (evm) [\#661](https://github.com/cosmos/ethermint/pull/661) Set nonce to the EVM account on genesis initialization.
* (rpc) [\#648](https://github.com/cosmos/ethermint/issues/648) Fix block cumulative gas used value.
* (evm) [\#621](https://github.com/cosmos/ethermint/issues/621) EVM `GenesisAccount` fields now share the same format as the auth module `Account`.

View File

@ -330,7 +330,7 @@ func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula
// Charge sender for gas up to limit
if gasLimit != 0 {
// Cost calculates the fees paid to validators based on gas limit and price
cost := new(big.Int).Mul(msgEthTx.Data.Price, new(big.Int).SetUint64(gasLimit))
cost := new(big.Int).Mul(msgEthTx.Data.Price.BigInt(), new(big.Int).SetUint64(gasLimit))
evmDenom := egcd.evmKeeper.GetParams(ctx).EvmDenom

View File

@ -48,15 +48,15 @@ func NewTransaction(tx *evmtypes.MsgEthereumTx, txHash, blockHash common.Hash, b
rpcTx := &Transaction{
From: from,
Gas: hexutil.Uint64(tx.Data.GasLimit),
GasPrice: (*hexutil.Big)(tx.Data.Price),
GasPrice: (*hexutil.Big)(tx.Data.Price.BigInt()),
Hash: txHash,
Input: hexutil.Bytes(tx.Data.Payload),
Nonce: hexutil.Uint64(tx.Data.AccountNonce),
To: tx.To(),
Value: (*hexutil.Big)(tx.Data.Amount),
V: (*hexutil.Big)(tx.Data.V),
R: (*hexutil.Big)(tx.Data.R),
S: (*hexutil.Big)(tx.Data.S),
Value: (*hexutil.Big)(tx.Data.Amount.BigInt()),
V: (*hexutil.Big)(new(big.Int).SetBytes(tx.Data.V)),
R: (*hexutil.Big)(new(big.Int).SetBytes(tx.Data.R)),
S: (*hexutil.Big)(new(big.Int).SetBytes(tx.Data.S)),
}
if blockHash != (common.Hash{}) {

View File

@ -1,23 +0,0 @@
syntax = "proto3";
package ethermint.v1;
import "third_party/proto/gogoproto/gogo.proto";
import "third_party/proto/cosmos-sdk/x/auth/types/types.proto";
option go_package = "github.com/cosmos/ethermint/types";
// EthAccount implements the auth.Account interface and embeds an
// auth.BaseAccount type. It is compatible with the auth.AccountKeeper.
message EthAccount {
option (gogoproto.goproto_getters) = false;
option (gogoproto.goproto_stringer) = false;
cosmos_sdk.x.auth.v1.BaseAccount base_account = 1 [
(gogoproto.embed) = true,
(gogoproto.moretags) = "yaml:\"base_account\""
];
bytes code_hash = 2 [
(gogoproto.moretags) = "yaml:\"code_hash\""
];
}

17
types/validation.go Normal file
View File

@ -0,0 +1,17 @@
package types
import (
"bytes"
ethcmn "github.com/ethereum/go-ethereum/common"
)
// IsEmptyHash returns true if the hash corresponds to an empty ethereum hex hash.
func IsEmptyHash(hash string) bool {
return bytes.Equal(ethcmn.HexToHash(hash).Bytes(), ethcmn.Hash{}.Bytes())
}
// IsZeroAddress returns true if the address corresponds to an empty ethereum hex address.
func IsZeroAddress(address string) bool {
return bytes.Equal(ethcmn.HexToAddress(address).Bytes(), ethcmn.Address{}.Bytes())
}

55
types/validation_test.go Normal file
View File

@ -0,0 +1,55 @@
package types
import (
"testing"
"github.com/stretchr/testify/require"
ethcmn "github.com/ethereum/go-ethereum/common"
)
func TestIsEmptyHash(t *testing.T) {
testCases := []struct {
name string
hash string
expEmpty bool
}{
{
"empty string", "", true,
},
{
"zero hash", ethcmn.Hash{}.String(), true,
},
{
"non-empty hash", ethcmn.BytesToHash([]byte{1, 2, 3, 4}).String(), false,
},
}
for _, tc := range testCases {
require.Equal(t, tc.expEmpty, IsEmptyHash(tc.hash), tc.name)
}
}
func TestIsZeroAddress(t *testing.T) {
testCases := []struct {
name string
address string
expEmpty bool
}{
{
"empty string", "", true,
},
{
"zero address", ethcmn.Address{}.String(), true,
},
{
"non-empty address", ethcmn.BytesToAddress([]byte{1, 2, 3, 4}).String(), false,
},
}
for _, tc := range testCases {
require.Equal(t, tc.expEmpty, IsZeroAddress(tc.address), tc.name)
}
}

View File

@ -9,15 +9,21 @@ import (
ethcmn "github.com/ethereum/go-ethereum/common"
ethermint "github.com/cosmos/ethermint/types"
"github.com/cosmos/ethermint/x/evm/keeper"
"github.com/cosmos/ethermint/x/evm/types"
abci "github.com/tendermint/tendermint/abci/types"
)
// InitGenesis initializes genesis state based on exported genesis
func InitGenesis(ctx sdk.Context, k Keeper, accountKeeper types.AccountKeeper, data GenesisState) []abci.ValidatorUpdate { // nolint: interfacer
k.SetParams(ctx, data.Params)
func InitGenesis(
ctx sdk.Context,
k keeper.Keeper,
accountKeeper types.AccountKeeper, // nolint: interfacer
data GenesisState,
) []abci.ValidatorUpdate {
k.SetParams(ctx, data.Params)
evmDenom := data.Params.EvmDenom
for _, account := range data.Accounts {
@ -42,16 +48,16 @@ func InitGenesis(ctx sdk.Context, k Keeper, accountKeeper types.AccountKeeper, d
k.SetNonce(ctx, address, acc.GetSequence())
k.SetBalance(ctx, address, evmBalance.BigInt())
k.SetCode(ctx, address, account.Code)
k.SetCode(ctx, address, ethcmn.Hex2Bytes(account.Code))
for _, storage := range account.Storage {
k.SetState(ctx, address, storage.Key, storage.Value)
k.SetState(ctx, address, ethcmn.HexToHash(storage.Key), ethcmn.HexToHash(storage.Value))
}
}
var err error
for _, txLog := range data.TxsLogs {
if err = k.SetLogs(ctx, txLog.Hash, txLog.Logs); err != nil {
if err = k.SetLogs(ctx, ethcmn.HexToHash(txLog.Hash), txLog.Logs); err != nil {
panic(err)
}
}
@ -75,7 +81,7 @@ func InitGenesis(ctx sdk.Context, k Keeper, accountKeeper types.AccountKeeper, d
}
// ExportGenesis exports genesis state of the EVM module
func ExportGenesis(ctx sdk.Context, k Keeper, ak types.AccountKeeper) GenesisState {
func ExportGenesis(ctx sdk.Context, k keeper.Keeper, ak types.AccountKeeper) GenesisState {
// nolint: prealloc
var ethGenAccounts []types.GenesisAccount
ak.IterateAccounts(ctx, func(account authexported.Account) bool {
@ -94,7 +100,7 @@ func ExportGenesis(ctx sdk.Context, k Keeper, ak types.AccountKeeper) GenesisSta
genAccount := types.GenesisAccount{
Address: addr.String(),
Code: k.GetCode(ctx, addr),
Code: ethcmn.Bytes2Hex(k.GetCode(ctx, addr)),
Storage: storage,
}

View File

@ -55,7 +55,7 @@ func (suite *EvmTestSuite) TestInitGenesis() {
{
Address: address.String(),
Storage: types.Storage{
{Key: common.BytesToHash([]byte("key")), Value: common.BytesToHash([]byte("value"))},
{Key: common.BytesToHash([]byte("key")).String(), Value: common.BytesToHash([]byte("value")).String()},
},
},
},

View File

@ -29,93 +29,16 @@ func NewHandler(k Keeper) sdk.Handler {
// handleMsgEthereumTx handles an Ethereum specific tx
func handleMsgEthereumTx(ctx sdk.Context, k Keeper, msg types.MsgEthereumTx) (*sdk.Result, error) {
// parse the chainID from a string to a base-10 integer
chainIDEpoch, err := ethermint.ParseChainID(ctx.ChainID())
// execute state transition
res, err := k.EthereumTx(ctx, msg)
if err != nil {
return nil, err
}
// Verify signature and retrieve sender address
sender, err := msg.VerifySig(chainIDEpoch)
if err != nil {
return nil, err
}
// log state transition result
k.Logger(ctx).Info(res.Log)
txHash := tmtypes.Tx(ctx.TxBytes()).Hash()
ethHash := common.BytesToHash(txHash)
st := types.StateTransition{
AccountNonce: msg.Data.AccountNonce,
Price: msg.Data.Price,
GasLimit: msg.Data.GasLimit,
Recipient: msg.Data.Recipient,
Amount: msg.Data.Amount,
Payload: msg.Data.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
blockHash := types.HashFromContext(ctx)
k.CommitStateDB.Prepare(ethHash, blockHash, k.TxCount)
k.TxCount++
}
config, found := k.GetChainConfig(ctx)
if !found {
return nil, types.ErrChainConfigNotFound
}
executionResult, err := st.TransitionDb(ctx, config)
if err != nil {
return nil, err
}
if !st.Simulate {
// update block bloom filter
k.Bloom.Or(k.Bloom, executionResult.Bloom)
// update transaction logs in KVStore
err = k.SetLogs(ctx, common.BytesToHash(txHash), executionResult.Logs)
if err != nil {
panic(err)
}
}
// log successful execution
k.Logger(ctx).Info(executionResult.Result.Log)
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
types.EventTypeEthereumTx,
sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Data.Amount.String()),
),
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
sdk.NewAttribute(sdk.AttributeKeySender, sender.String()),
),
})
if msg.Data.Recipient != nil {
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeEthereumTx,
sdk.NewAttribute(types.AttributeKeyRecipient, msg.Data.Recipient.String()),
),
)
}
// set the events to the result
executionResult.Result.Events = ctx.EventManager().Events()
return executionResult.Result, nil
return res, nil
}
// handleMsgEthermint handles an sdk.StdTx for an Ethereum state transition

View File

@ -151,6 +151,7 @@ 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) {
storage := types.Storage{}
err := k.ForEachStorage(ctx, address, func(key, value common.Hash) bool {
storage = append(storage, types.NewState(key, value))
return false
@ -164,9 +165,9 @@ 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) {
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixChainConfig)
// get from an empty key that's already prefixed by KeyPrefixChainConfig
bz := store.Get([]byte{})
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.KeyPrefixChainConfig)
if len(bz) == 0 {
return types.ChainConfig{}, false
}
@ -178,8 +179,7 @@ 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) {
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixChainConfig)
store := ctx.KVStore(k.storeKey)
bz := k.cdc.MustMarshalBinaryBare(config)
// get to an empty key that's already prefixed by KeyPrefixChainConfig
store.Set([]byte{}, bz)
store.Set(types.KeyPrefixChainConfig, bz)
}

View File

@ -98,10 +98,10 @@ func (suite *KeeperTestSuite) TestTransactionLogs() {
txLogs := suite.app.EvmKeeper.GetAllTxLogs(suite.ctx)
suite.Require().Equal(2, len(txLogs))
suite.Require().Equal(ethcmn.Hash{}.String(), txLogs[0].Hash.String())
suite.Require().Equal(ethcmn.Hash{}.String(), txLogs[0].Hash)
suite.Require().Equal([]*ethtypes.Log{log2, log3}, txLogs[0].Logs)
suite.Require().Equal(ethHash.String(), txLogs[1].Hash.String())
suite.Require().Equal(ethHash.String(), txLogs[1].Hash)
suite.Require().Equal([]*ethtypes.Log{log}, txLogs[1].Logs)
}

104
x/evm/keeper/msg_server.go Normal file
View File

@ -0,0 +1,104 @@
package keeper
import (
"github.com/ethereum/go-ethereum/common"
tmtypes "github.com/tendermint/tendermint/types"
sdk "github.com/cosmos/cosmos-sdk/types"
ethermint "github.com/cosmos/ethermint/types"
"github.com/cosmos/ethermint/x/evm/types"
)
// EthereumTx implements the Msg/EthereumTx gRPC method.
func (k Keeper) EthereumTx(ctx sdk.Context, msg types.MsgEthereumTx) (*sdk.Result, error) {
// parse the chainID from a string to a base-10 integer
chainIDEpoch, err := ethermint.ParseChainID(ctx.ChainID())
if err != nil {
return nil, err
}
// Verify signature and retrieve sender address
sender, err := msg.VerifySig(chainIDEpoch)
if err != nil {
return nil, err
}
var recipient *common.Address
if msg.Data.Recipient != nil {
addr := common.HexToAddress(msg.Data.Recipient.Address)
recipient = &addr
}
txHash := tmtypes.Tx(ctx.TxBytes()).Hash()
ethHash := common.BytesToHash(txHash)
st := types.StateTransition{
AccountNonce: msg.Data.AccountNonce,
Price: msg.Data.Price.BigInt(),
GasLimit: msg.Data.GasLimit,
Recipient: recipient,
Amount: msg.Data.Amount.BigInt(),
Payload: msg.Data.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
blockHash := types.HashFromContext(ctx)
k.CommitStateDB.Prepare(ethHash, blockHash, k.TxCount)
k.TxCount++
}
config, found := k.GetChainConfig(ctx)
if !found {
return nil, types.ErrChainConfigNotFound
}
executionResult, err := st.TransitionDb(ctx, config)
if err != nil {
return nil, err
}
if !st.Simulate {
// update block bloom filter
k.Bloom.Or(k.Bloom, executionResult.Bloom)
// update transaction logs in KVStore
err = k.SetLogs(ctx, common.BytesToHash(txHash), executionResult.Logs)
if err != nil {
panic(err)
}
}
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
types.EventTypeEthereumTx,
sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Data.Amount.String()),
),
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
sdk.NewAttribute(sdk.AttributeKeySender, sender.String()),
),
})
if msg.Data.Recipient != nil {
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeEthereumTx,
sdk.NewAttribute(types.AttributeKeyRecipient, msg.Data.Recipient.Address),
),
)
}
executionResult.Result.Events = ctx.EventManager().Events()
return executionResult.Result, nil
}

View File

@ -1,7 +1,6 @@
package keeper
import (
"encoding/json"
"fmt"
"strconv"
@ -39,8 +38,6 @@ func NewQuerier(keeper Keeper) sdk.Querier {
return queryLogs(ctx, keeper)
case types.QueryAccount:
return queryAccount(ctx, path, keeper)
case types.QueryExportAccount:
return queryExportAccount(ctx, path, keeper)
default:
return nil, sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, "unknown query endpoint")
}
@ -183,31 +180,3 @@ func queryAccount(ctx sdk.Context, path []string, keeper Keeper) ([]byte, error)
}
return bz, nil
}
func queryExportAccount(ctx sdk.Context, path []string, keeper Keeper) ([]byte, error) {
hexAddress := path[1]
addr := ethcmn.HexToAddress(hexAddress)
var storage types.Storage
err := keeper.ForEachStorage(ctx, addr, func(key, value ethcmn.Hash) bool {
storage = append(storage, types.NewState(key, value))
return false
})
if err != nil {
return nil, err
}
res := types.GenesisAccount{
Address: hexAddress,
Code: keeper.GetCode(ctx, addr),
Storage: storage,
}
// TODO: codec.MarshalJSONIndent doesn't call the String() method of types properly
bz, err := json.MarshalIndent(res, "", "\t")
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
}
return bz, nil
}

View File

@ -34,7 +34,6 @@ func (suite *KeeperTestSuite) TestQuerier() {
}, true},
{"logs", []string{types.QueryLogs, "0x0"}, func() {}, true},
{"account", []string{types.QueryAccount, "0x0"}, func() {}, true},
{"exportAccount", []string{types.QueryExportAccount, "0x0"}, func() {}, true},
{"unknown request", []string{"other"}, func() {}, false},
}

View File

@ -582,7 +582,7 @@ func (suite *KeeperTestSuite) TestCommitStateDB_ForEachStorage() {
name string
malleate func()
callback func(key, value ethcmn.Hash) (stop bool)
expValues []ethcmn.Hash
expValues []string
}{
{
"aggregate state",
@ -595,12 +595,12 @@ func (suite *KeeperTestSuite) TestCommitStateDB_ForEachStorage() {
storage = append(storage, types.NewState(key, value))
return false
},
[]ethcmn.Hash{
ethcmn.BytesToHash([]byte("value0")),
ethcmn.BytesToHash([]byte("value1")),
ethcmn.BytesToHash([]byte("value2")),
ethcmn.BytesToHash([]byte("value3")),
ethcmn.BytesToHash([]byte("value4")),
[]string{
ethcmn.BytesToHash([]byte("value0")).String(),
ethcmn.BytesToHash([]byte("value1")).String(),
ethcmn.BytesToHash([]byte("value2")).String(),
ethcmn.BytesToHash([]byte("value3")).String(),
ethcmn.BytesToHash([]byte("value4")).String(),
},
},
{
@ -616,8 +616,8 @@ func (suite *KeeperTestSuite) TestCommitStateDB_ForEachStorage() {
}
return false
},
[]ethcmn.Hash{
ethcmn.BytesToHash([]byte("filtervalue")),
[]string{
ethcmn.BytesToHash([]byte("filtervalue")).String(),
},
},
}
@ -632,7 +632,7 @@ func (suite *KeeperTestSuite) TestCommitStateDB_ForEachStorage() {
suite.Require().NoError(err)
suite.Require().Equal(len(tc.expValues), len(storage), fmt.Sprintf("Expected values:\n%v\nStorage Values\n%v", tc.expValues, storage))
vals := make([]ethcmn.Hash, len(storage))
vals := make([]string, len(storage))
for i := range storage {
vals[i] = storage[i].Value
}

View File

@ -151,12 +151,6 @@ func validateHash(hex string) error {
return sdkerrors.Wrapf(ErrInvalidChainConfig, "hash cannot be blank")
}
bz := common.FromHex(hex)
lenHex := len(bz)
if lenHex > 0 && lenHex != common.HashLength {
return sdkerrors.Wrapf(ErrInvalidChainConfig, "invalid hash length, expected %d, got %d", common.HashLength, lenHex)
}
return nil
}

View File

@ -4,8 +4,9 @@ import (
"errors"
"fmt"
ethermint "github.com/cosmos/ethermint/types"
ethcmn "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
type (
@ -22,19 +23,19 @@ type (
// storage type and that it doesn't contain the private key field.
// NOTE: balance is omitted as it is imported from the auth account balance.
GenesisAccount struct {
Address string `json:"address"`
Code hexutil.Bytes `json:"code,omitempty"`
Storage Storage `json:"storage,omitempty"`
Address string `json:"address"`
Code string `json:"code,omitempty"`
Storage Storage `json:"storage,omitempty"`
}
)
// Validate performs a basic validation of a GenesisAccount fields.
func (ga GenesisAccount) Validate() error {
if ga.Address == (ethcmn.Address{}.String()) {
if ethermint.IsZeroAddress(ga.Address) {
return fmt.Errorf("address cannot be the zero address %s", ga.Address)
}
if ga.Code != nil && len(ga.Code) == 0 {
return errors.New("code bytes cannot be empty")
if len(ethcmn.Hex2Bytes(ga.Code)) == 0 {
return errors.New("code cannot be empty")
}
return ga.Storage.Validate()
@ -67,15 +68,15 @@ func (gs GenesisState) Validate() error {
}
for _, tx := range gs.TxsLogs {
if seenTxs[tx.Hash.String()] {
return fmt.Errorf("duplicated logs from transaction %s", tx.Hash.String())
if seenTxs[tx.Hash] {
return fmt.Errorf("duplicated logs from transaction %s", tx.Hash)
}
if err := tx.Validate(); err != nil {
return fmt.Errorf("invalid logs from transaction %s: %w", tx.Hash.String(), err)
return fmt.Errorf("invalid logs from transaction %s: %w", tx.Hash, err)
}
seenTxs[tx.Hash.String()] = true
seenTxs[tx.Hash] = true
}
if err := gs.ChainConfig.Validate(); err != nil {

View File

@ -3,18 +3,36 @@ package types
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
ethcmn "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/cosmos/ethermint/crypto/ethsecp256k1"
)
var address = ethcmn.BytesToAddress([]byte{1, 2, 3, 4, 5})
type GenesisTestSuite struct {
suite.Suite
func TestValidateGenesisAccount(t *testing.T) {
address ethcmn.Address
hash ethcmn.Hash
code string
}
func (suite *GenesisTestSuite) SetupTest() {
priv, err := ethsecp256k1.GenerateKey()
suite.Require().NoError(err)
suite.address = ethcmn.BytesToAddress(priv.PubKey().Address().Bytes())
suite.hash = ethcmn.BytesToHash([]byte("hash"))
suite.code = ethcmn.Bytes2Hex([]byte{1, 2, 3})
}
func TestGenesisTestSuite(t *testing.T) {
suite.Run(t, new(GenesisTestSuite))
}
func (suite *GenesisTestSuite) TestValidateGenesisAccount() {
testCases := []struct {
name string
genesisAccount GenesisAccount
@ -23,10 +41,10 @@ func TestValidateGenesisAccount(t *testing.T) {
{
"valid genesis account",
GenesisAccount{
Address: address.String(),
Code: []byte{1, 2, 3},
Address: suite.address.String(),
Code: suite.code,
Storage: Storage{
NewState(ethcmn.BytesToHash([]byte{1, 2, 3}), ethcmn.BytesToHash([]byte{1, 2, 3})),
NewState(suite.hash, suite.hash),
},
},
true,
@ -41,8 +59,8 @@ func TestValidateGenesisAccount(t *testing.T) {
{
"empty code bytes",
GenesisAccount{
Address: address.String(),
Code: []byte{},
Address: suite.address.String(),
Code: "",
},
false,
},
@ -52,18 +70,14 @@ func TestValidateGenesisAccount(t *testing.T) {
tc := tc
err := tc.genesisAccount.Validate()
if tc.expPass {
require.NoError(t, err, tc.name)
suite.Require().NoError(err, tc.name)
} else {
require.Error(t, err, tc.name)
suite.Require().Error(err, tc.name)
}
}
}
func TestValidateGenesis(t *testing.T) {
priv, err := ethsecp256k1.GenerateKey()
require.NoError(t, err)
addr := ethcrypto.PubkeyToAddress(priv.ToECDSA().PublicKey)
func (suite *GenesisTestSuite) TestValidateGenesis() {
testCases := []struct {
name string
genState GenesisState
@ -79,25 +93,25 @@ func TestValidateGenesis(t *testing.T) {
genState: GenesisState{
Accounts: []GenesisAccount{
{
Address: address.String(),
Code: []byte{1, 2, 3},
Address: suite.address.String(),
Code: suite.code,
Storage: Storage{
{Key: ethcmn.BytesToHash([]byte{1, 2, 3})},
{Key: suite.hash.String()},
},
},
},
TxsLogs: []TransactionLogs{
{
Hash: ethcmn.BytesToHash([]byte("tx_hash")),
Hash: suite.hash.String(),
Logs: []*ethtypes.Log{
{
Address: addr,
Topics: []ethcmn.Hash{ethcmn.BytesToHash([]byte("topic"))},
Address: suite.address,
Topics: []ethcmn.Hash{suite.hash},
Data: []byte("data"),
BlockNumber: 1,
TxHash: ethcmn.BytesToHash([]byte("tx_hash")),
TxHash: suite.hash,
TxIndex: 1,
BlockHash: ethcmn.BytesToHash([]byte("block_hash")),
BlockHash: suite.hash,
Index: 1,
Removed: false,
},
@ -130,17 +144,17 @@ func TestValidateGenesis(t *testing.T) {
genState: GenesisState{
Accounts: []GenesisAccount{
{
Address: address.String(),
Code: []byte{1, 2, 3},
Address: suite.address.String(),
Code: suite.code,
Storage: Storage{
NewState(ethcmn.BytesToHash([]byte{1, 2, 3}), ethcmn.BytesToHash([]byte{1, 2, 3})),
NewState(suite.hash, suite.hash),
},
},
{
Address: address.String(),
Code: []byte{1, 2, 3},
Address: suite.address.String(),
Code: suite.code,
Storage: Storage{
NewState(ethcmn.BytesToHash([]byte{1, 2, 3}), ethcmn.BytesToHash([]byte{1, 2, 3})),
NewState(suite.hash, suite.hash),
},
},
},
@ -152,41 +166,41 @@ func TestValidateGenesis(t *testing.T) {
genState: GenesisState{
Accounts: []GenesisAccount{
{
Address: address.String(),
Code: []byte{1, 2, 3},
Address: suite.address.String(),
Code: suite.code,
Storage: Storage{
{Key: ethcmn.BytesToHash([]byte{1, 2, 3})},
{Key: suite.hash.String()},
},
},
},
TxsLogs: []TransactionLogs{
{
Hash: ethcmn.BytesToHash([]byte("tx_hash")),
Hash: suite.hash.String(),
Logs: []*ethtypes.Log{
{
Address: addr,
Topics: []ethcmn.Hash{ethcmn.BytesToHash([]byte("topic"))},
Address: suite.address,
Topics: []ethcmn.Hash{suite.hash},
Data: []byte("data"),
BlockNumber: 1,
TxHash: ethcmn.BytesToHash([]byte("tx_hash")),
TxHash: suite.hash,
TxIndex: 1,
BlockHash: ethcmn.BytesToHash([]byte("block_hash")),
BlockHash: suite.hash,
Index: 1,
Removed: false,
},
},
},
{
Hash: ethcmn.BytesToHash([]byte("tx_hash")),
Hash: suite.hash.String(),
Logs: []*ethtypes.Log{
{
Address: addr,
Topics: []ethcmn.Hash{ethcmn.BytesToHash([]byte("topic"))},
Address: suite.address,
Topics: []ethcmn.Hash{suite.hash},
Data: []byte("data"),
BlockNumber: 1,
TxHash: ethcmn.BytesToHash([]byte("tx_hash")),
TxHash: suite.hash,
TxIndex: 1,
BlockHash: ethcmn.BytesToHash([]byte("block_hash")),
BlockHash: suite.hash,
Index: 1,
Removed: false,
},
@ -201,10 +215,10 @@ func TestValidateGenesis(t *testing.T) {
genState: GenesisState{
Accounts: []GenesisAccount{
{
Address: address.String(),
Code: []byte{1, 2, 3},
Address: suite.address.String(),
Code: suite.code,
Storage: Storage{
{Key: ethcmn.BytesToHash([]byte{1, 2, 3})},
{Key: suite.hash.String()},
},
},
},
@ -234,9 +248,9 @@ func TestValidateGenesis(t *testing.T) {
tc := tc
err := tc.genState.Validate()
if tc.expPass {
require.NoError(t, err, tc.name)
suite.Require().NoError(err, tc.name)
} else {
require.Error(t, err, tc.name)
suite.Require().Error(err, tc.name)
}
}
}

View File

@ -1,10 +1,10 @@
package types
import (
"bytes"
"errors"
"fmt"
ethermint "github.com/cosmos/ethermint/types"
ethcmn "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
)
@ -13,14 +13,14 @@ import (
// with a given hash. It it used for import/export data as transactions are not persisted
// on blockchain state after an upgrade.
type TransactionLogs struct {
Hash ethcmn.Hash `json:"hash"`
Hash string `json:"hash"`
Logs []*ethtypes.Log `json:"logs"`
}
// NewTransactionLogs creates a new NewTransactionLogs instance.
func NewTransactionLogs(hash ethcmn.Hash, logs []*ethtypes.Log) TransactionLogs {
func NewTransactionLogs(hash ethcmn.Hash, logs []*ethtypes.Log) TransactionLogs { // nolint: interfacer
return TransactionLogs{
Hash: hash,
Hash: hash.String(),
Logs: logs,
}
}
@ -39,16 +39,16 @@ func UnmarshalLogs(in []byte) ([]*ethtypes.Log, error) {
// Validate performs a basic validation of a GenesisAccount fields.
func (tx TransactionLogs) Validate() error {
if bytes.Equal(tx.Hash.Bytes(), ethcmn.Hash{}.Bytes()) {
return fmt.Errorf("hash cannot be the empty %s", tx.Hash.String())
if ethermint.IsEmptyHash(tx.Hash) {
return fmt.Errorf("hash cannot be the empty %s", tx.Hash)
}
for i, log := range tx.Logs {
if err := ValidateLog(log); err != nil {
return fmt.Errorf("invalid log %d: %w", i, err)
}
if !bytes.Equal(log.TxHash.Bytes(), tx.Hash.Bytes()) {
return fmt.Errorf("log tx hash mismatch (%s ≠ %s)", log.TxHash.String(), tx.Hash.String())
if log.TxHash.String() != tx.Hash {
return fmt.Errorf("log tx hash mismatch (%s ≠ %s)", log.TxHash.String(), tx.Hash)
}
}
return nil
@ -59,16 +59,16 @@ func ValidateLog(log *ethtypes.Log) error {
if log == nil {
return errors.New("log cannot be nil")
}
if bytes.Equal(log.Address.Bytes(), ethcmn.Address{}.Bytes()) {
if ethermint.IsZeroAddress(log.Address.String()) {
return fmt.Errorf("log address cannot be empty %s", log.Address.String())
}
if bytes.Equal(log.BlockHash.Bytes(), ethcmn.Hash{}.Bytes()) {
if ethermint.IsEmptyHash(log.BlockHash.String()) {
return fmt.Errorf("block hash cannot be the empty %s", log.BlockHash.String())
}
if log.BlockNumber == 0 {
return errors.New("block number cannot be zero")
}
if bytes.Equal(log.TxHash.Bytes(), ethcmn.Hash{}.Bytes()) {
if ethermint.IsEmptyHash(log.TxHash.String()) {
return fmt.Errorf("tx hash cannot be the empty %s", log.TxHash.String())
}
return nil

View File

@ -1,21 +1,11 @@
package types
import (
"testing"
"github.com/cosmos/ethermint/crypto/ethsecp256k1"
"github.com/stretchr/testify/require"
ethcmn "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
)
func TestTransactionLogsValidate(t *testing.T) {
priv, err := ethsecp256k1.GenerateKey()
require.NoError(t, err)
addr := ethcrypto.PubkeyToAddress(priv.ToECDSA().PublicKey)
func (suite *GenesisTestSuite) TestTransactionLogsValidate() {
testCases := []struct {
name string
txLogs TransactionLogs
@ -24,16 +14,16 @@ func TestTransactionLogsValidate(t *testing.T) {
{
"valid log",
TransactionLogs{
Hash: ethcmn.BytesToHash([]byte("tx_hash")),
Hash: suite.hash.String(),
Logs: []*ethtypes.Log{
{
Address: addr,
Address: suite.address,
Topics: []ethcmn.Hash{ethcmn.BytesToHash([]byte("topic"))},
Data: []byte("data"),
BlockNumber: 1,
TxHash: ethcmn.BytesToHash([]byte("tx_hash")),
TxHash: suite.hash,
TxIndex: 1,
BlockHash: ethcmn.BytesToHash([]byte("block_hash")),
BlockHash: suite.hash,
Index: 1,
Removed: false,
},
@ -44,14 +34,14 @@ func TestTransactionLogsValidate(t *testing.T) {
{
"empty hash",
TransactionLogs{
Hash: ethcmn.Hash{},
Hash: ethcmn.Hash{}.String(),
},
false,
},
{
"invalid log",
TransactionLogs{
Hash: ethcmn.BytesToHash([]byte("tx_hash")),
Hash: suite.hash.String(),
Logs: []*ethtypes.Log{nil},
},
false,
@ -59,16 +49,16 @@ func TestTransactionLogsValidate(t *testing.T) {
{
"hash mismatch log",
TransactionLogs{
Hash: ethcmn.BytesToHash([]byte("tx_hash")),
Hash: suite.hash.String(),
Logs: []*ethtypes.Log{
{
Address: addr,
Address: suite.address,
Topics: []ethcmn.Hash{ethcmn.BytesToHash([]byte("topic"))},
Data: []byte("data"),
BlockNumber: 1,
TxHash: ethcmn.BytesToHash([]byte("other_hash")),
TxIndex: 1,
BlockHash: ethcmn.BytesToHash([]byte("block_hash")),
BlockHash: suite.hash,
Index: 1,
Removed: false,
},
@ -82,18 +72,14 @@ func TestTransactionLogsValidate(t *testing.T) {
tc := tc
err := tc.txLogs.Validate()
if tc.expPass {
require.NoError(t, err, tc.name)
suite.Require().NoError(err, tc.name)
} else {
require.Error(t, err, tc.name)
suite.Require().Error(err, tc.name)
}
}
}
func TestValidateLog(t *testing.T) {
priv, err := ethsecp256k1.GenerateKey()
require.NoError(t, err)
addr := ethcrypto.PubkeyToAddress(priv.ToECDSA().PublicKey)
func (suite *GenesisTestSuite) TestValidateLog() {
testCases := []struct {
name string
log *ethtypes.Log
@ -102,13 +88,13 @@ func TestValidateLog(t *testing.T) {
{
"valid log",
&ethtypes.Log{
Address: addr,
Address: suite.address,
Topics: []ethcmn.Hash{ethcmn.BytesToHash([]byte("topic"))},
Data: []byte("data"),
BlockNumber: 1,
TxHash: ethcmn.BytesToHash([]byte("tx_hash")),
TxHash: suite.hash,
TxIndex: 1,
BlockHash: ethcmn.BytesToHash([]byte("block_hash")),
BlockHash: suite.hash,
Index: 1,
Removed: false,
},
@ -127,7 +113,7 @@ func TestValidateLog(t *testing.T) {
{
"empty block hash",
&ethtypes.Log{
Address: addr,
Address: suite.address,
BlockHash: ethcmn.Hash{},
},
false,
@ -135,8 +121,8 @@ func TestValidateLog(t *testing.T) {
{
"zero block number",
&ethtypes.Log{
Address: addr,
BlockHash: ethcmn.BytesToHash([]byte("block_hash")),
Address: suite.address,
BlockHash: suite.hash,
BlockNumber: 0,
},
false,
@ -144,8 +130,8 @@ func TestValidateLog(t *testing.T) {
{
"empty tx hash",
&ethtypes.Log{
Address: addr,
BlockHash: ethcmn.BytesToHash([]byte("block_hash")),
Address: suite.address,
BlockHash: suite.hash,
BlockNumber: 1,
TxHash: ethcmn.Hash{},
},
@ -157,9 +143,9 @@ func TestValidateLog(t *testing.T) {
tc := tc
err := ValidateLog(tc.log)
if tc.expPass {
require.NoError(t, err, tc.name)
suite.Require().NoError(err, tc.name)
} else {
require.Error(t, err, tc.name)
suite.Require().Error(err, tc.name)
}
}
}

View File

@ -9,6 +9,7 @@ import (
"sync/atomic"
"github.com/cosmos/ethermint/types"
"gopkg.in/yaml.v2"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
@ -147,37 +148,43 @@ func NewMsgEthereumTxContract(
}
func newMsgEthereumTx(
nonce uint64, to *ethcmn.Address, amount *big.Int,
nonce uint64, to *ethcmn.Address, amount *big.Int, // nolint: interfacer
gasLimit uint64, gasPrice *big.Int, payload []byte,
) MsgEthereumTx {
if len(payload) > 0 {
payload = ethcmn.CopyBytes(payload)
}
var recipient *Recipient
if to != nil {
recipient = &Recipient{Address: to.String()}
}
txData := TxData{
AccountNonce: nonce,
Recipient: to,
Recipient: recipient,
Payload: payload,
GasLimit: gasLimit,
Amount: new(big.Int),
Price: new(big.Int),
V: new(big.Int),
R: new(big.Int),
S: new(big.Int),
Amount: sdk.ZeroInt(),
Price: sdk.ZeroInt(),
V: []byte{},
R: []byte{},
S: []byte{},
}
if amount != nil {
txData.Amount.Set(amount)
txData.Amount = sdk.NewIntFromBigInt(amount)
}
if gasPrice != nil {
txData.Price.Set(gasPrice)
txData.Price = sdk.NewIntFromBigInt(gasPrice)
}
return MsgEthereumTx{Data: txData}
}
func (msg MsgEthereumTx) String() string {
return msg.Data.String()
out, _ := yaml.Marshal(msg.Data)
return string(out)
}
// Route returns the route value of an MsgEthereumTx.
@ -189,16 +196,16 @@ func (msg MsgEthereumTx) Type() string { return TypeMsgEthereumTx }
// ValidateBasic implements the sdk.Msg interface. It performs basic validation
// checks of a Transaction. If returns an error if validation fails.
func (msg MsgEthereumTx) ValidateBasic() error {
if msg.Data.Price.Cmp(big.NewInt(0)) == 0 {
if msg.Data.Price.IsZero() {
return sdkerrors.Wrapf(types.ErrInvalidValue, "gas price cannot be 0")
}
if msg.Data.Price.Sign() == -1 {
if msg.Data.Price.IsNegative() {
return sdkerrors.Wrapf(types.ErrInvalidValue, "gas price cannot be negative %s", msg.Data.Price)
}
// Amount can be 0
if msg.Data.Amount.Sign() == -1 {
if msg.Data.Amount.IsNegative() {
return sdkerrors.Wrapf(types.ErrInvalidValue, "amount cannot be negative %s", msg.Data.Amount)
}
@ -208,7 +215,12 @@ func (msg MsgEthereumTx) ValidateBasic() error {
// To returns the recipient address of the transaction. It returns nil if the
// transaction is a contract creation.
func (msg MsgEthereumTx) To() *ethcmn.Address {
return msg.Data.Recipient
if msg.Data.Recipient == nil {
return nil
}
recipient := ethcmn.HexToAddress(msg.Data.Recipient.Address)
return &recipient
}
// GetMsgs returns a single MsgEthereumTx as an sdk.Msg.
@ -242,10 +254,10 @@ func (msg MsgEthereumTx) GetSignBytes() []byte {
func (msg MsgEthereumTx) RLPSignBytes(chainID *big.Int) ethcmn.Hash {
return rlpHash([]interface{}{
msg.Data.AccountNonce,
msg.Data.Price,
msg.Data.Price.BigInt(),
msg.Data.GasLimit,
msg.Data.Recipient,
msg.Data.Amount,
msg.To(),
msg.Data.Amount.BigInt(),
msg.Data.Payload,
chainID, uint(0), uint(0),
})
@ -253,7 +265,39 @@ func (msg MsgEthereumTx) RLPSignBytes(chainID *big.Int) ethcmn.Hash {
// EncodeRLP implements the rlp.Encoder interface.
func (msg *MsgEthereumTx) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, &msg.Data)
var hash ethcmn.Hash
if len(msg.Data.Hash) > 0 {
hash = ethcmn.HexToHash(msg.Data.Hash)
}
data := struct {
AccountNonce uint64
Price *big.Int `json:"gasPrice"`
GasLimit uint64 `json:"gas"`
Recipient *ethcmn.Address `json:"to" rlp:"nil"` // nil means contract creation
Amount *big.Int `json:"value"`
Payload []byte `json:"input"`
// signature values
V *big.Int `json:"v"`
R *big.Int `json:"r"`
S *big.Int `json:"s"`
// hash is only used when marshaling to JSON
Hash *ethcmn.Hash `json:"hash" rlp:"-"`
}{
AccountNonce: msg.Data.AccountNonce,
Price: msg.Data.Price.BigInt(),
GasLimit: msg.Data.GasLimit,
Recipient: msg.To(),
Amount: msg.Data.Amount.BigInt(),
Payload: msg.Data.Payload,
V: new(big.Int).SetBytes(msg.Data.V),
R: new(big.Int).SetBytes(msg.Data.R),
S: new(big.Int).SetBytes(msg.Data.S),
Hash: &hash,
}
return rlp.Encode(w, data)
}
// DecodeRLP implements the rlp.Decoder interface.
@ -264,10 +308,50 @@ func (msg *MsgEthereumTx) DecodeRLP(s *rlp.Stream) error {
return err
}
if err := s.Decode(&msg.Data); err != nil {
var data struct {
AccountNonce uint64
Price *big.Int `json:"gasPrice"`
GasLimit uint64 `json:"gas"`
Recipient *ethcmn.Address `json:"to" rlp:"nil"` // nil means contract creation
Amount *big.Int `json:"value"`
Payload []byte `json:"input"`
// signature values
V *big.Int `json:"v"`
R *big.Int `json:"r"`
S *big.Int `json:"s"`
// hash is only used when marshaling to JSON
Hash *ethcmn.Hash `json:"hash" rlp:"-"`
}
if err := s.Decode(&data); err != nil {
return err
}
var hash string
if data.Hash != nil {
hash = data.Hash.String()
}
var recipient *Recipient
if data.Recipient != nil {
recipient = &Recipient{Address: data.Recipient.String()}
}
msg.Data = TxData{
AccountNonce: data.AccountNonce,
Price: sdk.NewIntFromBigInt(data.Price),
GasLimit: data.GasLimit,
Recipient: recipient,
Amount: sdk.NewIntFromBigInt(data.Amount),
Payload: data.Payload,
V: data.V.Bytes(),
R: data.R.Bytes(),
S: data.S.Bytes(),
Hash: hash,
}
msg.size.Store(ethcmn.StorageSize(rlp.ListSize(size)))
return nil
}
@ -302,15 +386,16 @@ func (msg *MsgEthereumTx) Sign(chainID *big.Int, priv *ecdsa.PrivateKey) error {
v.Add(v, chainIDMul)
}
msg.Data.V = v
msg.Data.R = r
msg.Data.S = s
msg.Data.V = v.Bytes()
msg.Data.R = r.Bytes()
msg.Data.S = s.Bytes()
return nil
}
// VerifySig attempts to verify a Transaction's signature for a given chainID.
// A derived address is returned upon success or an error if recovery fails.
func (msg *MsgEthereumTx) VerifySig(chainID *big.Int) (ethcmn.Address, error) {
v, r, s := msg.RawSignatureValues()
signer := ethtypes.NewEIP155Signer(chainID)
if sc := msg.from.Load(); sc != nil {
@ -328,11 +413,11 @@ func (msg *MsgEthereumTx) VerifySig(chainID *big.Int) (ethcmn.Address, error) {
}
chainIDMul := new(big.Int).Mul(chainID, big.NewInt(2))
V := new(big.Int).Sub(msg.Data.V, chainIDMul)
V := new(big.Int).Sub(v, chainIDMul)
V.Sub(V, big8)
sigHash := msg.RLPSignBytes(chainID)
sender, err := recoverEthSig(msg.Data.R, msg.Data.S, V, sigHash)
sender, err := recoverEthSig(r, s, V, sigHash)
if err != nil {
return ethcmn.Address{}, err
}
@ -348,25 +433,30 @@ func (msg MsgEthereumTx) GetGas() uint64 {
// Fee returns gasprice * gaslimit.
func (msg MsgEthereumTx) Fee() *big.Int {
return new(big.Int).Mul(msg.Data.Price, new(big.Int).SetUint64(msg.Data.GasLimit))
gasPrice := msg.Data.Price.BigInt()
gasLimit := new(big.Int).SetUint64(msg.Data.GasLimit)
return new(big.Int).Mul(gasPrice, gasLimit)
}
// ChainID returns which chain id this transaction was signed for (if at all)
func (msg *MsgEthereumTx) ChainID() *big.Int {
return deriveChainID(msg.Data.V)
v := new(big.Int).SetBytes(msg.Data.V)
return deriveChainID(v)
}
// Cost returns amount + gasprice * gaslimit.
func (msg MsgEthereumTx) Cost() *big.Int {
total := msg.Fee()
total.Add(total, msg.Data.Amount)
total.Add(total, msg.Data.Amount.BigInt())
return total
}
// RawSignatureValues returns the V, R, S signature values of the transaction.
// The return values should not be modified by the caller.
func (msg MsgEthereumTx) RawSignatureValues() (v, r, s *big.Int) {
return msg.Data.V, msg.Data.R, msg.Data.S
return new(big.Int).SetBytes(msg.Data.V),
new(big.Int).SetBytes(msg.Data.R),
new(big.Int).SetBytes(msg.Data.S)
}
// From loads the ethereum sender address from the sigcache and returns an

View File

@ -92,7 +92,8 @@ func TestMsgEthereumTx(t *testing.T) {
msg := NewMsgEthereumTx(0, &addr, nil, 100000, nil, []byte("test"))
require.NotNil(t, msg)
require.Equal(t, *msg.Data.Recipient, addr)
require.NotNil(t, msg.Data.Recipient)
require.Equal(t, msg.Data.Recipient.Address, addr.String())
require.Equal(t, msg.Route(), RouterKey)
require.Equal(t, msg.Type(), TypeMsgEthereumTx)
require.NotNil(t, msg.To())
@ -102,7 +103,7 @@ func TestMsgEthereumTx(t *testing.T) {
msg = NewMsgEthereumTxContract(0, nil, 100000, nil, []byte("test"))
require.NotNil(t, msg)
require.Nil(t, msg.Data.Recipient)
require.Empty(t, msg.Data.Recipient)
require.Nil(t, msg.To())
}

View File

@ -41,11 +41,11 @@ type Params struct {
// EnableCall toggles state transitions that use the vm.Call function
EnableCall bool `json:"enable_call" yaml:"enable_call"`
// ExtraEIPs defines the additional EIPs for the vm.Config
ExtraEIPs []int `json:"extra_eips" yaml:"extra_eips"`
ExtraEIPs []int64 `json:"extra_eips" yaml:"extra_eips"`
}
// NewParams creates a new Params instance
func NewParams(evmDenom string, enableCreate, enableCall bool, extraEIPs ...int) Params {
func NewParams(evmDenom string, enableCreate, enableCall bool, extraEIPs ...int64) Params {
return Params{
EvmDenom: evmDenom,
EnableCreate: enableCreate,
@ -60,7 +60,7 @@ func DefaultParams() Params {
EvmDenom: ethermint.AttoPhoton,
EnableCreate: true,
EnableCall: true,
ExtraEIPs: []int(nil), // TODO: define default values
ExtraEIPs: []int64(nil), // TODO: define default values
}
}
@ -107,13 +107,13 @@ func validateBool(i interface{}) error {
}
func validateEIPs(i interface{}) error {
eips, ok := i.([]int)
eips, ok := i.([]int64)
if !ok {
return fmt.Errorf("invalid EIP slice type: %T", i)
}
for _, eip := range eips {
if !vm.ValidEip(eip) {
if !vm.ValidEip(int(eip)) {
return fmt.Errorf("EIP %d is not activateable", eip)
}
}

View File

@ -34,7 +34,7 @@ func TestParamsValidate(t *testing.T) {
"invalid eip",
Params{
EvmDenom: "stake",
ExtraEIPs: []int{1},
ExtraEIPs: []int64{1},
},
true,
},
@ -57,7 +57,7 @@ func TestParamsValidatePriv(t *testing.T) {
require.Error(t, validateBool(""))
require.NoError(t, validateBool(true))
require.Error(t, validateEIPs(""))
require.NoError(t, validateEIPs([]int{1884}))
require.NoError(t, validateEIPs([]int64{1884}))
}
func TestParams_String(t *testing.T) {

View File

@ -18,7 +18,6 @@ const (
QueryBloom = "bloom"
QueryLogs = "logs"
QueryAccount = "account"
QueryExportAccount = "exportAccount"
)
// QueryResBalance is response type for balance query

View File

@ -10,7 +10,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
"github.com/cosmos/ethermint/types"
ethermint "github.com/cosmos/ethermint/types"
ethcmn "github.com/ethereum/go-ethereum/common"
ethstate "github.com/ethereum/go-ethereum/core/state"
@ -53,7 +53,7 @@ type StateObject interface {
// Account values can be accessed and modified through the object.
// Finally, call CommitTrie to write the modified storage trie into a database.
type stateObject struct {
code types.Code // contract bytecode, which gets set when code is loaded
code ethermint.Code // contract bytecode, which gets set when code is loaded
// State objects are used by the consensus core and VM which are
// unable to deal with database-level errors. Any error that occurs
// during a database read is memoized here and will eventually be returned
@ -64,7 +64,7 @@ type stateObject struct {
// DB error
dbErr error
stateDB *CommitStateDB
account *types.EthAccount
account *ethermint.EthAccount
keyToOriginStorageIndex map[ethcmn.Hash]int
keyToDirtyStorageIndex map[ethcmn.Hash]int
@ -82,7 +82,7 @@ type stateObject struct {
func newStateObject(db *CommitStateDB, accProto authexported.Account) *stateObject {
// func newStateObject(db *CommitStateDB, accProto authexported.Account, balance sdk.Int) *stateObject {
ethermintAccount, ok := accProto.(*types.EthAccount)
ethermintAccount, ok := accProto.(*ethermint.EthAccount)
if !ok {
panic(fmt.Sprintf("invalid account type for state object: %T", accProto))
}
@ -132,7 +132,7 @@ func (so *stateObject) SetState(db ethstate.Database, key, value ethcmn.Hash) {
func (so *stateObject) setState(key, value ethcmn.Hash) {
idx, ok := so.keyToDirtyStorageIndex[key]
if ok {
so.dirtyStorage[idx].Value = value
so.dirtyStorage[idx].Value = value.String()
return
}
@ -248,21 +248,24 @@ func (so *stateObject) commitState() {
for _, state := range so.dirtyStorage {
// NOTE: key is already prefixed from GetStorageByAddressKey
key := ethcmn.HexToHash(state.Key)
value := ethcmn.HexToHash(state.Value)
// delete empty values from the store
if (state.Value == ethcmn.Hash{}) {
store.Delete(state.Key.Bytes())
if ethermint.IsEmptyHash(state.Value) {
store.Delete(key.Bytes())
}
delete(so.keyToDirtyStorageIndex, state.Key)
delete(so.keyToDirtyStorageIndex, key)
// skip no-op changes, persist actual changes
idx, ok := so.keyToOriginStorageIndex[state.Key]
idx, ok := so.keyToOriginStorageIndex[key]
if !ok {
continue
}
if (state.Value == ethcmn.Hash{}) {
delete(so.keyToOriginStorageIndex, state.Key)
if ethermint.IsEmptyHash(state.Value) {
delete(so.keyToOriginStorageIndex, key)
continue
}
@ -271,7 +274,7 @@ func (so *stateObject) commitState() {
}
so.originStorage[idx].Value = state.Value
store.Set(state.Key.Bytes(), state.Value.Bytes())
store.Set(key.Bytes(), value.Bytes())
}
// clean storage as all entries are dirty
so.dirtyStorage = Storage{}
@ -348,7 +351,8 @@ func (so *stateObject) GetState(db ethstate.Database, key ethcmn.Hash) ethcmn.Ha
// if we have a dirty value for this state entry, return it
idx, dirty := so.keyToDirtyStorageIndex[prefixKey]
if dirty {
return so.dirtyStorage[idx].Value
value := ethcmn.HexToHash(so.dirtyStorage[idx].Value)
return value
}
// otherwise return the entry's original value
@ -365,23 +369,26 @@ func (so *stateObject) GetCommittedState(_ ethstate.Database, key ethcmn.Hash) e
// if we have the original value cached, return that
idx, cached := so.keyToOriginStorageIndex[prefixKey]
if cached {
return so.originStorage[idx].Value
value := ethcmn.HexToHash(so.originStorage[idx].Value)
return value
}
// otherwise load the value from the KVStore
state := NewState(prefixKey, ethcmn.Hash{})
value := ethcmn.Hash{}
ctx := so.stateDB.ctx
store := prefix.NewStore(ctx.KVStore(so.stateDB.storeKey), AddressStoragePrefix(so.Address()))
rawValue := store.Get(prefixKey.Bytes())
if len(rawValue) > 0 {
state.Value.SetBytes(rawValue)
value.SetBytes(rawValue)
state.Value = value.String()
}
so.originStorage = append(so.originStorage, state)
so.keyToOriginStorageIndex[prefixKey] = len(so.originStorage) - 1
return state.Value
return value
}
// ----------------------------------------------------------------------------

View File

@ -77,7 +77,7 @@ func (st StateTransition) newEVM(
gasLimit uint64,
gasPrice *big.Int,
config ChainConfig,
extraEIPs []int,
extraEIPs []int64,
) *vm.EVM {
// Create contexts for evm
@ -97,8 +97,13 @@ func (st StateTransition) newEVM(
GasPrice: gasPrice,
}
eips := make([]int, len(extraEIPs))
for i, eip := range extraEIPs {
eips[i] = int(eip)
}
vmConfig := vm.Config{
ExtraEips: extraEIPs,
ExtraEips: eips,
}
return vm.NewEVM(blockCtx, txCtx, csdb, config.EthereumConfig(st.ChainID), vmConfig)

View File

@ -834,7 +834,7 @@ func (csdb *CommitStateDB) ForEachStorage(addr ethcmn.Address, cb func(key, valu
if idx, dirty := so.keyToDirtyStorageIndex[key]; dirty {
// check if iteration stops
if cb(key, so.dirtyStorage[idx].Value) {
if cb(key, ethcmn.HexToHash(so.dirtyStorage[idx].Value)) {
break
}

View File

@ -646,7 +646,7 @@ func (suite *StateDBTestSuite) TestCommitStateDB_ForEachStorage() {
name string
malleate func()
callback func(key, value ethcmn.Hash) (stop bool)
expValues []ethcmn.Hash
expValues []string
}{
{
"aggregate state",
@ -659,12 +659,12 @@ func (suite *StateDBTestSuite) TestCommitStateDB_ForEachStorage() {
storage = append(storage, types.NewState(key, value))
return false
},
[]ethcmn.Hash{
ethcmn.BytesToHash([]byte("value0")),
ethcmn.BytesToHash([]byte("value1")),
ethcmn.BytesToHash([]byte("value2")),
ethcmn.BytesToHash([]byte("value3")),
ethcmn.BytesToHash([]byte("value4")),
[]string{
ethcmn.BytesToHash([]byte("value0")).String(),
ethcmn.BytesToHash([]byte("value1")).String(),
ethcmn.BytesToHash([]byte("value2")).String(),
ethcmn.BytesToHash([]byte("value3")).String(),
ethcmn.BytesToHash([]byte("value4")).String(),
},
},
{
@ -680,8 +680,8 @@ func (suite *StateDBTestSuite) TestCommitStateDB_ForEachStorage() {
}
return false
},
[]ethcmn.Hash{
ethcmn.BytesToHash([]byte("filtervalue")),
[]string{
ethcmn.BytesToHash([]byte("filtervalue")).String(),
},
},
}
@ -696,7 +696,7 @@ func (suite *StateDBTestSuite) TestCommitStateDB_ForEachStorage() {
suite.Require().NoError(err)
suite.Require().Equal(len(tc.expValues), len(storage), fmt.Sprintf("Expected values:\n%v\nStorage Values\n%v", tc.expValues, storage))
vals := make([]ethcmn.Hash, len(storage))
vals := make([]string, len(storage))
for i := range storage {
vals[i] = storage[i].Value
}

View File

@ -1,11 +1,12 @@
package types
import (
"bytes"
"fmt"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
ethermint "github.com/cosmos/ethermint/types"
ethcmn "github.com/ethereum/go-ethereum/common"
)
@ -17,15 +18,15 @@ type Storage []State
func (s Storage) Validate() error {
seenStorage := make(map[string]bool)
for i, state := range s {
if seenStorage[state.Key.String()] {
return sdkerrors.Wrapf(ErrInvalidState, "duplicate state key %d", i)
if seenStorage[state.Key] {
return sdkerrors.Wrapf(ErrInvalidState, "duplicate state key %d: %s", i, state.Key)
}
if err := state.Validate(); err != nil {
return err
}
seenStorage[state.Key.String()] = true
seenStorage[state.Key] = true
}
return nil
}
@ -34,7 +35,7 @@ func (s Storage) Validate() error {
func (s Storage) String() string {
var str string
for _, state := range s {
str += fmt.Sprintf("%s: %s\n", state.Key.String(), state.Value.String())
str += fmt.Sprintf("%s: %s\n", state.Key, state.Value)
}
return str
@ -50,13 +51,13 @@ func (s Storage) Copy() Storage {
// State represents a single Storage key value pair item.
type State struct {
Key ethcmn.Hash `json:"key"`
Value ethcmn.Hash `json:"value"`
Key string `json:"key"`
Value string `json:"value"`
}
// Validate performs a basic validation of the State fields.
func (s State) Validate() error {
if bytes.Equal(s.Key.Bytes(), ethcmn.Hash{}.Bytes()) {
if ethermint.IsEmptyHash(s.Key) {
return sdkerrors.Wrap(ErrInvalidState, "state key hash cannot be empty")
}
// NOTE: state value can be empty
@ -64,9 +65,9 @@ func (s State) Validate() error {
}
// NewState creates a new State instance
func NewState(key, value ethcmn.Hash) State {
func NewState(key, value ethcmn.Hash) State { // nolint: interfacer
return State{
Key: key,
Value: value,
Key: key.String(),
Value: value.String(),
}
}

View File

@ -3,8 +3,9 @@ package types
import (
"testing"
ethcmn "github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
ethcmn "github.com/ethereum/go-ethereum/common"
)
func TestStorageValidate(t *testing.T) {
@ -23,15 +24,15 @@ func TestStorageValidate(t *testing.T) {
{
"empty storage key bytes",
Storage{
{Key: ethcmn.Hash{}},
{Key: ethcmn.Hash{}.String()},
},
false,
},
{
"duplicated storage key",
Storage{
{Key: ethcmn.BytesToHash([]byte{1, 2, 3})},
{Key: ethcmn.BytesToHash([]byte{1, 2, 3})},
{Key: ethcmn.BytesToHash([]byte{1, 2, 3}).String()},
{Key: ethcmn.BytesToHash([]byte{1, 2, 3}).String()},
},
false,
},
@ -62,7 +63,7 @@ func TestStorageCopy(t *testing.T) {
{
"empty storage key value bytes",
Storage{
{Key: ethcmn.Hash{}, Value: ethcmn.Hash{}},
{Key: ethcmn.Hash{}.String(), Value: ethcmn.Hash{}.String()},
},
},
{

View File

@ -1,177 +1,29 @@
package types
import (
"fmt"
"math/big"
"github.com/cosmos/ethermint/utils"
ethcmn "github.com/ethereum/go-ethereum/common"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Recipient is a wrapper of the
type Recipient struct {
Address string
}
// TxData implements the Ethereum transaction data structure. It is used
// solely as intended in Ethereum abiding by the protocol.
type TxData struct {
AccountNonce uint64 `json:"nonce"`
Price *big.Int `json:"gasPrice"`
GasLimit uint64 `json:"gas"`
Recipient *ethcmn.Address `json:"to" rlp:"nil"` // nil means contract creation
Amount *big.Int `json:"value"`
Payload []byte `json:"input"`
AccountNonce uint64 `json:"nonce"`
Price sdk.Int `json:"gasPrice"`
GasLimit uint64 `json:"gas"`
Recipient *Recipient `json:"to" rlp:"nil"` // nil means contract creation
Amount sdk.Int `json:"value"`
Payload []byte `json:"input"`
// signature values
V *big.Int `json:"v"`
R *big.Int `json:"r"`
S *big.Int `json:"s"`
V []byte `json:"v"`
R []byte `json:"r"`
S []byte `json:"s"`
// hash is only used when marshaling to JSON
Hash *ethcmn.Hash `json:"hash" rlp:"-"`
Hash string `json:"hash" rlp:"-"`
}
// encodableTxData implements the Ethereum transaction data structure. It is used
// solely as intended in Ethereum abiding by the protocol.
type encodableTxData struct {
AccountNonce uint64 `json:"nonce"`
Price string `json:"gasPrice"`
GasLimit uint64 `json:"gas"`
Recipient *ethcmn.Address `json:"to" rlp:"nil"` // nil means contract creation
Amount string `json:"value"`
Payload []byte `json:"input"`
// signature values
V string `json:"v"`
R string `json:"r"`
S string `json:"s"`
// hash is only used when marshaling to JSON
Hash *ethcmn.Hash `json:"hash" rlp:"-"`
}
func (td TxData) String() string {
if td.Recipient != nil {
return fmt.Sprintf("nonce=%d price=%s gasLimit=%d recipient=%s amount=%s data=0x%x v=%s r=%s s=%s",
td.AccountNonce, td.Price, td.GasLimit, td.Recipient.Hex(), td.Amount, td.Payload, td.V, td.R, td.S)
}
return fmt.Sprintf("nonce=%d price=%s gasLimit=%d recipient=nil amount=%s data=0x%x v=%s r=%s s=%s",
td.AccountNonce, td.Price, td.GasLimit, td.Amount, td.Payload, td.V, td.R, td.S)
}
// MarshalAmino defines custom encoding scheme for TxData
func (td TxData) MarshalAmino() ([]byte, error) {
gasPrice, err := utils.MarshalBigInt(td.Price)
if err != nil {
return nil, err
}
amount, err := utils.MarshalBigInt(td.Amount)
if err != nil {
return nil, err
}
v, err := utils.MarshalBigInt(td.V)
if err != nil {
return nil, err
}
r, err := utils.MarshalBigInt(td.R)
if err != nil {
return nil, err
}
s, err := utils.MarshalBigInt(td.S)
if err != nil {
return nil, err
}
e := encodableTxData{
AccountNonce: td.AccountNonce,
Price: gasPrice,
GasLimit: td.GasLimit,
Recipient: td.Recipient,
Amount: amount,
Payload: td.Payload,
V: v,
R: r,
S: s,
Hash: td.Hash,
}
return ModuleCdc.MarshalBinaryBare(e)
}
// UnmarshalAmino defines custom decoding scheme for TxData
func (td *TxData) UnmarshalAmino(data []byte) error {
var e encodableTxData
err := ModuleCdc.UnmarshalBinaryBare(data, &e)
if err != nil {
return err
}
td.AccountNonce = e.AccountNonce
td.GasLimit = e.GasLimit
td.Recipient = e.Recipient
td.Payload = e.Payload
td.Hash = e.Hash
price, err := utils.UnmarshalBigInt(e.Price)
if err != nil {
return err
}
if td.Price != nil {
td.Price.Set(price)
} else {
td.Price = price
}
amt, err := utils.UnmarshalBigInt(e.Amount)
if err != nil {
return err
}
if td.Amount != nil {
td.Amount.Set(amt)
} else {
td.Amount = amt
}
v, err := utils.UnmarshalBigInt(e.V)
if err != nil {
return err
}
if td.V != nil {
td.V.Set(v)
} else {
td.V = v
}
r, err := utils.UnmarshalBigInt(e.R)
if err != nil {
return err
}
if td.R != nil {
td.R.Set(r)
} else {
td.R = r
}
s, err := utils.UnmarshalBigInt(e.S)
if err != nil {
return err
}
if td.S != nil {
td.S.Set(s)
} else {
td.S = s
}
return nil
}
// TODO: Implement JSON marshaling/ unmarshaling for this type
// TODO: Implement YAML marshaling/ unmarshaling for this type

View File

@ -1,56 +0,0 @@
package types
import (
"math/big"
"testing"
"github.com/stretchr/testify/require"
ethcmn "github.com/ethereum/go-ethereum/common"
)
func TestMarshalAndUnmarshalData(t *testing.T) {
addr := GenerateEthAddress()
hash := ethcmn.BigToHash(big.NewInt(2))
txData := TxData{
AccountNonce: 2,
Price: big.NewInt(3),
GasLimit: 1,
Recipient: &addr,
Amount: big.NewInt(4),
Payload: []byte("test"),
V: big.NewInt(5),
R: big.NewInt(6),
S: big.NewInt(7),
Hash: &hash,
}
bz, err := txData.MarshalAmino()
require.NoError(t, err)
require.NotNil(t, bz)
var txData2 TxData
err = txData2.UnmarshalAmino(bz)
require.NoError(t, err)
require.Equal(t, txData, txData2)
}
func TestMsgEthereumTxAmino(t *testing.T) {
addr := GenerateEthAddress()
msg := NewMsgEthereumTx(5, &addr, big.NewInt(1), 100000, big.NewInt(3), []byte("test"))
msg.Data.V = big.NewInt(1)
msg.Data.R = big.NewInt(2)
msg.Data.S = big.NewInt(3)
raw, err := ModuleCdc.MarshalBinaryBare(msg)
require.NoError(t, err)
var msg2 MsgEthereumTx
err = ModuleCdc.UnmarshalBinaryBare(raw, &msg2)
require.NoError(t, err)
require.Equal(t, msg, msg2)
}