Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

evm: balance and nonce invariants #502

Merged
merged 2 commits into from
Dec 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 8 additions & 19 deletions x/evm/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package evm

import (
"fmt"
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"

sdk "github.com/cosmos/cosmos-sdk/types"

Expand Down Expand Up @@ -37,16 +38,8 @@ 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 +76,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 +90,15 @@ func ExportGenesis(ctx sdk.Context, k Keeper, ak types.AccountKeeper) GenesisSta
panic(err)
}

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

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.OneDec(),
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.OneDec(),
},
},
},
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.OneDec(),
},
},
},
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/okex/okexchain/app/types"
"github.com/okex/okexchain/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/okex/okexchain/app/crypto/ethsecp256k1"
ethermint "github.com/okex/okexchain/app/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
4 changes: 0 additions & 4 deletions x/evm/keeper/querier.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,12 +198,8 @@ func queryExportAccount(ctx sdk.Context, path []string, keeper Keeper) ([]byte,
return nil, err
}

balanceInt := keeper.GetBalance(ctx, addr)
balance := sdk.NewDecFromBigIntWithPrec(balanceInt, sdk.Precision)

res := types.GenesisAccount{
Address: hexAddress,
Balance: balance,
Code: keeper.GetCode(ctx, addr),
Storage: storage,
}
Expand Down
4 changes: 3 additions & 1 deletion x/evm/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ func (AppModule) Name() string {
}

// RegisterInvariants interface for registering invariants
func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) {}
func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) {
keeper.RegisterInvariants(ir, am.keeper)
}

// Route specifies path for transactions
func (am AppModule) Route() string {
Expand Down
1 change: 1 addition & 0 deletions x/evm/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
type AccountKeeper interface {
NewAccountWithAddress(ctx sdk.Context, addr sdk.AccAddress) authexported.Account
GetAllAccounts(ctx sdk.Context) (accounts []authexported.Account)
IterateAccounts(ctx sdk.Context, cb func(account authexported.Account) bool)
GetAccount(ctx sdk.Context, addr sdk.AccAddress) authexported.Account
SetAccount(ctx sdk.Context, account authexported.Account)
RemoveAccount(ctx sdk.Context, account authexported.Account)
Expand Down
Loading