Skip to content
This repository has been archived by the owner on Nov 30, 2021. It is now read-only.

Commit

Permalink
evm: balance and nonce invariants (#661)
Browse files Browse the repository at this point in the history
* evm: balance and nonce invariants

* nonce invariant

* changelog

* use iterator on export
  • Loading branch information
fedekunze committed Dec 15, 2020
1 parent 6001bae commit a1386ee
Show file tree
Hide file tree
Showing 12 changed files with 263 additions and 80 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,19 @@ Ref: https://keepachangelog.com/en/1.0.0/

## Unreleased

### API Breaking

* (evm) [\#661](https://github.com/cosmos/ethermint/pull/661) `Balance` field has been removed from the evm module's `GenesisState`.

### Improvements

* (evm) [\#661](https://github.com/cosmos/ethermint/pull/661) Add invariant check for account balance and account nonce.
* (deps) [\#654](https://github.com/cosmos/ethermint/pull/654) Bump go-ethereum version to [v1.9.25](https://github.com/ethereum/go-ethereum/releases/tag/v1.9.25)
* (evm) [\#627](https://github.com/cosmos/ethermint/issues/627) Add extra EIPs parameter to apply custom EVM jump tables.

### Bug Fixes

* (evm) [\#661](https://github.com/cosmos/ethermint/pull/661) Set nonce to the EVM account on genesis initialization.
* (evm) [\#621](https://github.com/cosmos/ethermint/issues/621) EVM `GenesisAccount` fields now share the same format as the auth module `Account`.
* (evm) [\#618](https://github.com/cosmos/ethermint/issues/618) Add missing EVM `Context` `GetHash` field that retrieves a the header hash from a given block height.
* (app) [\#617](https://github.com/cosmos/ethermint/issues/617) Fix genesis export functionality.
Expand Down
2 changes: 1 addition & 1 deletion app/ethermint.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ func NewEthermintApp(
app.mm.SetOrderInitGenesis(
auth.ModuleName, distr.ModuleName, staking.ModuleName, bank.ModuleName,
slashing.ModuleName, gov.ModuleName, mint.ModuleName, supply.ModuleName,
crisis.ModuleName, genutil.ModuleName, evidence.ModuleName, evm.ModuleName,
evm.ModuleName, crisis.ModuleName, genutil.ModuleName, evidence.ModuleName,
faucet.ModuleName,
)

Expand Down
28 changes: 9 additions & 19 deletions x/evm/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"

sdk "github.com/cosmos/cosmos-sdk/types"
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"

ethcmn "github.com/ethereum/go-ethereum/common"

Expand All @@ -20,7 +21,6 @@ func InitGenesis(ctx sdk.Context, k Keeper, accountKeeper types.AccountKeeper, d
for _, account := range data.Accounts {
address := ethcmn.HexToAddress(account.Address)
accAddress := sdk.AccAddress(address.Bytes())

// check that the EVM balance the matches the account balance
acc := accountKeeper.GetAccount(ctx, accAddress)
if acc == nil {
Expand All @@ -37,17 +37,11 @@ func InitGenesis(ctx sdk.Context, k Keeper, accountKeeper types.AccountKeeper, d
}

evmBalance := acc.GetCoins().AmountOf(evmDenom)
if !evmBalance.Equal(account.Balance) {
panic(
fmt.Errorf(
"balance mismatch for account %s, expected %s%s, got %s%s",
account.Address, evmBalance, evmDenom, account.Balance, evmDenom,
),
)
}

k.SetBalance(ctx, address, account.Balance.BigInt())
k.SetNonce(ctx, address, acc.GetSequence())
k.SetBalance(ctx, address, evmBalance.BigInt())
k.SetCode(ctx, address, account.Code)

for _, storage := range account.Storage {
k.SetState(ctx, address, storage.Key, storage.Value)
}
Expand Down Expand Up @@ -83,12 +77,11 @@ func InitGenesis(ctx sdk.Context, k Keeper, accountKeeper types.AccountKeeper, d
func ExportGenesis(ctx sdk.Context, k Keeper, ak types.AccountKeeper) GenesisState {
// nolint: prealloc
var ethGenAccounts []types.GenesisAccount
accounts := ak.GetAllAccounts(ctx)

for _, account := range accounts {
ak.IterateAccounts(ctx, func(account authexported.Account) bool {
ethAccount, ok := account.(*ethermint.EthAccount)
if !ok {
continue
// ignore non EthAccounts
return false
}

addr := ethAccount.EthAddress()
Expand All @@ -98,18 +91,15 @@ func ExportGenesis(ctx sdk.Context, k Keeper, ak types.AccountKeeper) GenesisSta
panic(err)
}

balanceInt := k.GetBalance(ctx, addr)
balance := sdk.NewIntFromBigInt(balanceInt)

genAccount := types.GenesisAccount{
Address: addr.String(),
Balance: balance,
Code: k.GetCode(ctx, addr),
Storage: storage,
}

ethGenAccounts = append(ethGenAccounts, genAccount)
}
return false
})

config, _ := k.GetChainConfig(ctx)

Expand Down
20 changes: 0 additions & 20 deletions x/evm/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ func (suite *EvmTestSuite) TestInitGenesis() {
Accounts: []types.GenesisAccount{
{
Address: address.String(),
Balance: sdk.OneInt(),
Storage: types.Storage{
{Key: common.BytesToHash([]byte("key")), Value: common.BytesToHash([]byte("value"))},
},
Expand Down Expand Up @@ -87,25 +86,6 @@ func (suite *EvmTestSuite) TestInitGenesis() {
Accounts: []types.GenesisAccount{
{
Address: address.String(),
Balance: sdk.OneInt(),
},
},
},
true,
},
{
"balance mismatch",
func() {
acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, address.Bytes())
suite.Require().NotNil(acc)
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
},
types.GenesisState{
Params: types.DefaultParams(),
Accounts: []types.GenesisAccount{
{
Address: address.String(),
Balance: sdk.OneInt(),
},
},
},
Expand Down
100 changes: 100 additions & 0 deletions x/evm/keeper/invariants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package keeper

import (
"fmt"

sdk "github.com/cosmos/cosmos-sdk/types"
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"

ethermint "github.com/cosmos/ethermint/types"
"github.com/cosmos/ethermint/x/evm/types"
)

const (
balanceInvariant = "balance"
nonceInvariant = "nonce"
)

// RegisterInvariants registers the evm module invariants
func RegisterInvariants(ir sdk.InvariantRegistry, k Keeper) {
ir.RegisterRoute(types.ModuleName, balanceInvariant, k.BalanceInvariant())
ir.RegisterRoute(types.ModuleName, nonceInvariant, k.NonceInvariant())
}

// BalanceInvariant checks that all auth module's EthAccounts in the application have the same balance
// as the EVM one.
func (k Keeper) BalanceInvariant() sdk.Invariant {
return func(ctx sdk.Context) (string, bool) {
var (
msg string
count int
)

k.accountKeeper.IterateAccounts(ctx, func(account authexported.Account) bool {
ethAccount, ok := account.(*ethermint.EthAccount)
if !ok {
// ignore non EthAccounts
return false
}

evmDenom := k.GetParams(ctx).EvmDenom
accountBalance := ethAccount.GetCoins().AmountOf(evmDenom)
evmBalance := k.GetBalance(ctx, ethAccount.EthAddress())

if evmBalance.Cmp(accountBalance.BigInt()) != 0 {
count++
msg += fmt.Sprintf(
"\tbalance mismatch for address %s: account balance %s, evm balance %s\n",
account.GetAddress(), accountBalance.String(), evmBalance.String(),
)
}

return false
})

broken := count != 0

return sdk.FormatInvariant(
types.ModuleName, balanceInvariant,
fmt.Sprintf("account balances mismatches found %d\n%s", count, msg),
), broken
}
}

// NonceInvariant checks that all auth module's EthAccounts in the application have the same nonce
// sequence as the EVM.
func (k Keeper) NonceInvariant() sdk.Invariant {
return func(ctx sdk.Context) (string, bool) {
var (
msg string
count int
)

k.accountKeeper.IterateAccounts(ctx, func(account authexported.Account) bool {
ethAccount, ok := account.(*ethermint.EthAccount)
if !ok {
// ignore non EthAccounts
return false
}

evmNonce := k.GetNonce(ctx, ethAccount.EthAddress())

if evmNonce != ethAccount.Sequence {
count++
msg += fmt.Sprintf(
"\nonce mismatch for address %s: account nonce %d, evm nonce %d\n",
account.GetAddress(), ethAccount.Sequence, evmNonce,
)
}

return false
})

broken := count != 0

return sdk.FormatInvariant(
types.ModuleName, nonceInvariant,
fmt.Sprintf("account nonces mismatches found %d\n%s", count, msg),
), broken
}
}
139 changes: 139 additions & 0 deletions x/evm/keeper/invariants_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package keeper_test

import (
"math/big"

sdk "github.com/cosmos/cosmos-sdk/types"
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"
)

func (suite *KeeperTestSuite) TestBalanceInvariant() {
privkey, err := ethsecp256k1.GenerateKey()
suite.Require().NoError(err)

address := ethcmn.HexToAddress(privkey.PubKey().Address().String())

testCases := []struct {
name string
malleate func()
expBroken bool
}{
{
"balance mismatch",
func() {
acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, address.Bytes())
suite.Require().NotNil(acc)
err := acc.SetCoins(sdk.NewCoins(ethermint.NewPhotonCoinInt64(1)))
suite.Require().NoError(err)
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)

suite.app.EvmKeeper.SetBalance(suite.ctx, address, big.NewInt(1000))
},
true,
},
{
"balance ok",
func() {
acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, address.Bytes())
suite.Require().NotNil(acc)
err := acc.SetCoins(sdk.NewCoins(ethermint.NewPhotonCoinInt64(1)))
suite.Require().NoError(err)
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)

suite.app.EvmKeeper.SetBalance(suite.ctx, address, big.NewInt(1))
},
false,
},
{
"invalid account type",
func() {
acc := authtypes.NewBaseAccountWithAddress(address.Bytes())
suite.app.AccountKeeper.SetAccount(suite.ctx, &acc)
},
false,
},
}

for _, tc := range testCases {
suite.Run(tc.name, func() {
suite.SetupTest() // reset values

tc.malleate()

_, broken := suite.app.EvmKeeper.BalanceInvariant()(suite.ctx)
if tc.expBroken {
suite.Require().True(broken)
} else {
suite.Require().False(broken)
}
})
}
}

func (suite *KeeperTestSuite) TestNonceInvariant() {
privkey, err := ethsecp256k1.GenerateKey()
suite.Require().NoError(err)

address := ethcmn.HexToAddress(privkey.PubKey().Address().String())

testCases := []struct {
name string
malleate func()
expBroken bool
}{
{
"nonce mismatch",
func() {
acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, address.Bytes())
suite.Require().NotNil(acc)
err := acc.SetSequence(1)
suite.Require().NoError(err)
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)

suite.app.EvmKeeper.SetNonce(suite.ctx, address, 100)
},
true,
},
{
"nonce ok",
func() {
acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, address.Bytes())
suite.Require().NotNil(acc)
err := acc.SetSequence(1)
suite.Require().NoError(err)
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)

suite.app.EvmKeeper.SetNonce(suite.ctx, address, 1)
},
false,
},
{
"invalid account type",
func() {
acc := authtypes.NewBaseAccountWithAddress(address.Bytes())
suite.app.AccountKeeper.SetAccount(suite.ctx, &acc)
},
false,
},
}

for _, tc := range testCases {
suite.Run(tc.name, func() {
suite.SetupTest() // reset values

tc.malleate()

_, broken := suite.app.EvmKeeper.NonceInvariant()(suite.ctx)
if tc.expBroken {
suite.Require().True(broken)
} else {
suite.Require().False(broken)
}
})
}
}
3 changes: 3 additions & 0 deletions x/evm/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ type Keeper struct {
// - storing block height -> bloom filter map. Needed for the Web3 API.
// - storing block hash -> block height map. Needed for the Web3 API.
storeKey sdk.StoreKey
// Account Keeper for fetching accounts
accountKeeper types.AccountKeeper
// Ethermint concrete implementation on the EVM StateDB interface
CommitStateDB *types.CommitStateDB
// Transaction counter in a block. Used on StateSB's Prepare function.
Expand All @@ -52,6 +54,7 @@ func NewKeeper(
return Keeper{
cdc: cdc,
storeKey: storeKey,
accountKeeper: ak,
CommitStateDB: types.NewCommitStateDB(sdk.Context{}, storeKey, paramSpace, ak),
TxCount: 0,
Bloom: big.NewInt(0),
Expand Down
Loading

0 comments on commit a1386ee

Please sign in to comment.