evm: remove CommitStateDB, journal and stateObject (#84)

* evm: remove CommitStateDB and stateObject

* imported build fixes

* lint

* rm set nonce

* update account response

* changelog
This commit is contained in:
Federico Kunze 2021-06-08 13:10:29 -04:00 committed by GitHub
parent 6eadc8fdf8
commit 0c6e44d3d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 260 additions and 4250 deletions

View File

@ -39,6 +39,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
### State Machine Breaking
* (evm) [tharsis#84](https://github.com/tharsis/ethermint/pull/84) Remove `journal`, `CommitStateDB` and `stateObjects`.
* (rpc, evm) [tharsis#81](https://github.com/tharsis/ethermint/pull/81) Remove tx `Receipt` from store and replace it with fields obtained from the Tendermint RPC client.
* (evm) [tharsis#72](https://github.com/tharsis/ethermint/issues/72) Update `AccessList` to use `TransientStore` instead of map.
* (evm) [tharsis#68](https://github.com/tharsis/ethermint/issues/68) Replace block hash storage map to use staking `HistoricalInfo`.

View File

@ -385,7 +385,7 @@ func NewEthermintApp(
params.NewAppModule(app.ParamsKeeper),
transferModule,
// Ethermint app modules
evm.NewAppModule(app.EvmKeeper, app.AccountKeeper, app.BankKeeper),
evm.NewAppModule(app.EvmKeeper, app.AccountKeeper),
)
// During begin block slashing happens after distr.BeginBlocker so that

View File

@ -427,7 +427,7 @@ QueryAccountResponse is the response type for the Query/Account RPC method.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `balance` | [string](#string) | | balance is the balance of the EVM denomination. |
| `code_hash` | [bytes](#bytes) | | code_hash is the code bytes from the EOA. |
| `code_hash` | [string](#string) | | code hash is the hex-formatted code bytes from the EOA. |
| `nonce` | [uint64](#uint64) | | nonce is the account's sequence number. |

2
go.mod
View File

@ -45,7 +45,7 @@ require (
github.com/tyler-smith/go-bip39 v1.1.0
github.com/xlab/closer v0.0.0-20190328110542-03326addb7c2
github.com/xlab/suplog v1.3.0
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect
google.golang.org/genproto v0.0.0-20210607140030-00d4fb20b1ae
google.golang.org/grpc v1.38.0
gopkg.in/yaml.v2 v2.4.0

View File

@ -75,8 +75,8 @@ message QueryAccountRequest {
message QueryAccountResponse {
// balance is the balance of the EVM denomination.
string balance = 1;
// code_hash is the code bytes from the EOA.
bytes code_hash = 2;
// code hash is the hex-formatted code bytes from the EOA.
string code_hash = 2;
// nonce is the account's sequence number.
uint64 nonce = 3;
}

View File

@ -107,7 +107,7 @@ func createAndTestGenesis(t *testing.T, cms sdk.CommitMultiStore, ak authkeeper.
genBlock := ethcore.DefaultGenesisBlock()
ms := cms.CacheMultiStore()
ctx := sdk.NewContext(ms, tmproto.Header{}, false, logger)
evmKeeper.CommitStateDB.WithContext(ctx)
evmKeeper.WithContext(ctx)
// Set the default Ethermint parameters to the parameter keeper store
evmKeeper.SetParams(ctx, evmtypes.DefaultParams())
@ -126,25 +126,19 @@ func createAndTestGenesis(t *testing.T, cms sdk.CommitMultiStore, ak authkeeper.
addr := ethcmn.HexToAddress(addrStr)
acc := genBlock.Alloc[addr]
evmKeeper.CommitStateDB.AddBalance(addr, acc.Balance)
evmKeeper.CommitStateDB.SetCode(addr, acc.Code)
evmKeeper.CommitStateDB.SetNonce(addr, acc.Nonce)
evmKeeper.AddBalance(addr, acc.Balance)
evmKeeper.SetCode(addr, acc.Code)
evmKeeper.SetNonce(addr, acc.Nonce)
for key, value := range acc.Storage {
evmKeeper.CommitStateDB.SetState(addr, key, value)
evmKeeper.SetState(addr, key, value)
}
}
// get balance of one of the genesis account having 400 ETH
b := evmKeeper.CommitStateDB.GetBalance(genInvestor)
b := evmKeeper.GetBalance(genInvestor)
require.Equal(t, "200000000000000000000", b.String())
// commit the stateDB with 'false' to delete empty objects
//
// NOTE: Commit does not yet return the intra merkle root (version)
_, err := evmKeeper.CommitStateDB.Commit(false)
require.NoError(t, err)
// persist multi-store cache state
ms.Write()
@ -267,15 +261,13 @@ func TestImportBlocks(t *testing.T) {
ms := cms.CacheMultiStore()
ctx := sdk.NewContext(ms, tmproto.Header{}, false, logger)
ctx = ctx.WithBlockHeight(int64(block.NumberU64()))
evmKeeper.CommitStateDB.WithContext(ctx)
evmKeeper.WithContext(ctx)
if chainConfig.DAOForkSupport && chainConfig.DAOForkBlock != nil && chainConfig.DAOForkBlock.Cmp(block.Number()) == 0 {
applyDAOHardFork(evmKeeper)
}
for i, tx := range block.Transactions() {
evmKeeper.CommitStateDB.Prepare(tx.Hash(), block.Hash(), i)
// evmKeeper.CommitStateDB.Set(block.Hash())
for _, tx := range block.Transactions() {
receipt, gas, err := applyTransaction(
chainConfig, chainContext, nil, gp, evmKeeper, header, tx, usedGas, vmConfig,
@ -287,10 +279,6 @@ func TestImportBlocks(t *testing.T) {
// apply mining rewards
accumulateRewards(chainConfig, evmKeeper, header, block.Uncles())
// commit stateDB
_, err := evmKeeper.CommitStateDB.Commit(chainConfig.IsEIP158(block.Number()))
require.NoError(t, err, "failed to commit StateDB")
// simulate BaseApp EndBlocker commitment
ms.Write()
cms.Commit()
@ -325,12 +313,12 @@ func accumulateRewards(
r.Sub(r, header.Number)
r.Mul(r, blockReward)
r.Div(r, rewardBig8)
evmKeeper.CommitStateDB.AddBalance(uncle.Coinbase, r)
evmKeeper.AddBalance(uncle.Coinbase, r)
r.Div(blockReward, rewardBig32)
reward.Add(reward, r)
}
evmKeeper.CommitStateDB.AddBalance(header.Coinbase, reward)
evmKeeper.AddBalance(header.Coinbase, reward)
}
// ApplyDAOHardFork modifies the state database according to the DAO hard-fork
@ -341,14 +329,13 @@ func accumulateRewards(
// Ref: https://github.com/ethereum/go-ethereum/blob/52f2461774bcb8cdd310f86b4bc501df5b783852/consensus/misc/dao.go#L74
func applyDAOHardFork(evmKeeper *evmkeeper.Keeper) {
// Retrieve the contract to refund balances into
if !evmKeeper.CommitStateDB.Exist(ethparams.DAORefundContract) {
evmKeeper.CommitStateDB.CreateAccount(ethparams.DAORefundContract)
if !evmKeeper.Exist(ethparams.DAORefundContract) {
evmKeeper.CreateAccount(ethparams.DAORefundContract)
}
// Move every DAO account and extra-balance account funds into the refund contract
for _, addr := range ethparams.DAODrainList() {
evmKeeper.CommitStateDB.AddBalance(ethparams.DAORefundContract, evmKeeper.CommitStateDB.GetBalance(addr))
evmKeeper.CommitStateDB.SetBalance(addr, new(big.Int))
evmKeeper.AddBalance(ethparams.DAORefundContract, evmKeeper.GetBalance(addr))
}
}
@ -374,7 +361,7 @@ func applyTransaction(
// Create a new environment which holds all relevant information
// about the transaction and calling mechanisms.
vmenv := ethvm.NewEVM(blockCtx, txCtx, evmKeeper.CommitStateDB, config, cfg)
vmenv := ethvm.NewEVM(blockCtx, txCtx, evmKeeper, config, cfg)
// Apply the transaction to the current state (included in the env)
execResult, err := ethcore.ApplyMessage(vmenv, msg, gp)
@ -383,19 +370,11 @@ func applyTransaction(
return &ethtypes.Receipt{}, 0, nil
}
// Update the state with pending changes
var intRoot ethcmn.Hash
if config.IsByzantium(header.Number) {
err = evmKeeper.CommitStateDB.Finalise(true)
} else {
intRoot, err = evmKeeper.CommitStateDB.IntermediateRoot(config.IsEIP158(header.Number))
}
if err != nil {
return nil, execResult.UsedGas, err
}
root := intRoot.Bytes()
root := ethcmn.Hash{}.Bytes()
*usedGas += execResult.UsedGas
// Create a new receipt for the transaction, storing the intermediate root and gas used by the tx
@ -410,11 +389,11 @@ func applyTransaction(
}
// Set the receipt logs and create a bloom for filtering
receipt.Logs, err = evmKeeper.CommitStateDB.GetLogs(tx.Hash())
receipt.Logs = evmKeeper.GetTxLogs(tx.Hash())
receipt.Bloom = ethtypes.CreateBloom(ethtypes.Receipts{receipt})
receipt.BlockHash = evmKeeper.CommitStateDB.BlockHash()
receipt.BlockHash = header.Hash()
receipt.BlockNumber = header.Number
receipt.TransactionIndex = uint(evmKeeper.CommitStateDB.TxIndex())
receipt.TransactionIndex = uint(evmKeeper.GetTxIndexTransient())
return receipt, execResult.UsedGas, err
}

View File

@ -18,16 +18,12 @@ func InitGenesis(
ctx sdk.Context,
k *keeper.Keeper,
accountKeeper types.AccountKeeper, // nolint: interfacer
bankKeeper types.BankKeeper,
data types.GenesisState,
) []abci.ValidatorUpdate {
k.WithContext(ctx)
k.WithChainID(ctx)
k.CommitStateDB.WithContext(ctx)
k.SetParams(ctx, data.Params)
evmDenom := data.Params.EvmDenom
for _, account := range data.Accounts {
address := ethcmn.HexToAddress(account.Address)
@ -47,46 +43,25 @@ func InitGenesis(
)
}
evmBalance := bankKeeper.GetBalance(ctx, accAddress, evmDenom)
k.CommitStateDB.SetBalance(address, evmBalance.Amount.BigInt())
k.CommitStateDB.SetNonce(address, acc.GetSequence())
k.CommitStateDB.SetCode(address, ethcmn.Hex2Bytes(account.Code))
k.SetCode(address, ethcmn.Hex2Bytes(account.Code))
for _, storage := range account.Storage {
k.SetState(address, ethcmn.HexToHash(storage.Key), ethcmn.HexToHash(storage.Value))
}
}
var err error
for _, txLog := range data.TxsLogs {
err = k.CommitStateDB.SetLogs(ethcmn.HexToHash(txLog.Hash), txLog.EthLogs())
if err != nil {
panic(err)
}
k.SetLogs(ethcmn.HexToHash(txLog.Hash), txLog.EthLogs())
}
k.SetChainConfig(ctx, data.ChainConfig)
// set state objects and code to store
_, err = k.CommitStateDB.Commit(false)
if err != nil {
panic(err)
}
// set storage to store
// NOTE: don't delete empty object to prevent import-export simulation failure
err = k.CommitStateDB.Finalise(false)
if err != nil {
panic(err)
}
return []abci.ValidatorUpdate{}
}
// ExportGenesis exports genesis state of the EVM module
func ExportGenesis(ctx sdk.Context, k *keeper.Keeper, ak types.AccountKeeper) *types.GenesisState {
k.WithContext(ctx)
k.CommitStateDB.WithContext(ctx)
// nolint: prealloc
var ethGenAccounts []types.GenesisAccount
@ -106,7 +81,7 @@ func ExportGenesis(ctx sdk.Context, k *keeper.Keeper, ak types.AccountKeeper) *t
genAccount := types.GenesisAccount{
Address: addr.String(),
Code: ethcmn.Bytes2Hex(k.CommitStateDB.GetCode(addr)),
Code: ethcmn.Bytes2Hex(k.GetCode(addr)),
Storage: storage,
}

View File

@ -91,13 +91,13 @@ func (suite *EvmTestSuite) TestInitGenesis() {
if tc.expPanic {
suite.Require().Panics(
func() {
_ = evm.InitGenesis(suite.ctx, suite.app.EvmKeeper, suite.app.AccountKeeper, suite.app.BankKeeper, *tc.genState)
_ = evm.InitGenesis(suite.ctx, suite.app.EvmKeeper, suite.app.AccountKeeper, *tc.genState)
},
)
} else {
suite.Require().NotPanics(
func() {
_ = evm.InitGenesis(suite.ctx, suite.app.EvmKeeper, suite.app.AccountKeeper, suite.app.BankKeeper, *tc.genState)
_ = evm.InitGenesis(suite.ctx, suite.app.EvmKeeper, suite.app.AccountKeeper, *tc.genState)
},
)
}

View File

@ -8,12 +8,11 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/ethermint/x/evm/keeper"
"github.com/cosmos/ethermint/x/evm/types"
)
// NewHandler returns a handler for Ethermint type messages.
func NewHandler(k *keeper.Keeper) sdk.Handler {
func NewHandler(server types.MsgServer) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) (result *sdk.Result, err error) {
defer Recover(&err)
@ -22,7 +21,7 @@ func NewHandler(k *keeper.Keeper) sdk.Handler {
switch msg := msg.(type) {
case *types.MsgEthereumTx:
// execute state transition
res, err := k.EthereumTx(sdk.WrapSDKContext(ctx), msg)
res, err := server.EthereumTx(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
default:

View File

@ -49,7 +49,7 @@ func (suite *EvmTestSuite) SetupTest() {
suite.app = app.Setup(checkTx)
suite.ctx = suite.app.BaseApp.NewContext(checkTx, tmproto.Header{Height: 1, ChainID: "ethermint-1", Time: time.Now().UTC()})
suite.app.EvmKeeper.CommitStateDB.WithContext(suite.ctx)
suite.app.EvmKeeper.WithContext(suite.ctx)
suite.handler = evm.NewHandler(suite.app.EvmKeeper)
suite.codec = suite.app.AppCodec()
suite.chainID = suite.app.EvmKeeper.ChainID()
@ -84,7 +84,6 @@ func (suite *EvmTestSuite) TestHandleMsgEthereumTx() {
{
"passed",
func() {
suite.app.EvmKeeper.CommitStateDB.SetBalance(suite.from, big.NewInt(100))
to := ethcmn.BytesToAddress(suite.to)
tx = types.NewMsgEthereumTx(suite.chainID, 0, &to, big.NewInt(100), 0, big.NewInt(10000), nil, nil)
tx.From = suite.from.String()
@ -189,11 +188,10 @@ func (suite *EvmTestSuite) TestHandlerLogs() {
suite.Require().Equal(len(txResponse.Logs[0].Topics), 2)
hash := []byte{1}
err = suite.app.EvmKeeper.CommitStateDB.SetLogs(ethcmn.BytesToHash(hash), types.LogsToEthereum(txResponse.Logs))
suite.app.EvmKeeper.SetLogs(ethcmn.BytesToHash(hash), types.LogsToEthereum(txResponse.Logs))
suite.Require().NoError(err)
logs, err := suite.app.EvmKeeper.CommitStateDB.GetLogs(ethcmn.BytesToHash(hash))
suite.Require().NoError(err, "failed to get logs")
logs := suite.app.EvmKeeper.GetTxLogs(ethcmn.BytesToHash(hash))
suite.Require().Equal(logs, txResponse.Logs)
}
@ -310,8 +308,6 @@ func (suite *EvmTestSuite) TestSendTransaction() {
gasLimit := uint64(21000)
gasPrice := big.NewInt(0x55ae82600)
suite.app.EvmKeeper.CommitStateDB.SetBalance(suite.from, big.NewInt(100))
// send simple value transfer with gasLimit=21000
tx := types.NewMsgEthereumTx(suite.chainID, 1, &ethcmn.Address{0x1}, big.NewInt(1), gasLimit, gasPrice, nil, nil)
tx.From = suite.from.String()
@ -390,12 +386,12 @@ func (suite *EvmTestSuite) TestOutOfGasWhenDeployContract() {
err := tx.Sign(suite.ethSigner, suite.signer)
suite.Require().NoError(err)
snapshotCommitStateDBJson, err := json.Marshal(suite.app.EvmKeeper.CommitStateDB)
snapshotCommitStateDBJson, err := json.Marshal(suite.app.EvmKeeper)
suite.Require().Nil(err)
defer func() {
if r := recover(); r != nil {
currentCommitStateDBJson, err := json.Marshal(suite.app.EvmKeeper.CommitStateDB)
currentCommitStateDBJson, err := json.Marshal(suite.app.EvmKeeper)
suite.Require().Nil(err)
suite.Require().Equal(snapshotCommitStateDBJson, currentCommitStateDBJson)
} else {
@ -419,13 +415,13 @@ func (suite *EvmTestSuite) TestErrorWhenDeployContract() {
err := tx.Sign(suite.ethSigner, suite.signer)
suite.Require().NoError(err)
snapshotCommitStateDBJson, err := json.Marshal(suite.app.EvmKeeper.CommitStateDB)
snapshotCommitStateDBJson, err := json.Marshal(suite.app.EvmKeeper)
suite.Require().Nil(err)
_, sdkErr := suite.handler(suite.ctx, tx)
suite.Require().NotNil(sdkErr)
currentCommitStateDBJson, err := json.Marshal(suite.app.EvmKeeper.CommitStateDB)
currentCommitStateDBJson, err := json.Marshal(suite.app.EvmKeeper)
suite.Require().Nil(err)
suite.Require().Equal(snapshotCommitStateDBJson, currentCommitStateDBJson)
}

View File

@ -27,29 +27,13 @@ func (k *Keeper) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) {
// deleting the empty ones. It also sets the bloom filers for the request block to
// 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 {
func (k *Keeper) EndBlock(ctx sdk.Context, req abci.RequestEndBlock) []abci.ValidatorUpdate {
defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyEndBlocker)
// Gas costs are handled within msg handler so costs should be ignored
ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter())
k.CommitStateDB.WithContext(ctx)
infCtx := ctx.WithGasMeter(sdk.NewInfiniteGasMeter())
k.WithContext(ctx)
// Update account balances before committing other parts of state
k.CommitStateDB.UpdateAccounts()
root, err := k.CommitStateDB.Commit(true)
// Commit state objects to KV store
if err != nil {
k.Logger(ctx).Error("failed to commit state objects", "error", err, "height", ctx.BlockHeight())
panic(err)
}
// reset all cache after account data has been committed, that make sure node state consistent
if err = k.CommitStateDB.Reset(root); err != nil {
panic(err)
}
// get the block bloom bytes from the transient store and set it to the persistent storage
bloomBig, found := k.GetBlockBloomTransient()
if !found {
@ -57,7 +41,8 @@ func (k Keeper) EndBlock(ctx sdk.Context, req abci.RequestEndBlock) []abci.Valid
}
bloom := ethtypes.BytesToBloom(bloomBig.Bytes())
k.SetBlockBloom(ctx, req.Height, bloom)
k.SetBlockBloom(infCtx, req.Height, bloom)
k.WithContext(ctx)
return []abci.ValidatorUpdate{}
}

View File

@ -2,13 +2,10 @@ package keeper
import (
"context"
"math/big"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
tmtypes "github.com/tendermint/tendermint/types"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/query"
@ -33,19 +30,15 @@ func (k Keeper) Account(c context.Context, req *types.QueryAccountRequest) (*typ
)
}
ctx := sdk.UnwrapSDKContext(c)
k.CommitStateDB.WithContext(ctx)
addr := ethcmn.HexToAddress(req.Address)
so := k.CommitStateDB.GetOrNewStateObject(ethcmn.HexToAddress(req.Address))
balance, err := ethermint.MarshalBigInt(so.Balance())
if err != nil {
return nil, err
}
ctx := sdk.UnwrapSDKContext(c)
k.WithContext(ctx)
return &types.QueryAccountResponse{
Balance: balance,
CodeHash: so.CodeHash(),
Nonce: so.Nonce(),
Balance: k.GetBalance(addr).String(),
CodeHash: k.GetCodeHash(addr).Hex(),
Nonce: k.GetNonce(addr),
}, nil
}
@ -61,7 +54,7 @@ func (k Keeper) CosmosAccount(c context.Context, req *types.QueryCosmosAccountRe
}
ctx := sdk.UnwrapSDKContext(c)
k.CommitStateDB.WithContext(ctx)
k.WithContext(ctx)
ethAddr := ethcmn.HexToAddress(req.Address)
cosmosAddr := sdk.AccAddress(ethAddr.Bytes())
@ -93,9 +86,9 @@ func (k Keeper) Balance(c context.Context, req *types.QueryBalanceRequest) (*typ
}
ctx := sdk.UnwrapSDKContext(c)
k.CommitStateDB.WithContext(ctx)
k.WithContext(ctx)
balanceInt := k.CommitStateDB.GetBalance(ethcmn.HexToAddress(req.Address))
balanceInt := k.GetBalance(ethcmn.HexToAddress(req.Address))
balance, err := ethermint.MarshalBigInt(balanceInt)
if err != nil {
return nil, status.Error(
@ -130,12 +123,12 @@ func (k Keeper) Storage(c context.Context, req *types.QueryStorageRequest) (*typ
}
ctx := sdk.UnwrapSDKContext(c)
k.CommitStateDB.WithContext(ctx)
k.WithContext(ctx)
address := ethcmn.HexToAddress(req.Address)
key := ethcmn.HexToHash(req.Key)
state := k.CommitStateDB.GetState(address, key)
state := k.GetState(address, key)
stateHex := state.Hex()
if ethermint.IsEmptyHash(stateHex) {
@ -163,10 +156,10 @@ func (k Keeper) Code(c context.Context, req *types.QueryCodeRequest) (*types.Que
}
ctx := sdk.UnwrapSDKContext(c)
k.CommitStateDB.WithContext(ctx)
k.WithContext(ctx)
address := ethcmn.HexToAddress(req.Address)
code := k.CommitStateDB.GetCode(address)
code := k.GetCode(address)
return &types.QueryCodeResponse{
Code: code,
@ -187,19 +180,13 @@ func (k Keeper) TxLogs(c context.Context, req *types.QueryTxLogsRequest) (*types
}
ctx := sdk.UnwrapSDKContext(c)
k.CommitStateDB.WithContext(ctx)
k.WithContext(ctx)
hash := ethcmn.HexToHash(req.Hash)
logs, err := k.CommitStateDB.GetLogs(hash)
if err != nil {
return nil, status.Error(
codes.Internal,
err.Error(),
)
}
logs := k.GetTxLogs(hash)
return &types.QueryTxLogsResponse{
Logs: types.NewTransactionLogsFromEth(hash, logs).Logs,
Logs: types.NewLogsFromEth(logs),
}, nil
}
@ -277,59 +264,61 @@ func (k Keeper) StaticCall(c context.Context, req *types.QueryStaticCallRequest)
return nil, status.Error(codes.InvalidArgument, "empty request")
}
ctx := sdk.UnwrapSDKContext(c)
k.CommitStateDB.WithContext(ctx)
// ctx := sdk.UnwrapSDKContext(c)
// k.WithContext(ctx)
// parse the chainID from a string to a base-10 integer
chainIDEpoch, err := ethermint.ParseChainID(ctx.ChainID())
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
// // parse the chainID from a string to a base-10 integer
// chainIDEpoch, err := ethermint.ParseChainID(ctx.ChainID())
// if err != nil {
// return nil, status.Error(codes.Internal, err.Error())
// }
txHash := tmtypes.Tx(ctx.TxBytes()).Hash()
ethHash := ethcmn.BytesToHash(txHash)
// 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
}
// var recipient *ethcmn.Address
// if len(req.Address) > 0 {
// addr := ethcmn.HexToAddress(req.Address)
// recipient = &addr
// }
so := k.CommitStateDB.GetOrNewStateObject(*recipient)
sender := ethcmn.HexToAddress("0xaDd00275E3d9d213654Ce5223f0FADE8b106b707")
// so := k.GetOrNewStateObject(*recipient)
// sender := ethcmn.HexToAddress("0xaDd00275E3d9d213654Ce5223f0FADE8b106b707")
msg := types.NewMsgEthereumTx(
chainIDEpoch, so.Nonce(), recipient, big.NewInt(0), 100000000, big.NewInt(0), req.Input, nil,
)
msg.From = sender.Hex()
// msg := types.NewMsgEthereumTx(
// chainIDEpoch, so.Nonce(), recipient, big.NewInt(0), 100000000, big.NewInt(0), req.Input, nil,
// )
// msg.From = sender.Hex()
if err := msg.ValidateBasic(); err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
// if err := msg.ValidateBasic(); err != nil {
// return nil, status.Error(codes.Internal, err.Error())
// }
ethMsg, err := msg.AsMessage()
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
// ethMsg, err := msg.AsMessage()
// if err != nil {
// return nil, status.Error(codes.Internal, err.Error())
// }
st := &types.StateTransition{
Message: ethMsg,
Csdb: k.CommitStateDB.WithContext(ctx),
ChainID: chainIDEpoch,
TxHash: &ethHash,
Simulate: ctx.IsCheckTx(),
Debug: false,
}
// st := &types.StateTransition{
// Message: ethMsg,
// Csdb: k.WithContext(ctx),
// ChainID: chainIDEpoch,
// TxHash: &ethHash,
// Simulate: ctx.IsCheckTx(),
// Debug: false,
// }
config, found := k.GetChainConfig(ctx)
if !found {
return nil, status.Error(codes.Internal, types.ErrChainConfigNotFound.Error())
}
// config, found := k.GetChainConfig(ctx)
// if !found {
// return nil, status.Error(codes.Internal, types.ErrChainConfigNotFound.Error())
// }
ret, err := st.StaticCall(ctx, config)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
// ret, err := st.StaticCall(ctx, config)
// if err != nil {
// return nil, status.Error(codes.Internal, err.Error())
// }
return &types.QueryStaticCallResponse{Data: ret}, nil
// return &types.QueryStaticCallResponse{Data: ret}, nil
return nil, nil
}

View File

@ -5,6 +5,7 @@ import (
"google.golang.org/grpc/metadata"
"github.com/ethereum/go-ethereum/common"
ethcmn "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
@ -33,7 +34,7 @@ func (suite *KeeperTestSuite) TestQueryAccount() {
suite.app.BankKeeper.SetBalance(suite.ctx, suite.address.Bytes(), ethermint.NewPhotonCoinInt64(0))
expAccount = &types.QueryAccountResponse{
Balance: "0",
CodeHash: ethcrypto.Keccak256(nil),
CodeHash: common.BytesToHash(ethcrypto.Keccak256(nil)).Hex(),
Nonce: 0,
}
req = &types.QueryAccountRequest{
@ -48,7 +49,7 @@ func (suite *KeeperTestSuite) TestQueryAccount() {
suite.app.BankKeeper.SetBalance(suite.ctx, suite.address.Bytes(), ethermint.NewPhotonCoinInt64(100))
expAccount = &types.QueryAccountResponse{
Balance: "100",
CodeHash: ethcrypto.Keccak256(nil),
CodeHash: common.BytesToHash(ethcrypto.Keccak256(nil)).Hex(),
Nonce: 0,
}
req = &types.QueryAccountRequest{
@ -245,7 +246,7 @@ func (suite *KeeperTestSuite) TestQueryStorage() {
key := ethcmn.BytesToHash([]byte("key"))
value := ethcmn.BytesToHash([]byte("value"))
expValue = value.String()
suite.app.EvmKeeper.CommitStateDB.SetState(suite.address, key, value)
suite.app.EvmKeeper.SetState(suite.address, key, value)
req = &types.QueryStorageRequest{
Address: suite.address.String(),
Key: key.String(),
@ -300,7 +301,7 @@ func (suite *KeeperTestSuite) TestQueryCode() {
"success",
func() {
expCode = []byte("code")
suite.app.EvmKeeper.CommitStateDB.SetCode(suite.address, expCode)
suite.app.EvmKeeper.SetCode(suite.address, expCode)
req = &types.QueryCodeRequest{
Address: suite.address.String(),
@ -377,7 +378,7 @@ func (suite *KeeperTestSuite) TestQueryTxLogs() {
},
}
suite.app.EvmKeeper.CommitStateDB.SetLogs(hash, types.LogsToEthereum(expLogs))
suite.app.EvmKeeper.SetLogs(hash, types.LogsToEthereum(expLogs))
req = &types.QueryTxLogsRequest{
Hash: hash.String(),
@ -486,8 +487,8 @@ func (suite *KeeperTestSuite) TestQueryBlockLogs() {
},
}
suite.app.EvmKeeper.CommitStateDB.SetLogs(ethcmn.BytesToHash([]byte("tx_hash_0")), types.LogsToEthereum(expLogs[0].Logs))
suite.app.EvmKeeper.CommitStateDB.SetLogs(ethcmn.BytesToHash([]byte("tx_hash_1")), types.LogsToEthereum(expLogs[1].Logs))
suite.app.EvmKeeper.SetLogs(ethcmn.BytesToHash([]byte("tx_hash_0")), types.LogsToEthereum(expLogs[0].Logs))
suite.app.EvmKeeper.SetLogs(ethcmn.BytesToHash([]byte("tx_hash_1")), types.LogsToEthereum(expLogs[1].Logs))
req = &types.QueryBlockLogsRequest{
Hash: hash.String(),

View File

@ -1,105 +0,0 @@
package keeper
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
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.CommitStateDB.WithContext(ctx)
k.accountKeeper.IterateAccounts(ctx, func(account authtypes.AccountI) bool {
ethAccount, ok := account.(*ethermint.EthAccount)
if !ok {
// ignore non EthAccounts
return false
}
evmDenom := k.GetParams(ctx).EvmDenom
accountBalance := k.bankKeeper.GetBalance(ctx, ethAccount.GetAddress(), evmDenom)
evmBalance := k.CommitStateDB.GetBalance(ethAccount.EthAddress())
if evmBalance.Cmp(accountBalance.Amount.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.CommitStateDB.WithContext(ctx)
k.accountKeeper.IterateAccounts(ctx, func(account authtypes.AccountI) bool {
ethAccount, ok := account.(*ethermint.EthAccount)
if !ok {
// ignore non EthAccounts
return false
}
evmNonce := k.CommitStateDB.GetNonce(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
}
}

View File

@ -1,138 +0,0 @@
package keeper_test
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"
)
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)
suite.app.BankKeeper.SetBalance(suite.ctx, acc.GetAddress(), ethermint.NewPhotonCoinInt64(1))
suite.Require().NoError(err)
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
suite.app.EvmKeeper.CommitStateDB.SetBalance(address, big.NewInt(1000))
},
true,
},
{
"balance ok",
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.Require().NoError(err)
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
suite.app.EvmKeeper.CommitStateDB.SetBalance(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
suite.app.EvmKeeper.CommitStateDB.WithContext(suite.ctx)
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.CommitStateDB.SetNonce(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.CommitStateDB.SetNonce(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
suite.app.EvmKeeper.CommitStateDB.WithContext(suite.ctx)
tc.malleate()
_, broken := suite.app.EvmKeeper.NonceInvariant()(suite.ctx)
if tc.expBroken {
suite.Require().True(broken)
} else {
suite.Require().False(broken)
}
})
}
}

View File

@ -42,10 +42,6 @@ type Keeper struct {
eip155ChainID *big.Int
debug bool
// TODO: deprecate
// Ethermint concrete implementation on the EVM StateDB interface
CommitStateDB *types.CommitStateDB
// hash header for the current height. Reset during abci.RequestBeginBlock
headerHash common.Hash
}
@ -69,7 +65,6 @@ func NewKeeper(
stakingKeeper: sk,
storeKey: storeKey,
transientKey: transientKey,
CommitStateDB: types.NewCommitStateDB(sdk.Context{}, storeKey, transientKey, paramSpace, ak, bankKeeper),
}
}

View File

@ -2,20 +2,15 @@ package keeper
import (
"context"
"errors"
"math/big"
"time"
"github.com/armon/go-metrics"
ethcmn "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
tmbytes "github.com/tendermint/tendermint/libs/bytes"
tmtypes "github.com/tendermint/tendermint/types"
"github.com/cosmos/cosmos-sdk/telemetry"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/ethermint/x/evm/types"
)
@ -26,17 +21,7 @@ func (k *Keeper) EthereumTx(goCtx context.Context, msg *types.MsgEthereumTx) (*t
defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), types.TypeMsgEthereumTx)
ctx := sdk.UnwrapSDKContext(goCtx)
k.CommitStateDB.WithContext(ctx)
ethMsg, err := msg.AsMessage()
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrorInvalidSigner, err.Error())
}
config, found := k.GetChainConfig(ctx)
if !found {
return nil, types.ErrChainConfigNotFound
}
k.WithContext(ctx)
var labels []metrics.Label
if msg.To() == nil {
@ -46,62 +31,22 @@ func (k *Keeper) EthereumTx(goCtx context.Context, msg *types.MsgEthereumTx) (*t
} else {
labels = []metrics.Label{
telemetry.NewLabel("execution", "call"),
// add label to the called recipient address (contract or account)
telemetry.NewLabel("to", msg.Data.To),
telemetry.NewLabel("to", msg.Data.To), // recipient address (contract or account)
}
}
sender := ethMsg.From()
sender := msg.From
tx := msg.AsTransaction()
txHash := tmtypes.Tx(ctx.TxBytes()).Hash()
ethHash := ethcmn.BytesToHash(txHash)
// Ethereum formatted tx hash
etherumTxHash := msg.AsTransaction().Hash()
st := &types.StateTransition{
Message: ethMsg,
Csdb: k.CommitStateDB.WithContext(ctx),
ChainID: msg.ChainID(),
TxHash: &ethHash,
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
k.CommitStateDB.Prepare(ethHash, k.headerHash, int(k.GetTxIndexTransient()))
k.IncreaseTxIndexTransient()
}
executionResult, err := st.TransitionDb(ctx, config)
response, err := k.ApplyTransaction(tx)
if err != nil {
if errors.Is(err, vm.ErrExecutionReverted) && executionResult != nil {
// keep the execution result for revert reason
executionResult.Response.Reverted = true
return executionResult.Response, nil
}
return nil, err
}
if !st.Simulate {
bloom, found := k.GetBlockBloomTransient()
if !found {
bloom = big.NewInt(0)
}
// update block bloom filter
logsBloom := ethtypes.LogsBloom(executionResult.Logs)
bloom = bloom.Or(bloom, new(big.Int).SetBytes(logsBloom))
k.SetBlockBloomTransient(bloom)
}
defer func() {
if st.Message.Value().IsInt64() {
if tx.Value().IsInt64() {
telemetry.SetGauge(
float32(st.Message.Value().Int64()),
float32(tx.Value().Int64()),
"tx", "msg", "ethereum_tx",
)
}
@ -114,9 +59,15 @@ func (k *Keeper) EthereumTx(goCtx context.Context, msg *types.MsgEthereumTx) (*t
}()
attrs := []sdk.Attribute{
sdk.NewAttribute(sdk.AttributeKeyAmount, st.Message.Value().String()),
sdk.NewAttribute(types.AttributeKeyTxHash, ethcmn.BytesToHash(txHash).Hex()),
sdk.NewAttribute(types.AttributeKeyEthereumTxHash, etherumTxHash.Hex()),
sdk.NewAttribute(sdk.AttributeKeyAmount, tx.Value().String()),
// add event for ethereum transaction hash format
sdk.NewAttribute(types.AttributeKeyEthereumTxHash, response.Hash),
}
if len(ctx.TxBytes()) > 0 {
// add event for tendermint transaction hash format
hash := tmbytes.HexBytes(tmtypes.Tx(ctx.TxBytes()).Hash())
attrs = append(attrs, sdk.NewAttribute(types.AttributeKeyTxHash, hash.String()))
}
if len(msg.Data.To) > 0 {
@ -132,10 +83,9 @@ func (k *Keeper) EthereumTx(goCtx context.Context, msg *types.MsgEthereumTx) (*t
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
sdk.NewAttribute(sdk.AttributeKeySender, sender.String()),
sdk.NewAttribute(sdk.AttributeKeySender, sender),
),
})
executionResult.Response.Hash = etherumTxHash.Hex()
return executionResult.Response, nil
return response, nil
}

View File

@ -18,6 +18,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params"
)
@ -91,7 +92,7 @@ func (k Keeper) GetHashFn() vm.GetHashFunc {
}
}
// TransitionDb runs and attempts to perform a state transition with the given transaction (i.e Message), that will
// ApplyTransaction runs and attempts to perform a state transition with the given transaction (i.e Message), that will
// only be persisted to the underlying KVStore if the transaction does not error.
//
// Gas tracking
@ -108,23 +109,39 @@ func (k Keeper) GetHashFn() vm.GetHashFunc {
// returning.
//
// For relevant discussion see: https://github.com/cosmos/cosmos-sdk/discussions/9072
func (k *Keeper) TransitionDb(msg core.Message) (*types.ExecutionResult, error) {
func (k *Keeper) ApplyTransaction(tx *ethtypes.Transaction) (*types.MsgEthereumTxResponse, error) {
defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), types.MetricKeyTransitionDB)
cfg, found := k.GetChainConfig(k.ctx)
gasMeter := k.ctx.GasMeter() // tx gas meter
infCtx := k.ctx.WithGasMeter(sdk.NewInfiniteGasMeter())
cfg, found := k.GetChainConfig(infCtx)
if !found {
return nil, types.ErrChainConfigNotFound
}
ethCfg := cfg.EthereumConfig(k.eip155ChainID)
evm := k.NewEVM(msg, cfg.EthereumConfig(k.eip155ChainID))
// create an ethereum StateTransition instance and run TransitionDb
result, err := k.ApplyMessage(evm, msg)
msg, err := tx.AsMessage(ethtypes.MakeSigner(ethCfg, big.NewInt(k.ctx.BlockHeight())))
if err != nil {
return nil, err
}
return result, nil
evm := k.NewEVM(msg, ethCfg)
k.IncreaseTxIndexTransient()
k.WithContext(k.ctx.WithGasMeter(gasMeter))
// create an ethereum StateTransition instance and run TransitionDb
res, err := k.ApplyMessage(evm, msg, ethCfg)
if err != nil {
return nil, err
}
txHash := tx.Hash()
res.Hash = txHash.Hex()
res.Logs = types.NewLogsFromEth(k.GetTxLogs(txHash))
return res, nil
}
// Gas consumption notes (write doc from this)
@ -143,10 +160,10 @@ func (k *Keeper) TransitionDb(msg core.Message) (*types.ExecutionResult, error)
// TODO: (@fedekunze) currently we consume the entire gas limit in the ante handler, so if a transaction fails
// the amount spent will be grater than the gas spent in an Ethereum tx (i.e here the leftover gas won't be refunded).
func (k *Keeper) ApplyMessage(evm *vm.EVM, msg core.Message) (*types.ExecutionResult, error) {
func (k *Keeper) ApplyMessage(evm *vm.EVM, msg core.Message, cfg *params.ChainConfig) (*types.MsgEthereumTxResponse, error) {
var (
ret []byte // return bytes from evm execution
vmErr, err error // vm errors do not effect consensus and are therefore not assigned to err
vmErr error // vm errors do not effect consensus and are therefore not assigned to err
)
sender := vm.AccountRef(msg.From())
@ -166,7 +183,7 @@ func (k *Keeper) ApplyMessage(evm *vm.EVM, msg core.Message) (*types.ExecutionRe
// ensure gas is consistent during CheckTx
if k.ctx.IsCheckTx() {
if err := k.checkGasConsumption(msg, gasConsumed, contractCreation); err != nil {
if err := k.CheckGasConsumption(msg, cfg, gasConsumed, contractCreation); err != nil {
return nil, err
}
}
@ -178,8 +195,7 @@ func (k *Keeper) ApplyMessage(evm *vm.EVM, msg core.Message) (*types.ExecutionRe
}
// refund gas prior to handling the vm error in order to set the updated gas meter
gasConsumed, leftoverGas, err = k.refundGas(msg, leftoverGas)
if err != nil {
if err := k.RefundGas(msg, leftoverGas); err != nil {
return nil, err
}
@ -193,26 +209,17 @@ func (k *Keeper) ApplyMessage(evm *vm.EVM, msg core.Message) (*types.ExecutionRe
return nil, sdkerrors.Wrap(types.ErrVMExecution, vmErr.Error())
}
return &types.ExecutionResult{
Response: &types.MsgEthereumTxResponse{
return &types.MsgEthereumTxResponse{
Ret: ret,
},
GasInfo: types.GasInfo{
GasLimit: k.ctx.GasMeter().Limit(),
GasConsumed: gasConsumed,
GasRefunded: leftoverGas,
},
Reverted: false,
}, nil
}
// checkGasConsumption verifies that the amount of gas consumed so far matches the intrinsic gas value.
func (k *Keeper) checkGasConsumption(msg core.Message, gasConsumed uint64, isContractCreation bool) error {
cfg, _ := k.GetChainConfig(k.ctx)
ethCfg := cfg.EthereumConfig(k.eip155ChainID)
// CheckGasConsumption verifies that the amount of gas consumed so far matches the intrinsic gas value.
func (k *Keeper) CheckGasConsumption(msg core.Message, cfg *params.ChainConfig, gasConsumed uint64, isContractCreation bool) error {
height := big.NewInt(k.ctx.BlockHeight())
homestead := ethCfg.IsHomestead(height)
istanbul := ethCfg.IsIstanbul(height)
homestead := cfg.IsHomestead(height)
istanbul := cfg.IsIstanbul(height)
intrinsicGas, err := core.IntrinsicGas(msg.Data(), msg.AccessList(), isContractCreation, homestead, istanbul)
if err != nil {
@ -227,11 +234,11 @@ func (k *Keeper) checkGasConsumption(msg core.Message, gasConsumed uint64, isCon
return nil
}
// refundGas transfers the leftover gas to the sender of the message, caped to half of the total gas
// RefundGas transfers the leftover gas to the sender of the message, caped to half of the total gas
// consumed in the transaction. Additionally, the function sets the total gas consumed to the value
// returned by the EVM execution, thus ignoring the previous intrinsic gas inconsumed during in the
// AnteHandler.
func (k *Keeper) refundGas(msg core.Message, leftoverGas uint64) (consumed, leftover uint64, err error) {
func (k *Keeper) RefundGas(msg core.Message, leftoverGas uint64) error {
gasConsumed := msg.Gas() - leftoverGas
// Apply refund counter, capped to half of the used gas.
@ -246,18 +253,21 @@ func (k *Keeper) refundGas(msg core.Message, leftoverGas uint64) (consumed, left
// Return EVM tokens for remaining gas, exchanged at the original rate.
remaining := new(big.Int).Mul(new(big.Int).SetUint64(leftoverGas), msg.GasPrice())
// ignore gas consumption
infCtx := k.ctx.WithGasMeter(sdk.NewInfiniteGasMeter())
switch remaining.Sign() {
case -1:
// negative refund errors
return 0, 0, fmt.Errorf("refunded amount value cannot be negative %d", remaining.Int64())
return fmt.Errorf("refunded amount value cannot be negative %d", remaining.Int64())
case 1:
// positive amount refund
params := k.GetParams(k.ctx)
params := k.GetParams(infCtx)
refundedCoins := sdk.Coins{sdk.NewCoin(params.EvmDenom, sdk.NewIntFromBigInt(remaining))}
// refund to sender from the fee collector module account, which is the escrow account in charge of collecting tx fees
if err := k.bankKeeper.SendCoinsFromModuleToAccount(k.ctx, authtypes.FeeCollectorName, msg.From().Bytes(), refundedCoins); err != nil {
return 0, 0, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds, "fee collector account failed to refund fees: %s", err.Error())
if err := k.bankKeeper.SendCoinsFromModuleToAccount(infCtx, authtypes.FeeCollectorName, msg.From().Bytes(), refundedCoins); err != nil {
return sdkerrors.Wrapf(sdkerrors.ErrInsufficientFunds, "fee collector account failed to refund fees: %s", err.Error())
}
default:
// no refund, consume gas and update the tx gas meter
@ -270,5 +280,5 @@ func (k *Keeper) refundGas(msg core.Message, leftoverGas uint64) (consumed, left
gasMeter.ConsumeGas(gasConsumed, "update gas consumption after refund")
k.WithContext(k.ctx.WithGasMeter(gasMeter))
return gasConsumed, leftoverGas, nil
return nil
}

View File

@ -88,16 +88,14 @@ type AppModule struct {
AppModuleBasic
keeper *keeper.Keeper
ak types.AccountKeeper
bk types.BankKeeper
}
// NewAppModule creates a new AppModule object
func NewAppModule(k *keeper.Keeper, ak types.AccountKeeper, bk types.BankKeeper) AppModule {
func NewAppModule(k *keeper.Keeper, ak types.AccountKeeper) AppModule {
return AppModule{
AppModuleBasic: AppModuleBasic{},
keeper: k,
ak: ak,
bk: bk,
}
}
@ -151,7 +149,7 @@ func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONMarshaler, data j
var genesisState types.GenesisState
cdc.MustUnmarshalJSON(data, &genesisState)
InitGenesis(ctx, am.keeper, am.ak, am.bk, genesisState)
InitGenesis(ctx, am.keeper, am.ak, genesisState)
return []abci.ValidatorUpdate{}
}

View File

@ -1,348 +0,0 @@
package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
ethcmn "github.com/ethereum/go-ethereum/common"
)
var ripemd = ethcmn.HexToAddress("0000000000000000000000000000000000000003")
// journalEntry is a modification entry in the state change journal that can be
// reverted on demand.
type journalEntry interface {
// revert undoes the changes introduced by this journal entry.
revert(*CommitStateDB)
// dirtied returns the Ethereum address modified by this journal entry.
dirtied() *ethcmn.Address
}
type revision struct {
id int
journalIndex int
}
// journal contains the list of state modifications applied since the last state
// commit. These are tracked to be able to be reverted in case of an execution
// exception or revertal request.
type journal struct {
entries []journalEntry // Current changes tracked by the journal
dirties []dirty // Dirty accounts and the number of changes
addressToJournalIndex map[ethcmn.Address]int // map from address to the index of the dirties slice
}
// dirty represents a single key value pair of the journal dirties, where the
// key correspons to the account address and the value to the number of
// changes for that account.
type dirty struct {
address ethcmn.Address
changes int
}
// newJournal create a new initialized journal.
func newJournal() *journal {
return &journal{
dirties: []dirty{},
addressToJournalIndex: make(map[ethcmn.Address]int),
}
}
// append inserts a new modification entry to the end of the change journal.
func (j *journal) append(entry journalEntry) {
j.entries = append(j.entries, entry)
if addr := entry.dirtied(); addr != nil {
j.addDirty(*addr)
}
}
// revert undoes a batch of journalled modifications along with any reverted
// dirty handling too.
func (j *journal) revert(statedb *CommitStateDB, snapshot int) {
for i := len(j.entries) - 1; i >= snapshot; i-- {
// Undo the changes made by the operation
j.entries[i].revert(statedb)
// Drop any dirty tracking induced by the change
if addr := j.entries[i].dirtied(); addr != nil {
j.substractDirty(*addr)
if j.getDirty(*addr) == 0 {
j.deleteDirty(*addr)
}
}
}
j.entries = j.entries[:snapshot]
}
// dirty explicitly sets an address to dirty, even if the change entries would
// otherwise suggest it as clean. This method is an ugly hack to handle the RIPEMD
// precompile consensus exception.
func (j *journal) dirty(addr ethcmn.Address) {
j.addDirty(addr)
}
// length returns the current number of entries in the journal.
func (j *journal) length() int {
return len(j.entries)
}
// getDirty returns the dirty count for a given address. If the address is not
// found it returns 0.
func (j *journal) getDirty(addr ethcmn.Address) int {
idx, found := j.addressToJournalIndex[addr]
if !found {
return 0
}
return j.dirties[idx].changes
}
// addDirty adds 1 to the dirty count of an address. If the dirty entry is not
// found it creates it.
func (j *journal) addDirty(addr ethcmn.Address) {
idx, found := j.addressToJournalIndex[addr]
if !found {
j.dirties = append(j.dirties, dirty{address: addr, changes: 0})
idx = len(j.dirties) - 1
j.addressToJournalIndex[addr] = idx
}
j.dirties[idx].changes++
}
// substractDirty subtracts 1 to the dirty count of an address. It performs a
// no-op if the address is not found.
func (j *journal) substractDirty(addr ethcmn.Address) {
idx, found := j.addressToJournalIndex[addr]
if !found {
return
}
if j.dirties[idx].changes == 0 {
return
}
j.dirties[idx].changes--
}
// deleteDirty deletes a dirty entry from the jounal's dirties slice. If the
// entry is not found it performs a no-op.
func (j *journal) deleteDirty(addr ethcmn.Address) {
idx, found := j.addressToJournalIndex[addr]
if !found {
return
}
j.dirties = append(j.dirties[:idx], j.dirties[idx+1:]...)
delete(j.addressToJournalIndex, addr)
}
type (
// Changes to the account trie.
createObjectChange struct {
account *ethcmn.Address
}
resetObjectChange struct {
prev *stateObject
}
suicideChange struct {
account *ethcmn.Address
prev bool // whether account had already suicided
prevBalance sdk.Int
}
// Changes to individual accounts.
balanceChange struct {
account *ethcmn.Address
prev sdk.Int
}
nonceChange struct {
account *ethcmn.Address
prev uint64
}
storageChange struct {
account *ethcmn.Address
key, prevValue ethcmn.Hash
}
codeChange struct {
account *ethcmn.Address
prevCode, prevHash []byte
}
// Changes to other state values.
refundChange struct {
prev uint64
}
addLogChange struct {
txhash ethcmn.Hash
}
addPreimageChange struct {
hash ethcmn.Hash
}
touchChange struct {
account *ethcmn.Address
// prev bool
// prevDirty bool
}
)
func (ch createObjectChange) revert(s *CommitStateDB) {
delete(s.stateObjectsDirty, *ch.account)
idx, exists := s.addressToObjectIndex[*ch.account]
if !exists {
// perform no-op
return
}
// remove from the slice
delete(s.addressToObjectIndex, *ch.account)
// if the slice contains one element, delete it
if len(s.stateObjects) == 1 {
s.stateObjects = []stateEntry{}
return
}
// move the elements one position left on the array
for i := idx + 1; i < len(s.stateObjects); i++ {
s.stateObjects[i-1] = s.stateObjects[i]
// the new index is i - 1
s.addressToObjectIndex[s.stateObjects[i].address] = i - 1
}
// finally, delete the last element of the slice to account for the removed object
s.stateObjects = s.stateObjects[:len(s.stateObjects)-1]
}
func (ch createObjectChange) dirtied() *ethcmn.Address {
return ch.account
}
func (ch resetObjectChange) revert(s *CommitStateDB) {
s.setStateObject(ch.prev)
}
func (ch resetObjectChange) dirtied() *ethcmn.Address {
return nil
}
func (ch suicideChange) revert(s *CommitStateDB) {
so := s.getStateObject(*ch.account)
if so != nil {
so.suicided = ch.prev
so.setBalance(ch.prevBalance)
}
}
func (ch suicideChange) dirtied() *ethcmn.Address {
return ch.account
}
func (ch touchChange) revert(s *CommitStateDB) {
}
func (ch touchChange) dirtied() *ethcmn.Address {
return ch.account
}
func (ch balanceChange) revert(s *CommitStateDB) {
s.getStateObject(*ch.account).setBalance(ch.prev)
}
func (ch balanceChange) dirtied() *ethcmn.Address {
return ch.account
}
func (ch nonceChange) revert(s *CommitStateDB) {
s.getStateObject(*ch.account).setNonce(ch.prev)
}
func (ch nonceChange) dirtied() *ethcmn.Address {
return ch.account
}
func (ch codeChange) revert(s *CommitStateDB) {
s.getStateObject(*ch.account).setCode(ethcmn.BytesToHash(ch.prevHash), ch.prevCode)
}
func (ch codeChange) dirtied() *ethcmn.Address {
return ch.account
}
func (ch storageChange) revert(s *CommitStateDB) {
s.getStateObject(*ch.account).setState(ch.key, ch.prevValue)
}
func (ch storageChange) dirtied() *ethcmn.Address {
return ch.account
}
func (ch refundChange) revert(s *CommitStateDB) {
s.refund = ch.prev
}
func (ch refundChange) dirtied() *ethcmn.Address {
return nil
}
func (ch addLogChange) revert(s *CommitStateDB) {
logs, err := s.GetLogs(ch.txhash)
if err != nil {
// panic on unmarshal error
panic(err)
}
// delete logs if entry is empty or has only one item
if len(logs) <= 1 {
s.DeleteLogs(ch.txhash)
} else if err := s.SetLogs(ch.txhash, logs[:len(logs)-1]); err != nil {
// panic on marshal error
panic(err)
}
s.logSize--
}
func (ch addLogChange) dirtied() *ethcmn.Address {
return nil
}
func (ch addPreimageChange) revert(s *CommitStateDB) {
idx, exists := s.hashToPreimageIndex[ch.hash]
if !exists {
// perform no-op
return
}
// remove from the slice
delete(s.hashToPreimageIndex, ch.hash)
// if the slice contains one element, delete it
if len(s.preimages) == 1 {
s.preimages = []preimageEntry{}
return
}
// move the elements one position left on the array
for i := idx + 1; i < len(s.preimages); i++ {
s.preimages[i-1] = s.preimages[i]
// the new index is i - 1
s.hashToPreimageIndex[s.preimages[i].hash] = i - 1
}
// finally, delete the last element
s.preimages = s.preimages[:len(s.preimages)-1]
}
func (ch addPreimageChange) dirtied() *ethcmn.Address {
return nil
}

View File

@ -1,354 +0,0 @@
package types
import (
"fmt"
"os"
"testing"
"github.com/cosmos/ethermint/crypto/ethsecp256k1"
enccodec "github.com/cosmos/ethermint/encoding/codec"
ethermint "github.com/cosmos/ethermint/types"
"github.com/stretchr/testify/suite"
tmlog "github.com/tendermint/tendermint/libs/log"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
tmdb "github.com/tendermint/tm-db"
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types"
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
paramkeeper "github.com/cosmos/cosmos-sdk/x/params/keeper"
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
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/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
)
func newTestCodec() (codec.BinaryMarshaler, *codec.LegacyAmino) {
interfaceRegistry := codectypes.NewInterfaceRegistry()
cdc := codec.NewProtoCodec(interfaceRegistry)
amino := codec.NewLegacyAmino()
sdk.RegisterLegacyAminoCodec(amino)
enccodec.RegisterInterfaces(interfaceRegistry)
return cdc, amino
}
type JournalTestSuite struct {
suite.Suite
address ethcmn.Address
journal *journal
ctx sdk.Context
stateDB *CommitStateDB
}
func (suite *JournalTestSuite) SetupTest() {
suite.setup()
privkey, err := ethsecp256k1.GenerateKey()
suite.Require().NoError(err)
suite.address = ethcmn.BytesToAddress(privkey.PubKey().Address().Bytes())
suite.journal = newJournal()
balance := ethermint.NewPhotonCoin(sdk.NewInt(100))
acc := &ethermint.EthAccount{
BaseAccount: authtypes.NewBaseAccount(sdk.AccAddress(suite.address.Bytes()), nil, 0, 0),
CodeHash: ethcrypto.Keccak256(nil),
}
suite.stateDB.accountKeeper.SetAccount(suite.ctx, acc)
suite.stateDB.bankKeeper.SetBalance(suite.ctx, sdk.AccAddress(suite.address.Bytes()), balance)
suite.stateDB.SetLogs(ethcmn.BytesToHash([]byte("txhash")), []*ethtypes.Log{
{
Address: suite.address,
Topics: []ethcmn.Hash{ethcmn.BytesToHash([]byte("topic_0"))},
Data: []byte("data_0"),
BlockNumber: 1,
TxHash: ethcmn.BytesToHash([]byte("tx_hash")),
TxIndex: 1,
BlockHash: ethcmn.BytesToHash([]byte("block_hash")),
Index: 1,
Removed: false,
},
{
Address: suite.address,
Topics: []ethcmn.Hash{ethcmn.BytesToHash([]byte("topic_1"))},
Data: []byte("data_1"),
BlockNumber: 10,
TxHash: ethcmn.BytesToHash([]byte("tx_hash")),
TxIndex: 0,
BlockHash: ethcmn.BytesToHash([]byte("block_hash")),
Index: 0,
Removed: false,
},
})
}
// setup performs a manual setup of the GoLevelDB and mounts the required IAVL stores. We use the manual
// setup here instead of the Ethermint app test setup because the journal methods are private and using
// the latter would result in a cycle dependency. We also want to avoid declaring the journal methods public
// to maintain consistency with the Geth implementation.
func (suite *JournalTestSuite) setup() {
authKey := sdk.NewKVStoreKey(authtypes.StoreKey)
paramsKey := sdk.NewKVStoreKey(paramtypes.StoreKey)
paramsTKey := sdk.NewTransientStoreKey(paramtypes.TStoreKey)
tKey := sdk.NewTransientStoreKey(TransientKey)
bankKey := sdk.NewKVStoreKey(banktypes.StoreKey)
storeKey := sdk.NewKVStoreKey(StoreKey)
db, err := tmdb.NewDB("state", tmdb.GoLevelDBBackend, "temp")
suite.Require().NoError(err)
defer func() {
os.RemoveAll("temp")
}()
cms := store.NewCommitMultiStore(db)
cms.MountStoreWithDB(authKey, sdk.StoreTypeIAVL, db)
cms.MountStoreWithDB(bankKey, sdk.StoreTypeIAVL, db)
cms.MountStoreWithDB(paramsKey, sdk.StoreTypeIAVL, db)
cms.MountStoreWithDB(storeKey, sdk.StoreTypeIAVL, db)
cms.MountStoreWithDB(paramsTKey, sdk.StoreTypeTransient, db)
cms.MountStoreWithDB(tKey, sdk.StoreTypeTransient, db)
err = cms.LoadLatestVersion()
suite.Require().NoError(err)
cdc, amino := newTestCodec()
paramsKeeper := paramkeeper.NewKeeper(cdc, amino, paramsKey, paramsTKey)
authSubspace := paramsKeeper.Subspace(authtypes.ModuleName)
bankSubspace := paramsKeeper.Subspace(banktypes.ModuleName)
evmSubspace := paramsKeeper.Subspace(ModuleName).WithKeyTable(ParamKeyTable())
ak := authkeeper.NewAccountKeeper(cdc, authKey, authSubspace, ethermint.ProtoAccount, nil)
bk := bankkeeper.NewBaseKeeper(cdc, bankKey, ak, bankSubspace, nil)
suite.ctx = sdk.NewContext(cms, tmproto.Header{ChainID: "ethermint-8"}, false, tmlog.NewNopLogger())
suite.stateDB = NewCommitStateDB(suite.ctx, storeKey, tKey, evmSubspace, ak, bk).WithContext(suite.ctx)
suite.stateDB.SetParams(DefaultParams())
}
func TestJournalTestSuite(t *testing.T) {
suite.Run(t, new(JournalTestSuite))
}
func (suite *JournalTestSuite) TestJournal_append_revert() {
testCases := []struct {
name string
entry journalEntry
}{
{
"createObjectChange",
createObjectChange{
account: &suite.address,
},
},
{
"resetObjectChange",
resetObjectChange{
prev: &stateObject{
address: suite.address,
balance: sdk.OneInt(),
},
},
},
{
"suicideChange",
suicideChange{
account: &suite.address,
prev: false,
prevBalance: sdk.OneInt(),
},
},
{
"balanceChange",
balanceChange{
account: &suite.address,
prev: sdk.OneInt(),
},
},
{
"nonceChange",
nonceChange{
account: &suite.address,
prev: 1,
},
},
{
"storageChange",
storageChange{
account: &suite.address,
key: ethcmn.BytesToHash([]byte("key")),
prevValue: ethcmn.BytesToHash([]byte("value")),
},
},
{
"codeChange",
codeChange{
account: &suite.address,
prevCode: []byte("code"),
prevHash: []byte("hash"),
},
},
{
"touchChange",
touchChange{
account: &suite.address,
},
},
{
"refundChange",
refundChange{
prev: 1,
},
},
{
"addPreimageChange",
addPreimageChange{
hash: ethcmn.BytesToHash([]byte("hash")),
},
},
{
"addLogChange",
addLogChange{
txhash: ethcmn.BytesToHash([]byte("hash")),
},
},
{
"addLogChange - 2 logs",
addLogChange{
txhash: ethcmn.BytesToHash([]byte("txhash")),
},
},
{
"accessListAddAccountChange",
accessListAddAccountChange{
address: &suite.address,
},
},
}
var dirtyCount int
for i, tc := range testCases {
suite.journal.append(tc.entry)
suite.Require().Equal(suite.journal.length(), i+1, tc.name)
if tc.entry.dirtied() != nil {
dirtyCount++
suite.Require().Equal(dirtyCount, suite.journal.getDirty(suite.address), tc.name)
}
}
// revert to the initial journal state
suite.journal.revert(suite.stateDB, 0)
// verify the dirty entry has been deleted
idx, ok := suite.journal.addressToJournalIndex[suite.address]
suite.Require().False(ok)
suite.Require().Zero(idx)
}
func (suite *JournalTestSuite) TestJournal_preimage_revert() {
suite.stateDB.preimages = []preimageEntry{
{
hash: ethcmn.BytesToHash([]byte("hash")),
preimage: []byte("preimage0"),
},
{
hash: ethcmn.BytesToHash([]byte("hash1")),
preimage: []byte("preimage1"),
},
{
hash: ethcmn.BytesToHash([]byte("hash2")),
preimage: []byte("preimage2"),
},
}
for i, preimage := range suite.stateDB.preimages {
suite.stateDB.hashToPreimageIndex[preimage.hash] = i
}
change := addPreimageChange{
hash: ethcmn.BytesToHash([]byte("hash")),
}
// delete first entry
change.revert(suite.stateDB)
suite.Require().Len(suite.stateDB.preimages, 2)
suite.Require().Equal(len(suite.stateDB.preimages), len(suite.stateDB.hashToPreimageIndex))
for i, entry := range suite.stateDB.preimages {
suite.Require().Equal(fmt.Sprintf("preimage%d", i+1), string(entry.preimage), entry.hash.String())
idx, found := suite.stateDB.hashToPreimageIndex[entry.hash]
suite.Require().True(found)
suite.Require().Equal(i, idx)
}
}
func (suite *JournalTestSuite) TestJournal_createObjectChange_revert() {
addr := ethcmn.BytesToAddress([]byte("addr"))
suite.stateDB.stateObjects = []stateEntry{
{
address: addr,
stateObject: &stateObject{
address: addr,
},
},
{
address: ethcmn.BytesToAddress([]byte("addr1")),
stateObject: &stateObject{
address: ethcmn.BytesToAddress([]byte("addr1")),
},
},
{
address: ethcmn.BytesToAddress([]byte("addr2")),
stateObject: &stateObject{
address: ethcmn.BytesToAddress([]byte("addr2")),
},
},
}
for i, so := range suite.stateDB.stateObjects {
suite.stateDB.addressToObjectIndex[so.address] = i
}
change := createObjectChange{
account: &addr,
}
// delete first entry
change.revert(suite.stateDB)
suite.Require().Len(suite.stateDB.stateObjects, 2)
suite.Require().Equal(len(suite.stateDB.stateObjects), len(suite.stateDB.addressToObjectIndex))
for i, entry := range suite.stateDB.stateObjects {
suite.Require().Equal(ethcmn.BytesToAddress([]byte(fmt.Sprintf("addr%d", i+1))).String(), entry.address.String())
idx, found := suite.stateDB.addressToObjectIndex[entry.address]
suite.Require().True(found)
suite.Require().Equal(i, idx)
}
}
func (suite *JournalTestSuite) TestJournal_dirty() {
// dirty entry hasn't been set
idx, ok := suite.journal.addressToJournalIndex[suite.address]
suite.Require().False(ok)
suite.Require().Zero(idx)
// update dirty count
suite.journal.dirty(suite.address)
suite.Require().Equal(1, suite.journal.getDirty(suite.address))
}

View File

@ -89,7 +89,7 @@ func (log *Log) ToEthereum() *ethtypes.Log {
}
func NewLogsFromEth(ethlogs []*ethtypes.Log) []*Log {
var logs []*Log
var logs []*Log // nolint: prealloc
for _, ethlog := range ethlogs {
logs = append(logs, NewLogFromEth(ethlog))
}

View File

@ -2,7 +2,6 @@ package types
import (
"fmt"
"io"
"math/big"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
@ -14,7 +13,6 @@ import (
ethcmn "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
)
var (
@ -191,48 +189,6 @@ func (msg MsgEthereumTx) GetSignBytes() []byte {
panic("must use 'RLPSignBytes' with a chain ID to get the valid bytes to sign")
}
// RLPSignBytes returns the RLP hash of an Ethereum transaction message with a
// given chainID used for signing.
func (msg MsgEthereumTx) RLPSignBytes(chainID *big.Int) ethcmn.Hash {
if msg.Data.ChainID != nil {
chainID = new(big.Int).SetBytes(msg.Data.ChainID)
}
var accessList *ethtypes.AccessList
if msg.Data.Accesses != nil {
accessList = msg.Data.Accesses.ToEthAccessList()
}
return rlpHash([]interface{}{
chainID,
msg.Data.Nonce,
new(big.Int).SetBytes(msg.Data.GasPrice),
msg.Data.GasLimit,
msg.To(),
new(big.Int).SetBytes(msg.Data.Amount),
new(big.Int).SetBytes(msg.Data.Input),
accessList,
})
}
// EncodeRLP implements the rlp.Encoder interface.
func (msg *MsgEthereumTx) EncodeRLP(w io.Writer) error {
tx := msg.AsTransaction()
return tx.EncodeRLP(w)
}
// DecodeRLP implements the rlp.Decoder interface.
func (msg *MsgEthereumTx) DecodeRLP(stream *rlp.Stream) error {
tx := &ethtypes.Transaction{}
if err := tx.DecodeRLP(stream); err != nil {
return err
}
msg.FromEthereumTx(tx)
return nil
}
// Sign calculates a secp256k1 ECDSA signature and signs the transaction. It
// takes a keyring signer and the chainID to sign an Ethereum transaction according to
// EIP155 standard.

View File

@ -1,7 +1,6 @@
package types
import (
"bytes"
"math/big"
"testing"
@ -15,7 +14,6 @@ import (
ethcmn "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
)
type MsgsTestSuite struct {
@ -91,23 +89,6 @@ func (suite *MsgsTestSuite) TestMsgEthereumTx_ValidateBasic() {
}
}
func (suite *MsgsTestSuite) TestMsgEthereumTx_EncodeRLP() {
expMsg := NewMsgEthereumTx(suite.chainID, 0, &suite.to, nil, 100000, nil, []byte("test"), nil)
raw, err := rlp.EncodeToBytes(&expMsg)
suite.Require().NoError(err)
msg := &MsgEthereumTx{}
err = rlp.Decode(bytes.NewReader(raw), &msg)
suite.Require().NoError(err)
suite.Require().Equal(expMsg.Data, msg.Data)
}
func (suite *MsgsTestSuite) TestMsgEthereumTx_RLPSignBytes() {
msg := NewMsgEthereumTx(suite.chainID, 0, &suite.to, nil, 100000, nil, []byte("test"), nil)
suite.NotPanics(func() { _ = msg.RLPSignBytes(suite.chainID) })
}
func (suite *MsgsTestSuite) TestMsgEthereumTx_Sign() {
msg := NewMsgEthereumTx(suite.chainID, 0, &suite.to, nil, 100000, nil, []byte("test"), nil)

View File

@ -73,8 +73,8 @@ var xxx_messageInfo_QueryAccountRequest proto.InternalMessageInfo
type QueryAccountResponse struct {
// balance is the balance of the EVM denomination.
Balance string `protobuf:"bytes,1,opt,name=balance,proto3" json:"balance,omitempty"`
// code_hash is the code bytes from the EOA.
CodeHash []byte `protobuf:"bytes,2,opt,name=code_hash,json=codeHash,proto3" json:"code_hash,omitempty"`
// code hash is the hex-formatted code bytes from the EOA.
CodeHash string `protobuf:"bytes,2,opt,name=code_hash,json=codeHash,proto3" json:"code_hash,omitempty"`
// nonce is the account's sequence number.
Nonce uint64 `protobuf:"varint,3,opt,name=nonce,proto3" json:"nonce,omitempty"`
}
@ -119,11 +119,11 @@ func (m *QueryAccountResponse) GetBalance() string {
return ""
}
func (m *QueryAccountResponse) GetCodeHash() []byte {
func (m *QueryAccountResponse) GetCodeHash() string {
if m != nil {
return m.CodeHash
}
return nil
return ""
}
func (m *QueryAccountResponse) GetNonce() uint64 {
@ -972,72 +972,72 @@ func init() {
}
var fileDescriptor_8bbc79ec2b6c5cb2 = []byte{
// 1028 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x96, 0xcf, 0x6f, 0x1b, 0x45,
0x14, 0xc7, 0xbd, 0x8d, 0x63, 0x27, 0x2f, 0x09, 0x2a, 0x83, 0x29, 0x61, 0x8b, 0x1c, 0x6b, 0x51,
0x63, 0xe7, 0x47, 0x77, 0x6b, 0x23, 0xf1, 0x4b, 0x48, 0x28, 0xa9, 0x14, 0x2a, 0x51, 0xa1, 0xe2,
0xf4, 0xc4, 0xc5, 0x1a, 0xaf, 0x47, 0x6b, 0x2b, 0xeb, 0x1d, 0xd7, 0xb3, 0xb6, 0x12, 0x45, 0xb9,
0x70, 0x40, 0x20, 0x71, 0x00, 0x71, 0xa0, 0x42, 0x42, 0xea, 0x95, 0x1b, 0x7f, 0x46, 0x8f, 0x95,
0xb8, 0x70, 0x42, 0x28, 0xe1, 0xc0, 0x9f, 0x81, 0x66, 0xe6, 0xad, 0xbd, 0x9b, 0x78, 0xb3, 0x0e,
0xb7, 0x9d, 0xf1, 0xfb, 0xf1, 0x79, 0xef, 0xcd, 0x7c, 0xc7, 0x60, 0xb1, 0xb0, 0xcb, 0x86, 0xfd,
0x5e, 0x10, 0x3a, 0x6c, 0xdc, 0x77, 0xc6, 0x75, 0xea, 0x0f, 0xba, 0xb4, 0xee, 0x3c, 0x1b, 0xb1,
0xe1, 0x89, 0x3d, 0x18, 0xf2, 0x90, 0x93, 0x3b, 0x13, 0x1b, 0x9b, 0x8d, 0xfb, 0x76, 0x64, 0x63,
0x96, 0x3c, 0xee, 0x71, 0x65, 0xe2, 0xc8, 0x2f, 0x6d, 0x6d, 0x6e, 0xbb, 0x5c, 0xf4, 0xb9, 0x70,
0xda, 0x54, 0x30, 0x1d, 0xc6, 0x19, 0xd7, 0xdb, 0x2c, 0xa4, 0x75, 0x67, 0x40, 0xbd, 0x5e, 0x40,
0xc3, 0x1e, 0x0f, 0xd0, 0xf6, 0x1d, 0x8f, 0x73, 0xcf, 0x67, 0x0e, 0x1d, 0xf4, 0x1c, 0x1a, 0x04,
0x3c, 0x54, 0x3f, 0x0a, 0xfc, 0xb5, 0x92, 0xc2, 0x26, 0x21, 0x94, 0x85, 0xf5, 0x11, 0xbc, 0xf1,
0xa5, 0xcc, 0xb0, 0xe7, 0xba, 0x7c, 0x14, 0x84, 0x4d, 0xf6, 0x6c, 0xc4, 0x44, 0x48, 0xd6, 0xa1,
0x48, 0x3b, 0x9d, 0x21, 0x13, 0x62, 0xdd, 0xa8, 0x18, 0xb5, 0xe5, 0x66, 0xb4, 0xfc, 0x78, 0xe9,
0xdb, 0x17, 0x1b, 0xb9, 0x7f, 0x5f, 0x6c, 0xe4, 0x2c, 0x17, 0x4a, 0x49, 0x57, 0x31, 0xe0, 0x81,
0x60, 0xd2, 0xb7, 0x4d, 0x7d, 0x1a, 0xb8, 0x2c, 0xf2, 0xc5, 0x25, 0xb9, 0x0b, 0xcb, 0x2e, 0xef,
0xb0, 0x56, 0x97, 0x8a, 0xee, 0xfa, 0xad, 0x8a, 0x51, 0x5b, 0x6d, 0x2e, 0xc9, 0x8d, 0x47, 0x54,
0x74, 0x49, 0x09, 0x16, 0x03, 0x2e, 0x9d, 0x16, 0x2a, 0x46, 0x2d, 0xdf, 0xd4, 0x0b, 0xeb, 0x53,
0x78, 0x5b, 0x25, 0x79, 0xa8, 0x5a, 0xf2, 0x3f, 0x28, 0xbf, 0x31, 0xc0, 0x9c, 0x15, 0x01, 0x61,
0xef, 0xc1, 0x6b, 0xba, 0xdb, 0xad, 0x64, 0xa4, 0x35, 0xbd, 0xbb, 0xa7, 0x37, 0x89, 0x09, 0x4b,
0x42, 0x26, 0x95, 0x7c, 0xb7, 0x14, 0xdf, 0x64, 0x2d, 0x43, 0x50, 0x1d, 0xb5, 0x15, 0x8c, 0xfa,
0x6d, 0x36, 0xc4, 0x0a, 0xd6, 0x70, 0xf7, 0x0b, 0xb5, 0x39, 0xe9, 0xf4, 0xbe, 0x6e, 0xc6, 0x4d,
0x6a, 0x78, 0x80, 0x9d, 0x9e, 0xb8, 0x66, 0x75, 0xda, 0xfa, 0x1c, 0x93, 0x1d, 0x86, 0x7c, 0x48,
0xbd, 0xec, 0x64, 0xe4, 0x36, 0x2c, 0x1c, 0xb1, 0x13, 0x55, 0xdb, 0x72, 0x53, 0x7e, 0xc6, 0xd2,
0xef, 0x62, 0xfa, 0x49, 0x30, 0x4c, 0x5f, 0x82, 0xc5, 0x31, 0xf5, 0x47, 0x51, 0x72, 0xbd, 0xb0,
0xde, 0x87, 0xdb, 0xd8, 0xef, 0xce, 0x8d, 0x8a, 0xac, 0xc2, 0xeb, 0x31, 0x3f, 0x4c, 0x41, 0x20,
0x2f, 0x0f, 0x88, 0xf2, 0x5a, 0x6d, 0xaa, 0x6f, 0xab, 0x01, 0x44, 0x19, 0x3e, 0x3d, 0x7e, 0xcc,
0x3d, 0x11, 0xa5, 0x20, 0x90, 0x57, 0xc7, 0x4a, 0xc7, 0x57, 0xdf, 0xb1, 0xe0, 0x07, 0xd8, 0x8f,
0xc8, 0x07, 0xc3, 0x3b, 0x90, 0xf7, 0xb9, 0x27, 0xa1, 0x16, 0x6a, 0x2b, 0x8d, 0xbb, 0xf6, 0xec,
0x6b, 0x6a, 0x3f, 0xe6, 0x5e, 0x53, 0x19, 0x5a, 0x67, 0xf0, 0xa6, 0x9e, 0x84, 0xcf, 0xdd, 0xa3,
0x8c, 0xf4, 0xe4, 0x00, 0x60, 0x7a, 0x5f, 0x55, 0x6b, 0x57, 0x1a, 0x9b, 0xb6, 0x3e, 0x58, 0xb6,
0xbc, 0xdc, 0xb6, 0xd6, 0x08, 0xbc, 0xdc, 0xf6, 0x93, 0xe9, 0xa4, 0x9a, 0x31, 0xcf, 0x58, 0x19,
0xbf, 0x19, 0x70, 0xe7, 0x72, 0x7e, 0x2c, 0xe5, 0x00, 0x8a, 0xe1, 0x71, 0x2b, 0x56, 0x4d, 0x35,
0xad, 0x9a, 0xa7, 0x43, 0x1a, 0x08, 0xea, 0xca, 0xd0, 0x32, 0xc2, 0x7e, 0xfe, 0xe5, 0x5f, 0x1b,
0xb9, 0x66, 0x21, 0x54, 0xad, 0x21, 0x9f, 0xcd, 0x80, 0xae, 0x66, 0x42, 0x6b, 0x88, 0x38, 0xb5,
0xb5, 0x1e, 0x47, 0xdd, 0xf7, 0x39, 0xef, 0x63, 0x6d, 0x96, 0x03, 0x6f, 0x5d, 0xf9, 0x65, 0x7a,
0xa4, 0xda, 0x72, 0x03, 0x07, 0xae, 0x17, 0x56, 0x09, 0x27, 0xfe, 0x84, 0x0e, 0x69, 0x3f, 0x6a,
0xb9, 0x75, 0x88, 0x33, 0x8d, 0x76, 0x31, 0xc4, 0x27, 0x50, 0x18, 0xa8, 0x1d, 0x15, 0x63, 0xa5,
0x51, 0x4e, 0xeb, 0x83, 0xf6, 0x8b, 0xca, 0xd7, 0x3e, 0xd6, 0x23, 0xa4, 0x3e, 0x94, 0x42, 0xea,
0x3e, 0xa4, 0xbe, 0x9f, 0x7d, 0x77, 0x4a, 0xb0, 0xd8, 0x0b, 0x06, 0xa3, 0x10, 0x25, 0x4d, 0x2f,
0xac, 0xfb, 0x58, 0x65, 0x3c, 0xd2, 0xf4, 0x54, 0x77, 0x68, 0x48, 0xa3, 0x53, 0x2d, 0xbf, 0x1b,
0xcf, 0x57, 0x61, 0x51, 0xd9, 0x93, 0x9f, 0x0d, 0x28, 0xa2, 0x4c, 0x91, 0x9d, 0x34, 0xf8, 0x19,
0xa2, 0x6d, 0xee, 0xce, 0x67, 0xac, 0x21, 0xac, 0xfa, 0xd7, 0x7f, 0xfc, 0xf3, 0xd3, 0xad, 0x1d,
0xb2, 0xe5, 0xa4, 0x3c, 0x12, 0x28, 0x5f, 0xce, 0x29, 0xd6, 0x79, 0x46, 0x7e, 0x37, 0x60, 0x2d,
0x21, 0xa3, 0xa4, 0x7e, 0x6d, 0xca, 0x59, 0xa2, 0x6d, 0x36, 0x6e, 0xe2, 0x82, 0xac, 0x1f, 0x2a,
0xd6, 0x06, 0x79, 0x90, 0xc6, 0x1a, 0x69, 0xf8, 0x15, 0xe4, 0xe7, 0x06, 0x14, 0x51, 0x36, 0x33,
0x9a, 0x99, 0xd4, 0xe5, 0x8c, 0x66, 0x5e, 0x52, 0x62, 0xab, 0xa1, 0x00, 0x77, 0xc9, 0x76, 0x1a,
0x20, 0x0a, 0xb3, 0x88, 0xa1, 0xfd, 0x6a, 0x40, 0x11, 0x25, 0x35, 0x03, 0x2d, 0xa9, 0xe2, 0x19,
0x68, 0x97, 0x54, 0xda, 0xfa, 0x40, 0xa1, 0xd5, 0x89, 0x93, 0x86, 0x26, 0xb4, 0xc3, 0x94, 0xcc,
0x39, 0x3d, 0x62, 0x27, 0x67, 0xe4, 0x7b, 0x03, 0xf2, 0x52, 0x8c, 0x49, 0x2d, 0x63, 0x62, 0x13,
0x9d, 0x37, 0xb7, 0xe6, 0xb0, 0x44, 0x2c, 0x47, 0x61, 0x6d, 0x91, 0x6a, 0xfa, 0x48, 0x3b, 0x89,
0x76, 0xfd, 0x68, 0x40, 0x41, 0xcb, 0x37, 0xd9, 0xbe, 0x36, 0x4d, 0xe2, 0x5d, 0x30, 0x77, 0xe6,
0xb2, 0x45, 0x28, 0x5b, 0x41, 0xd5, 0xc8, 0x66, 0x1a, 0x14, 0x4a, 0xac, 0x73, 0x2a, 0x05, 0x5e,
0x8d, 0x70, 0x79, 0x22, 0xc5, 0xe4, 0xfe, 0xf5, 0x47, 0xe6, 0xd2, 0x93, 0x61, 0xda, 0xf3, 0x9a,
0xcf, 0x7b, 0x61, 0xdb, 0xd2, 0x25, 0xc1, 0xf7, 0x8b, 0x01, 0x30, 0x55, 0x59, 0x32, 0x47, 0xc6,
0xb8, 0x50, 0x9b, 0xce, 0xdc, 0xf6, 0x88, 0xb8, 0xa3, 0x10, 0xef, 0x91, 0x77, 0xaf, 0x47, 0x54,
0xaa, 0x4e, 0xbe, 0x33, 0xa0, 0xa0, 0x35, 0x38, 0x63, 0xa0, 0x09, 0xd9, 0xcf, 0x18, 0x68, 0xf2,
0x31, 0xb0, 0x36, 0x15, 0x50, 0x85, 0x94, 0xd3, 0x80, 0xb4, 0xec, 0xab, 0x46, 0x4d, 0x85, 0x3a,
0xa3, 0x51, 0x57, 0xde, 0x86, 0x8c, 0x46, 0x5d, 0x7d, 0x01, 0xb2, 0x1b, 0x25, 0x94, 0x4f, 0xcb,
0xa5, 0xbe, 0xbf, 0xbf, 0xf7, 0xf2, 0xbc, 0x6c, 0xbc, 0x3a, 0x2f, 0x1b, 0x7f, 0x9f, 0x97, 0x8d,
0x1f, 0x2e, 0xca, 0xb9, 0x57, 0x17, 0xe5, 0xdc, 0x9f, 0x17, 0xe5, 0xdc, 0x57, 0x55, 0xaf, 0x17,
0x76, 0x47, 0x6d, 0xdb, 0xe5, 0x7d, 0x94, 0xc0, 0x58, 0xbc, 0x63, 0x15, 0x31, 0x3c, 0x19, 0x30,
0xd1, 0x2e, 0xa8, 0x7f, 0xfb, 0xef, 0xfd, 0x17, 0x00, 0x00, 0xff, 0xff, 0xc1, 0xa4, 0x09, 0x94,
0xad, 0x0c, 0x00, 0x00,
// 1026 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x96, 0xcf, 0x6f, 0xe3, 0x44,
0x14, 0xc7, 0xe3, 0x36, 0x4d, 0xda, 0xd7, 0x16, 0x2d, 0x43, 0x58, 0x8a, 0x17, 0xa5, 0x95, 0xd1,
0x36, 0xe9, 0x8f, 0xb5, 0x37, 0x41, 0xe2, 0x97, 0x90, 0x50, 0xbb, 0x52, 0x59, 0x89, 0x15, 0x5a,
0xd2, 0x3d, 0x71, 0x89, 0x26, 0xce, 0xc8, 0x89, 0xea, 0x78, 0xb2, 0x19, 0x27, 0x6a, 0x55, 0xf5,
0xc2, 0x01, 0x81, 0xc4, 0x01, 0xc4, 0x81, 0x15, 0x12, 0xd2, 0x5e, 0xb9, 0xf1, 0x67, 0xec, 0xb1,
0x12, 0x17, 0x4e, 0x08, 0xb5, 0x1c, 0xf8, 0x33, 0xd0, 0xcc, 0x3c, 0x27, 0x76, 0x1b, 0xd7, 0x29,
0x37, 0xcf, 0xe4, 0xfd, 0xf8, 0xbc, 0xf7, 0x66, 0xbe, 0x13, 0xb0, 0x58, 0xd8, 0x61, 0x83, 0x5e,
0x37, 0x08, 0x1d, 0x36, 0xea, 0x39, 0xa3, 0x1a, 0xf5, 0xfb, 0x1d, 0x5a, 0x73, 0x9e, 0x0f, 0xd9,
0xe0, 0xc4, 0xee, 0x0f, 0x78, 0xc8, 0xc9, 0xdd, 0xb1, 0x8d, 0xcd, 0x46, 0x3d, 0x3b, 0xb2, 0x31,
0x4b, 0x1e, 0xf7, 0xb8, 0x32, 0x71, 0xe4, 0x97, 0xb6, 0x36, 0xb7, 0x5d, 0x2e, 0x7a, 0x5c, 0x38,
0x2d, 0x2a, 0x98, 0x0e, 0xe3, 0x8c, 0x6a, 0x2d, 0x16, 0xd2, 0x9a, 0xd3, 0xa7, 0x5e, 0x37, 0xa0,
0x61, 0x97, 0x07, 0x68, 0xfb, 0x8e, 0xc7, 0xb9, 0xe7, 0x33, 0x87, 0xf6, 0xbb, 0x0e, 0x0d, 0x02,
0x1e, 0xaa, 0x1f, 0x05, 0xfe, 0xba, 0x91, 0xc2, 0x26, 0x21, 0x94, 0x85, 0xf5, 0x11, 0xbc, 0xf1,
0xa5, 0xcc, 0xb0, 0xe7, 0xba, 0x7c, 0x18, 0x84, 0x0d, 0xf6, 0x7c, 0xc8, 0x44, 0x48, 0xd6, 0xa0,
0x48, 0xdb, 0xed, 0x01, 0x13, 0x62, 0xcd, 0xd8, 0x30, 0xaa, 0x4b, 0x8d, 0x68, 0xf9, 0xf1, 0xe2,
0xb7, 0x2f, 0xd7, 0x73, 0xff, 0xbe, 0x5c, 0xcf, 0x59, 0x2e, 0x94, 0x92, 0xae, 0xa2, 0xcf, 0x03,
0xc1, 0xa4, 0x6f, 0x8b, 0xfa, 0x34, 0x70, 0x59, 0xe4, 0x8b, 0x4b, 0x72, 0x0f, 0x96, 0x5c, 0xde,
0x66, 0xcd, 0x0e, 0x15, 0x9d, 0xb5, 0x39, 0xf5, 0xdb, 0xa2, 0xdc, 0x78, 0x4c, 0x45, 0x87, 0x94,
0x60, 0x21, 0xe0, 0xd2, 0x69, 0x7e, 0xc3, 0xa8, 0xe6, 0x1b, 0x7a, 0x61, 0x7d, 0x0a, 0x6f, 0xab,
0x24, 0x8f, 0x54, 0x4b, 0xfe, 0x07, 0xe5, 0x37, 0x06, 0x98, 0xd3, 0x22, 0x20, 0xec, 0x7d, 0x78,
0x4d, 0x77, 0xbb, 0x99, 0x8c, 0xb4, 0xaa, 0x77, 0xf7, 0xf4, 0x26, 0x31, 0x61, 0x51, 0xc8, 0xa4,
0x92, 0x6f, 0x4e, 0xf1, 0x8d, 0xd7, 0x32, 0x04, 0xd5, 0x51, 0x9b, 0xc1, 0xb0, 0xd7, 0x62, 0x03,
0xac, 0x60, 0x15, 0x77, 0xbf, 0x50, 0x9b, 0xe3, 0x4e, 0xef, 0xeb, 0x66, 0xdc, 0xa6, 0x86, 0x87,
0xd8, 0xe9, 0xb1, 0x6b, 0x56, 0xa7, 0xad, 0xcf, 0x31, 0xd9, 0x61, 0xc8, 0x07, 0xd4, 0xcb, 0x4e,
0x46, 0xee, 0xc0, 0xfc, 0x11, 0x3b, 0xc1, 0xa1, 0xc8, 0xcf, 0x58, 0xfa, 0x5d, 0x4c, 0x3f, 0x0e,
0x86, 0xe9, 0x4b, 0xb0, 0x30, 0xa2, 0xfe, 0x30, 0x4a, 0xae, 0x17, 0xd6, 0xfb, 0x70, 0x07, 0xfb,
0xdd, 0xbe, 0x55, 0x91, 0x15, 0x78, 0x3d, 0xe6, 0x87, 0x29, 0x08, 0xe4, 0xe5, 0x01, 0x51, 0x5e,
0x2b, 0x0d, 0xf5, 0x6d, 0xd5, 0x81, 0x28, 0xc3, 0x67, 0xc7, 0x4f, 0xb8, 0x27, 0xa2, 0x14, 0x04,
0xf2, 0xea, 0x58, 0xe9, 0xf8, 0xea, 0x3b, 0x16, 0xfc, 0x00, 0xfb, 0x11, 0xf9, 0x60, 0x78, 0x07,
0xf2, 0x3e, 0xf7, 0x24, 0xd4, 0x7c, 0x75, 0xb9, 0x7e, 0xcf, 0x9e, 0x7e, 0x4d, 0xed, 0x27, 0xdc,
0x6b, 0x28, 0x43, 0xeb, 0x0c, 0xde, 0xd4, 0x93, 0xf0, 0xb9, 0x7b, 0x94, 0x91, 0x9e, 0x1c, 0x00,
0x4c, 0xee, 0xab, 0x6a, 0xed, 0x72, 0x7d, 0xd3, 0xd6, 0x07, 0xcb, 0x96, 0x97, 0xdb, 0xd6, 0x1a,
0x81, 0x97, 0xdb, 0x7e, 0x3a, 0x99, 0x54, 0x23, 0xe6, 0x19, 0x2b, 0xe3, 0x37, 0x03, 0xee, 0x5e,
0xcd, 0x8f, 0xa5, 0x1c, 0x40, 0x31, 0x3c, 0x6e, 0xc6, 0xaa, 0xa9, 0xa4, 0x55, 0xf3, 0x6c, 0x40,
0x03, 0x41, 0x5d, 0x19, 0x5a, 0x46, 0xd8, 0xcf, 0xbf, 0xfa, 0x6b, 0x3d, 0xd7, 0x28, 0x84, 0xaa,
0x35, 0xe4, 0xb3, 0x29, 0xd0, 0x95, 0x4c, 0x68, 0x0d, 0x11, 0xa7, 0xb6, 0xd6, 0xe2, 0xa8, 0xfb,
0x3e, 0xe7, 0x3d, 0xac, 0xcd, 0x72, 0xe0, 0xad, 0x6b, 0xbf, 0x4c, 0x8e, 0x54, 0x4b, 0x6e, 0xe0,
0xc0, 0xf5, 0xc2, 0x2a, 0xe1, 0xc4, 0x9f, 0xd2, 0x01, 0xed, 0x45, 0x2d, 0xb7, 0x0e, 0x71, 0xa6,
0xd1, 0x2e, 0x86, 0xf8, 0x04, 0x0a, 0x7d, 0xb5, 0xa3, 0x62, 0x2c, 0xd7, 0xcb, 0x69, 0x7d, 0xd0,
0x7e, 0x51, 0xf9, 0xda, 0xc7, 0x7a, 0x8c, 0xd4, 0x87, 0x52, 0x48, 0xdd, 0x47, 0xd4, 0xf7, 0xb3,
0xef, 0x4e, 0x09, 0x16, 0xba, 0x41, 0x7f, 0x18, 0xaa, 0x6e, 0xad, 0x34, 0xf4, 0xc2, 0x7a, 0x80,
0x55, 0xc6, 0x23, 0x4d, 0x4e, 0x75, 0x9b, 0x86, 0x34, 0x3a, 0xd5, 0xf2, 0xbb, 0xfe, 0x62, 0x05,
0x16, 0x94, 0x3d, 0xf9, 0xd9, 0x80, 0x22, 0xca, 0x14, 0xd9, 0x49, 0x83, 0x9f, 0x22, 0xda, 0xe6,
0xee, 0x6c, 0xc6, 0x1a, 0xc2, 0xaa, 0x7d, 0xfd, 0xc7, 0x3f, 0x3f, 0xcd, 0xed, 0x90, 0x2d, 0x27,
0xe5, 0x91, 0x40, 0xf9, 0x72, 0x4e, 0xb1, 0xce, 0x33, 0xf2, 0xbb, 0x01, 0xab, 0x09, 0x19, 0x25,
0xb5, 0x1b, 0x53, 0x4e, 0x13, 0x6d, 0xb3, 0x7e, 0x1b, 0x17, 0x64, 0xfd, 0x50, 0xb1, 0xd6, 0xc9,
0xc3, 0x34, 0xd6, 0x48, 0xc3, 0xaf, 0x21, 0xbf, 0x30, 0xa0, 0x88, 0xb2, 0x99, 0xd1, 0xcc, 0xa4,
0x2e, 0x67, 0x34, 0xf3, 0x8a, 0x12, 0x5b, 0x75, 0x05, 0xb8, 0x4b, 0xb6, 0xd3, 0x00, 0x51, 0x98,
0x45, 0x0c, 0xed, 0x57, 0x03, 0x8a, 0x28, 0xa9, 0x19, 0x68, 0x49, 0x15, 0xcf, 0x40, 0xbb, 0xa2,
0xd2, 0xd6, 0x07, 0x0a, 0xad, 0x46, 0x9c, 0x34, 0x34, 0xa1, 0x1d, 0x26, 0x64, 0xce, 0xe9, 0x11,
0x3b, 0x39, 0x23, 0xdf, 0x1b, 0x90, 0x97, 0x62, 0x4c, 0xaa, 0x19, 0x13, 0x1b, 0xeb, 0xbc, 0xb9,
0x35, 0x83, 0x25, 0x62, 0x39, 0x0a, 0x6b, 0x8b, 0x54, 0xd2, 0x47, 0xda, 0x4e, 0xb4, 0xeb, 0x47,
0x03, 0x0a, 0x5a, 0xbe, 0xc9, 0xf6, 0x8d, 0x69, 0x12, 0xef, 0x82, 0xb9, 0x33, 0x93, 0x2d, 0x42,
0xd9, 0x0a, 0xaa, 0x4a, 0x36, 0xd3, 0xa0, 0x50, 0x62, 0x9d, 0x53, 0x29, 0xf0, 0x6a, 0x84, 0x4b,
0x63, 0x29, 0x26, 0x0f, 0x6e, 0x3e, 0x32, 0x57, 0x9e, 0x0c, 0xd3, 0x9e, 0xd5, 0x7c, 0xd6, 0x0b,
0xdb, 0x92, 0x2e, 0x09, 0xbe, 0x5f, 0x0c, 0x80, 0x89, 0xca, 0x92, 0x19, 0x32, 0xc6, 0x85, 0xda,
0x74, 0x66, 0xb6, 0x47, 0xc4, 0x1d, 0x85, 0x78, 0x9f, 0xbc, 0x7b, 0x33, 0xa2, 0x52, 0x75, 0xf2,
0x9d, 0x01, 0x05, 0xad, 0xc1, 0x19, 0x03, 0x4d, 0xc8, 0x7e, 0xc6, 0x40, 0x93, 0x8f, 0x81, 0xb5,
0xa9, 0x80, 0x36, 0x48, 0x39, 0x0d, 0x48, 0xcb, 0xbe, 0x6a, 0xd4, 0x44, 0xa8, 0x33, 0x1a, 0x75,
0xed, 0x6d, 0xc8, 0x68, 0xd4, 0xf5, 0x17, 0x20, 0xbb, 0x51, 0x42, 0xf9, 0x34, 0x5d, 0xea, 0xfb,
0xfb, 0x7b, 0xaf, 0x2e, 0xca, 0xc6, 0xf9, 0x45, 0xd9, 0xf8, 0xfb, 0xa2, 0x6c, 0xfc, 0x70, 0x59,
0xce, 0x9d, 0x5f, 0x96, 0x73, 0x7f, 0x5e, 0x96, 0x73, 0x5f, 0x55, 0xbc, 0x6e, 0xd8, 0x19, 0xb6,
0x6c, 0x97, 0xf7, 0x50, 0x02, 0x63, 0xf1, 0x8e, 0x55, 0xc4, 0xf0, 0xa4, 0xcf, 0x44, 0xab, 0xa0,
0xfe, 0xed, 0xbf, 0xf7, 0x5f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x4b, 0xf0, 0x51, 0x91, 0xad, 0x0c,
0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
@ -2579,7 +2579,7 @@ func (m *QueryAccountResponse) Unmarshal(dAtA []byte) error {
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field CodeHash", wireType)
}
var byteLen int
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowQuery
@ -2589,25 +2589,23 @@ func (m *QueryAccountResponse) Unmarshal(dAtA []byte) error {
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= int(b&0x7F) << shift
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthQuery
}
postIndex := iNdEx + byteLen
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthQuery
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.CodeHash = append(m.CodeHash[:0], dAtA[iNdEx:postIndex]...)
if m.CodeHash == nil {
m.CodeHash = []byte{}
}
m.CodeHash = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 3:
if wireType != 0 {

View File

@ -1,457 +0,0 @@
package types
import (
"bytes"
"fmt"
"io"
"math/big"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
ethermint "github.com/cosmos/ethermint/types"
ethcmn "github.com/ethereum/go-ethereum/common"
ethstate "github.com/ethereum/go-ethereum/core/state"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
)
var (
_ StateObject = (*stateObject)(nil)
EmptyCodeHash = ethcrypto.Keccak256(nil)
)
// StateObject interface for interacting with state object
type StateObject interface {
GetCommittedState(db ethstate.Database, key ethcmn.Hash) ethcmn.Hash
GetState(db ethstate.Database, key ethcmn.Hash) ethcmn.Hash
SetState(db ethstate.Database, key, value ethcmn.Hash)
Code(db ethstate.Database) []byte
SetCode(codeHash ethcmn.Hash, code []byte)
CodeHash() []byte
AddBalance(amount *big.Int)
SubBalance(amount *big.Int)
SetBalance(amount *big.Int)
Balance() *big.Int
ReturnGas(gas *big.Int)
Address() ethcmn.Address
SetNonce(nonce uint64)
Nonce() uint64
}
// stateObject represents an Ethereum account which is being modified.
//
// The usage pattern is as follows:
// First you need to obtain a state object.
// 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 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
// by StateDB.Commit.
originStorage Storage // Storage cache of original entries to dedup rewrites
dirtyStorage Storage // Storage entries that need to be flushed to disk
// DB error
dbErr error
stateDB *CommitStateDB
account *ethermint.EthAccount
// balance represents the amount of the EVM denom token that an account holds
balance sdk.Int
keyToOriginStorageIndex map[ethcmn.Hash]int
keyToDirtyStorageIndex map[ethcmn.Hash]int
address ethcmn.Address
// cache flags
//
// When an object is marked suicided it will be delete from the trie during
// the "update" phase of the state transition.
dirtyCode bool // true if the code was updated
suicided bool
deleted bool
}
func newStateObject(db *CommitStateDB, accProto authtypes.AccountI, balance sdk.Int) *stateObject {
ethAccount, ok := accProto.(*ethermint.EthAccount)
if !ok {
panic(fmt.Sprintf("invalid account type for state object: %T", accProto))
}
// set empty code hash
if ethAccount.CodeHash == nil {
ethAccount.CodeHash = EmptyCodeHash
}
return &stateObject{
stateDB: db,
account: ethAccount,
balance: balance,
address: ethAccount.EthAddress(),
originStorage: Storage{},
dirtyStorage: Storage{},
keyToOriginStorageIndex: make(map[ethcmn.Hash]int),
keyToDirtyStorageIndex: make(map[ethcmn.Hash]int),
}
}
// ----------------------------------------------------------------------------
// Setters
// ----------------------------------------------------------------------------
// SetState updates a value in account storage. Note, the key will be prefixed
// with the address of the state object.
func (so *stateObject) SetState(db ethstate.Database, key, value ethcmn.Hash) {
// if the new value is the same as old, don't set
prev := so.GetState(db, key)
if prev == value {
return
}
prefixKey := so.GetStorageByAddressKey(key.Bytes())
// since the new value is different, update and journal the change
so.stateDB.journal.append(storageChange{
account: &so.address,
key: prefixKey,
prevValue: prev,
})
so.setState(prefixKey, value)
}
// setState sets a state with a prefixed key and value to the dirty storage.
func (so *stateObject) setState(key, value ethcmn.Hash) {
idx, ok := so.keyToDirtyStorageIndex[key]
if ok {
so.dirtyStorage[idx].Value = value.String()
return
}
// create new entry
so.dirtyStorage = append(so.dirtyStorage, NewState(key, value))
idx = len(so.dirtyStorage) - 1
so.keyToDirtyStorageIndex[key] = idx
}
// SetCode sets the state object's code.
func (so *stateObject) SetCode(codeHash ethcmn.Hash, code []byte) {
prevCode := so.Code(nil)
so.stateDB.journal.append(codeChange{
account: &so.address,
prevHash: so.CodeHash(),
prevCode: prevCode,
})
so.setCode(codeHash, code)
}
func (so *stateObject) setCode(codeHash ethcmn.Hash, code []byte) {
so.code = code
so.account.CodeHash = codeHash.Bytes()
so.dirtyCode = true
}
// AddBalance adds an amount to a state object's balance. It is used to add
// funds to the destination account of a transfer.
func (so *stateObject) AddBalance(amount *big.Int) {
amt := sdk.NewIntFromBigInt(amount)
// EIP158: We must check emptiness for the objects such that the account
// clearing (0,0,0 objects) can take effect.
// NOTE: this will panic if amount is nil
if amt.IsZero() {
if so.empty() {
so.touch()
}
return
}
newBalance := so.balance.Add(amt)
so.SetBalance(newBalance.BigInt())
}
// SubBalance removes an amount from the stateObject's balance. It is used to
// remove funds from the origin account of a transfer.
func (so *stateObject) SubBalance(amount *big.Int) {
amt := sdk.NewIntFromBigInt(amount)
if amt.IsZero() {
return
}
newBalance := so.balance.Sub(amt)
so.SetBalance(newBalance.BigInt())
}
// SetBalance sets the state object's balance. It doesn't perform any validation
// on the amount value.
func (so *stateObject) SetBalance(amount *big.Int) {
amt := sdk.NewIntFromBigInt(amount)
so.stateDB.journal.append(balanceChange{
account: &so.address,
prev: so.balance,
})
so.setBalance(amt)
}
func (so *stateObject) setBalance(amount sdk.Int) {
so.balance = amount
}
// SetNonce sets the state object's nonce (i.e sequence number of the account).
func (so *stateObject) SetNonce(nonce uint64) {
so.stateDB.journal.append(nonceChange{
account: &so.address,
prev: so.account.Sequence,
})
so.setNonce(nonce)
}
func (so *stateObject) setNonce(nonce uint64) {
if so.account == nil {
panic("state object account is empty")
}
so.account.Sequence = nonce
}
// setError remembers the first non-nil error it is called with.
func (so *stateObject) setError(err error) {
if so.dbErr == nil {
so.dbErr = err
}
}
func (so *stateObject) markSuicided() {
so.suicided = true
}
// commitState commits all dirty storage to a KVStore and resets
// the dirty storage slice to the empty state.
func (so *stateObject) commitState() {
ctx := so.stateDB.ctx
store := prefix.NewStore(ctx.KVStore(so.stateDB.storeKey), AddressStoragePrefix(so.Address()))
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 ethermint.IsEmptyHash(state.Value) {
store.Delete(key.Bytes())
}
delete(so.keyToDirtyStorageIndex, key)
// skip no-op changes, persist actual changes
idx, ok := so.keyToOriginStorageIndex[key]
if !ok {
continue
}
if ethermint.IsEmptyHash(state.Value) {
delete(so.keyToOriginStorageIndex, key)
continue
}
if state.Value == so.originStorage[idx].Value {
continue
}
so.originStorage[idx].Value = state.Value
store.Set(key.Bytes(), value.Bytes())
}
// clean storage as all entries are dirty
so.dirtyStorage = Storage{}
}
// commitCode persists the state object's code to the KVStore.
func (so *stateObject) commitCode() {
ctx := so.stateDB.ctx
store := prefix.NewStore(ctx.KVStore(so.stateDB.storeKey), KeyPrefixCode)
store.Set(so.CodeHash(), so.code)
}
// ----------------------------------------------------------------------------
// Getters
// ----------------------------------------------------------------------------
// Address returns the address of the state object.
func (so stateObject) Address() ethcmn.Address {
return so.address
}
// Balance returns the state object's current balance.
func (so *stateObject) Balance() *big.Int {
balance := so.balance.BigInt()
if balance == nil {
return zeroBalance
}
return balance
}
// CodeHash returns the state object's code hash.
func (so *stateObject) CodeHash() []byte {
if so.account == nil || len(so.account.CodeHash) == 0 {
return EmptyCodeHash
}
return so.account.CodeHash
}
// Nonce returns the state object's current nonce (sequence number).
func (so *stateObject) Nonce() uint64 {
if so.account == nil {
return 0
}
return so.account.Sequence
}
// Code returns the contract code associated with this object, if any.
func (so *stateObject) Code(_ ethstate.Database) []byte {
if len(so.code) > 0 {
return so.code
}
if bytes.Equal(so.CodeHash(), EmptyCodeHash) {
return nil
}
ctx := so.stateDB.ctx
store := prefix.NewStore(ctx.KVStore(so.stateDB.storeKey), KeyPrefixCode)
code := store.Get(so.CodeHash())
if len(code) == 0 {
so.setError(fmt.Errorf("failed to get code hash %x for address %s", so.CodeHash(), so.Address().String()))
}
return code
}
// GetState retrieves a value from the account storage trie. Note, the key will
// be prefixed with the address of the state object.
func (so *stateObject) GetState(db ethstate.Database, key ethcmn.Hash) ethcmn.Hash {
prefixKey := so.GetStorageByAddressKey(key.Bytes())
// if we have a dirty value for this state entry, return it
idx, dirty := so.keyToDirtyStorageIndex[prefixKey]
if dirty {
value := ethcmn.HexToHash(so.dirtyStorage[idx].Value)
return value
}
// otherwise return the entry's original value
value := so.GetCommittedState(db, key)
return value
}
// GetCommittedState retrieves a value from the committed account storage trie.
//
// NOTE: the key will be prefixed with the address of the state object.
func (so *stateObject) GetCommittedState(_ ethstate.Database, key ethcmn.Hash) ethcmn.Hash {
prefixKey := so.GetStorageByAddressKey(key.Bytes())
// if we have the original value cached, return that
idx, cached := so.keyToOriginStorageIndex[prefixKey]
if cached {
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 {
value.SetBytes(rawValue)
state.Value = value.String()
}
so.originStorage = append(so.originStorage, state)
so.keyToOriginStorageIndex[prefixKey] = len(so.originStorage) - 1
return value
}
// ----------------------------------------------------------------------------
// Auxiliary
// ----------------------------------------------------------------------------
// ReturnGas returns the gas back to the origin. Used by the Virtual machine or
// Closures. It performs a no-op.
func (so *stateObject) ReturnGas(gas *big.Int) {}
func (so *stateObject) deepCopy(db *CommitStateDB) *stateObject {
newStateObj := newStateObject(db, so.account, so.balance)
newStateObj.code = so.code
newStateObj.dirtyStorage = so.dirtyStorage.Copy()
newStateObj.originStorage = so.originStorage.Copy()
newStateObj.suicided = so.suicided
newStateObj.dirtyCode = so.dirtyCode
newStateObj.deleted = so.deleted
return newStateObj
}
// empty returns whether the account is considered empty.
func (so *stateObject) empty() bool {
return so.account == nil ||
(so.account != nil &&
so.account.Sequence == 0 &&
(so.balance.BigInt() == nil || so.balance.IsZero()) &&
bytes.Equal(so.account.CodeHash, EmptyCodeHash))
}
// EncodeRLP implements rlp.Encoder.
func (so *stateObject) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, so.account)
}
func (so *stateObject) touch() {
so.stateDB.journal.append(touchChange{
account: &so.address,
})
if so.address == ripemd {
// Explicitly put it in the dirty-cache, which is otherwise generated from
// flattened journals.
so.stateDB.journal.dirty(so.address)
}
}
// GetStorageByAddressKey returns a hash of the composite key for a state
// object's storage prefixed with it's address.
func (so stateObject) GetStorageByAddressKey(key []byte) ethcmn.Hash {
prefix := so.Address().Bytes()
compositeKey := make([]byte, len(prefix)+len(key))
copy(compositeKey, prefix)
copy(compositeKey[len(prefix):], key)
return ethcrypto.Keccak256Hash(compositeKey)
}
// stateEntry represents a single key value pair from the StateDB's stateObject mappindg.
// This is to prevent non determinism at genesis initialization or export.
type stateEntry struct {
// address key of the state object
address ethcmn.Address
stateObject *stateObject
}

View File

@ -1,135 +0,0 @@
package types_test
import (
"math/big"
ethcmn "github.com/ethereum/go-ethereum/common"
)
func (suite *StateDBTestSuite) TestStateObject_State() {
testCase := []struct {
name string
key ethcmn.Hash
expValue ethcmn.Hash
malleate func()
}{
{
"no set value, load from KVStore",
ethcmn.BytesToHash([]byte("key")),
ethcmn.Hash{},
func() {},
},
{
"no-op SetState",
ethcmn.BytesToHash([]byte("key")),
ethcmn.Hash{},
func() {
suite.stateObject.SetState(nil, ethcmn.BytesToHash([]byte("key")), ethcmn.Hash{})
},
},
{
"cached value",
ethcmn.BytesToHash([]byte("key1")),
ethcmn.BytesToHash([]byte("value1")),
func() {
suite.stateObject.SetState(nil, ethcmn.BytesToHash([]byte("key1")), ethcmn.BytesToHash([]byte("value1")))
},
},
{
"update value",
ethcmn.BytesToHash([]byte("key1")),
ethcmn.BytesToHash([]byte("value2")),
func() {
suite.stateObject.SetState(nil, ethcmn.BytesToHash([]byte("key1")), ethcmn.BytesToHash([]byte("value2")))
},
},
{
"update various keys",
ethcmn.BytesToHash([]byte("key1")),
ethcmn.BytesToHash([]byte("value1")),
func() {
suite.stateObject.SetState(nil, ethcmn.BytesToHash([]byte("key1")), ethcmn.BytesToHash([]byte("value1")))
suite.stateObject.SetState(nil, ethcmn.BytesToHash([]byte("key2")), ethcmn.BytesToHash([]byte("value2")))
suite.stateObject.SetState(nil, ethcmn.BytesToHash([]byte("key3")), ethcmn.BytesToHash([]byte("value3")))
},
},
}
for _, tc := range testCase {
tc.malleate()
value := suite.stateObject.GetState(nil, tc.key)
suite.Require().Equal(tc.expValue, value, tc.name)
}
}
func (suite *StateDBTestSuite) TestStateObject_AddBalance() {
testCase := []struct {
name string
amount *big.Int
expBalance *big.Int
}{
{"zero amount", big.NewInt(0), big.NewInt(0)},
{"positive amount", big.NewInt(10), big.NewInt(10)},
{"negative amount", big.NewInt(-1), big.NewInt(9)},
}
for _, tc := range testCase {
suite.stateObject.AddBalance(tc.amount)
suite.Require().Equal(tc.expBalance, suite.stateObject.Balance(), tc.name)
}
}
func (suite *StateDBTestSuite) TestStateObject_SubBalance() {
testCase := []struct {
name string
amount *big.Int
expBalance *big.Int
}{
{"zero amount", big.NewInt(0), big.NewInt(0)},
{"negative amount", big.NewInt(-10), big.NewInt(10)},
{"positive amount", big.NewInt(1), big.NewInt(9)},
}
for _, tc := range testCase {
suite.stateObject.SubBalance(tc.amount)
suite.Require().Equal(tc.expBalance, suite.stateObject.Balance(), tc.name)
}
}
func (suite *StateDBTestSuite) TestStateObject_Code() {
testCase := []struct {
name string
expCode []byte
malleate func()
}{
{
"cached code",
[]byte("code"),
func() {
suite.stateObject.SetCode(ethcmn.BytesToHash([]byte("code_hash")), []byte("code"))
},
},
{
"empty code hash",
nil,
func() {
suite.stateObject.SetCode(ethcmn.Hash{}, nil)
},
},
{
"empty code",
nil,
func() {
suite.stateObject.SetCode(ethcmn.BytesToHash([]byte("code_hash")), nil)
},
},
}
for _, tc := range testCase {
tc.malleate()
code := suite.stateObject.Code(nil)
suite.Require().Equal(tc.expCode, code, tc.name)
}
}

View File

@ -1,340 +0,0 @@
package types
import (
"math/big"
"os"
"time"
"github.com/pkg/errors"
log "github.com/xlab/suplog"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
tmtypes "github.com/tendermint/tendermint/types"
"github.com/cosmos/cosmos-sdk/telemetry"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
// StateTransition defines data to transitionDB in evm
type StateTransition struct {
// TxData fields
Message core.Message
ChainID *big.Int
Csdb *CommitStateDB
TxHash *common.Hash
Simulate bool // i.e CheckTx execution
Debug bool // enable EVM debugging
}
// GasInfo returns the gas limit, gas consumed and gas refunded from the EVM transition
// execution
type GasInfo struct {
GasLimit uint64
GasConsumed uint64
GasRefunded uint64
}
// ExecutionResult represents what's returned from a transition
type ExecutionResult struct {
Logs []*ethtypes.Log
Bloom *big.Int
Response *MsgEthereumTxResponse
GasInfo GasInfo
}
// GetHashFn implements vm.GetHashFunc for Ethermint. It handles 3 cases:
// 1. The requested height matches the current height from context (and thus same epoch number)
// 2. The requested height is from an previous height from the same chain epoch
// 3. The requested height is from a height greater than the latest one
func GetHashFn(ctx sdk.Context, csdb *CommitStateDB) vm.GetHashFunc {
return func(height uint64) common.Hash {
switch {
case ctx.BlockHeight() == int64(height):
// Case 1: The requested height matches the one from the context so we can retrieve the header
// hash directly from the context.
return csdb.bhash
case ctx.BlockHeight() > int64(height):
// Case 2: if the chain is not the current height we need to retrieve the hash from the store for the
// current chain epoch. This only applies if the current height is greater than the requested height.
return csdb.WithContext(ctx).GetHeightHash(height)
default:
// Case 3: heights greater than the current one returns an empty hash.
return common.Hash{}
}
}
}
func (st *StateTransition) newEVM(
ctx sdk.Context,
csdb *CommitStateDB,
gasLimit uint64,
config ChainConfig,
extraEIPs []int64,
) *vm.EVM {
// Create context for evm
blockCtx := vm.BlockContext{
CanTransfer: core.CanTransfer,
Transfer: core.Transfer,
GetHash: GetHashFn(ctx, csdb),
Coinbase: common.Address{}, // there's no beneficiary since we're not mining
BlockNumber: big.NewInt(ctx.BlockHeight()),
Time: big.NewInt(ctx.BlockHeader().Time.Unix()),
Difficulty: big.NewInt(0), // unused. Only required in PoW context
GasLimit: gasLimit,
}
txCtx := core.NewEVMTxContext(st.Message)
eips := make([]int, len(extraEIPs))
for i, eip := range extraEIPs {
eips[i] = int(eip)
}
vmConfig := vm.Config{
EnablePreimageRecording: false, // no need for StateDB.AddPreimage
ExtraEips: eips,
}
if st.Debug {
vmConfig.Tracer = vm.NewJSONLogger(&vm.LogConfig{
Debug: true,
}, os.Stderr)
vmConfig.Debug = true
}
return vm.NewEVM(blockCtx, txCtx, csdb, config.EthereumConfig(st.ChainID), vmConfig)
}
// TransitionDb will transition the state by applying the current transaction and
// returning the evm execution result.
// NOTE: State transition checks are run during AnteHandler execution.
func (st *StateTransition) TransitionDb(ctx sdk.Context, config ChainConfig) (resp *ExecutionResult, err error) {
defer telemetry.ModuleMeasureSince(ModuleName, time.Now(), MetricKeyTransitionDB)
contractCreation := st.Message.To() == nil
cost, err := core.IntrinsicGas(st.Message.Data(), st.Message.AccessList(), true, false, true)
if err != nil {
err = sdkerrors.Wrap(err, "invalid intrinsic gas for transaction")
return nil, err
}
// This gas limit the the transaction gas limit with intrinsic gas subtracted
gasLimit := st.Message.Gas() - ctx.GasMeter().GasConsumed()
csdb := st.Csdb.WithContext(ctx)
if st.Simulate {
// gasLimit is set here because stdTxs incur gaskv charges in the ante handler, but for eth_call
// the cost needs to be the same as an Ethereum transaction sent through the web3 API
consumedGas := ctx.GasMeter().GasConsumed()
gasLimit = st.Message.Gas() - cost
if consumedGas < cost {
// If Cosmos standard tx ante handler cost is less than EVM intrinsic cost
// gas must be consumed to match to accurately simulate an Ethereum transaction
ctx.GasMeter().ConsumeGas(cost-consumedGas, "Intrinsic gas match")
}
csdb = st.Csdb.Copy()
}
// This gas meter is set up to consume gas from gaskv during evm execution and be ignored
currentGasMeter := ctx.GasMeter()
evmGasMeter := sdk.NewInfiniteGasMeter()
csdb.WithContext(ctx.WithGasMeter(evmGasMeter))
// Clear cache of accounts to handle changes outside of the EVM
csdb.UpdateAccounts()
params := csdb.GetParams()
gasPrice := ctx.MinGasPrices().AmountOf(params.EvmDenom)
//gasPrice := sdk.ZeroDec()
if gasPrice.IsNil() {
return nil, errors.New("min gas price cannot be nil")
}
evm := st.newEVM(ctx, csdb, gasLimit, config, params.ExtraEIPs)
var (
ret []byte
leftOverGas uint64
contractAddress common.Address
senderRef = vm.AccountRef(st.Message.From())
)
// Get nonce of account outside of the EVM
currentNonce := csdb.GetNonce(st.Message.From())
// Set nonce of sender account before evm state transition for usage in generating Create address
csdb.SetNonce(st.Message.From(), st.Message.Nonce())
// create contract or execute call
switch contractCreation {
case true:
if !params.EnableCreate {
return nil, ErrCreateDisabled
}
ret, contractAddress, leftOverGas, err = evm.Create(senderRef, st.Message.Data(), gasLimit, st.Message.Value())
if err != nil {
log.WithField("simulate", st.Simulate).
WithField("nonce", st.Message.Nonce()).
WithField("contract", contractAddress.String()).
WithError(err).Warningln("evm contract creation failed")
}
gasConsumed := gasLimit - leftOverGas
resp = &ExecutionResult{
Response: &MsgEthereumTxResponse{
Ret: ret,
},
GasInfo: GasInfo{
GasConsumed: gasConsumed,
GasLimit: gasLimit,
GasRefunded: leftOverGas,
},
}
default:
if !params.EnableCall {
return nil, ErrCallDisabled
}
// Increment the nonce for the next transaction (just for evm state transition)
csdb.SetNonce(st.Message.From(), csdb.GetNonce(st.Message.From())+1)
ret, leftOverGas, err = evm.Call(senderRef, *st.Message.To(), st.Message.Data(), gasLimit, st.Message.Value())
// fmt.Println("EVM CALL!!!", senderRef.Address().Hex(), (*st.Message.To()).Hex(), gasLimit)
// fmt.Println("EVM CALL RESULT", common.ToHex(ret), leftOverGas, err)
if err != nil {
log.WithField("recipient", st.Message.To().String()).
WithError(err).Debugln("evm call failed")
}
gasConsumed := gasLimit - leftOverGas
resp = &ExecutionResult{
Response: &MsgEthereumTxResponse{
Ret: ret,
},
GasInfo: GasInfo{
GasConsumed: gasConsumed,
GasLimit: gasLimit,
GasRefunded: leftOverGas,
},
}
}
if err != nil {
// Consume gas before returning
ctx.GasMeter().ConsumeGas(resp.GasInfo.GasConsumed, "evm execution consumption")
return resp, err
}
// Resets nonce to value pre state transition
csdb.SetNonce(st.Message.From(), currentNonce)
var (
logs []*ethtypes.Log
)
if st.TxHash != nil && !st.Simulate {
logs, err = csdb.GetLogs(*st.TxHash)
if err != nil {
err = errors.Wrap(err, "failed to get logs")
return nil, err
}
}
if !st.Simulate {
// Finalise state if not a simulated transaction
// TODO: change to depend on config
if err := csdb.Finalise(true); err != nil {
return nil, err
}
}
resp.Logs = logs
resp.Response = &MsgEthereumTxResponse{
Logs: NewLogsFromEth(logs),
Ret: ret,
}
// TODO: Refund unused gas here, if intended in future
// Consume gas from evm execution
// Out of gas check does not need to be done here since it is done within the EVM execution
// TODO: @albert, @maxim, decide if can take this out, since InternalEthereumTx may want to continue execution afterwards
// which will use gas.
_ = currentGasMeter
//ctx.WithGasMeter(currentGasMeter).GasMeter().ConsumeGas(resp.GasInfo.GasConsumed, "EVM execution consumption")
return resp, nil
}
// StaticCall executes the contract associated with the addr with the given input
// as parameters while disallowing any modifications to the state during the call.
// Opcodes that attempt to perform such modifications will result in exceptions
// instead of performing the modifications.
func (st *StateTransition) StaticCall(ctx sdk.Context, config ChainConfig) ([]byte, error) {
defer telemetry.ModuleMeasureSince(ModuleName, time.Now(), MetricKeyStaticCall)
// This gas limit the the transaction gas limit with intrinsic gas subtracted
gasLimit := st.Message.Gas() - ctx.GasMeter().GasConsumed()
csdb := st.Csdb.WithContext(ctx)
// This gas meter is set up to consume gas from gaskv during evm execution and be ignored
evmGasMeter := sdk.NewInfiniteGasMeter()
csdb.WithContext(ctx.WithGasMeter(evmGasMeter))
// Clear cache of accounts to handle changes outside of the EVM
csdb.UpdateAccounts()
params := csdb.GetParams()
gasPrice := ctx.MinGasPrices().AmountOf(params.EvmDenom)
if gasPrice.IsNil() {
return []byte{}, errors.New("min gas price cannot be nil")
}
evm := st.newEVM(ctx, csdb, gasLimit, config, params.ExtraEIPs)
senderRef := vm.AccountRef(st.Message.From())
ret, _, err := evm.StaticCall(senderRef, *st.Message.To(), st.Message.Data(), gasLimit)
// fmt.Println("EVM STATIC CALL!!!", senderRef.Address().Hex(), (*st.Message.To()).Hex(), st.Message.Data(), gasLimit)
// fmt.Println("EVM STATIC CALL RESULT", common.ToHex(ret), leftOverGas, err)
return ret, err
}
// HashFromContext returns the Ethereum Header hash from the context's Tendermint
// block header.
func HashFromContext(ctx sdk.Context) common.Hash {
// cast the ABCI header to tendermint Header type
protoHeader := ctx.BlockHeader()
tmHeader, err := tmtypes.HeaderFromProto(&protoHeader)
if err != nil {
return common.Hash{}
}
// get the Tendermint block hash from the current header
tmBlockHash := tmHeader.Hash()
// NOTE: if the validator set hash is missing the hash will be returned as nil,
// so we need to check for this case to prevent a panic when calling Bytes()
if tmBlockHash == nil {
return common.Hash{}
}
return common.BytesToHash(tmBlockHash.Bytes())
}

View File

@ -1,190 +0,0 @@
package types_test
import (
"math/big"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/ethermint/crypto/ethsecp256k1"
ethermint "github.com/cosmos/ethermint/types"
"github.com/cosmos/ethermint/x/evm/types"
ethcmn "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
)
func (suite *StateDBTestSuite) TestTransitionDb() {
suite.stateDB.SetNonce(suite.address, 123)
addr := sdk.AccAddress(suite.address.Bytes())
balance := ethermint.NewPhotonCoin(sdk.NewInt(5000))
acc := suite.app.AccountKeeper.GetAccount(suite.ctx, addr)
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
suite.app.BankKeeper.SetBalance(suite.ctx, addr, balance)
priv, err := ethsecp256k1.GenerateKey()
suite.Require().NoError(err)
recipient := ethcrypto.PubkeyToAddress(priv.ToECDSA().PublicKey)
testCase := []struct {
name string
malleate func()
state types.StateTransition
expPass bool
}{
{
"passing state transition",
func() {},
types.StateTransition{
Message: ethtypes.NewMessage(
suite.address,
&recipient,
123,
big.NewInt(50),
11,
big.NewInt(10),
[]byte("data"),
nil,
true,
),
ChainID: big.NewInt(1),
Csdb: suite.stateDB,
TxHash: &ethcmn.Hash{},
Simulate: suite.ctx.IsCheckTx(),
},
true,
},
{
"contract creation",
func() {},
types.StateTransition{
Message: ethtypes.NewMessage(
suite.address,
nil,
123,
big.NewInt(50),
11,
big.NewInt(10),
[]byte("data"),
nil,
true,
),
ChainID: big.NewInt(1),
Csdb: suite.stateDB,
TxHash: &ethcmn.Hash{},
Simulate: true,
},
true,
},
{
"state transition simulation",
func() {},
types.StateTransition{
Message: ethtypes.NewMessage(
suite.address,
&recipient,
123,
big.NewInt(50),
11,
big.NewInt(10),
[]byte("data"),
nil,
true,
),
ChainID: big.NewInt(1),
Csdb: suite.stateDB,
TxHash: &ethcmn.Hash{},
Simulate: true,
},
true,
},
{
"fail by sending more than balance",
func() {},
types.StateTransition{
Message: ethtypes.NewMessage(
suite.address,
&recipient,
123,
big.NewInt(50000000),
11,
big.NewInt(10),
[]byte("data"),
nil,
true,
),
ChainID: big.NewInt(1),
Csdb: suite.stateDB,
TxHash: &ethcmn.Hash{},
Simulate: suite.ctx.IsCheckTx(),
},
false,
},
{
"failed to Finalize",
func() {},
types.StateTransition{
Message: ethtypes.NewMessage(
suite.address,
&recipient,
123,
big.NewInt(-5000),
11,
big.NewInt(10),
[]byte("data"),
nil,
true,
),
ChainID: big.NewInt(1),
Csdb: suite.stateDB,
TxHash: &ethcmn.Hash{},
Simulate: false,
},
false,
},
{
"nil gas price",
func() {
invalidGas := sdk.DecCoins{
{Denom: ethermint.AttoPhoton},
}
suite.ctx = suite.ctx.WithMinGasPrices(invalidGas)
},
types.StateTransition{
Message: ethtypes.NewMessage(
suite.address,
&recipient,
123,
big.NewInt(50),
11,
nil,
[]byte("data"),
nil,
true,
),
ChainID: big.NewInt(1),
Csdb: suite.stateDB,
TxHash: &ethcmn.Hash{},
Simulate: suite.ctx.IsCheckTx(),
},
false,
},
}
for _, tc := range testCase {
tc.malleate()
_, err = tc.state.TransitionDb(suite.ctx, types.DefaultChainConfig())
if tc.expPass {
suite.Require().NoError(err, tc.name)
fromBalance := suite.app.EvmKeeper.CommitStateDB.GetBalance(suite.address)
toBalance := suite.app.EvmKeeper.CommitStateDB.GetBalance(recipient)
suite.Require().Equal(fromBalance, big.NewInt(4950), tc.name)
suite.Require().Equal(toBalance, big.NewInt(50), tc.name)
} else {
suite.Require().Error(err, tc.name)
}
}
}

View File

@ -1,990 +0,0 @@
package types
import (
"fmt"
"math/big"
"sort"
"sync"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
ethermint "github.com/cosmos/ethermint/types"
ethcmn "github.com/ethereum/go-ethereum/common"
ethstate "github.com/ethereum/go-ethereum/core/state"
ethtypes "github.com/ethereum/go-ethereum/core/types"
ethvm "github.com/ethereum/go-ethereum/core/vm"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
)
var (
_ ethvm.StateDB = (*CommitStateDB)(nil)
zeroBalance = sdk.ZeroInt().BigInt()
)
// CommitStateDB implements the Geth state.StateDB interface. Instead of using
// a trie and database for querying and persistence, the Keeper uses KVStores
// and an AccountKeeper to facilitate state transitions.
//
// TODO: This implementation is subject to change in regards to its statefull
// manner. In otherwords, how this relates to the keeper in this module.
type CommitStateDB struct {
// TODO: We need to store the context as part of the structure itself opposed
// to being passed as a parameter (as it should be) in order to implement the
// StateDB interface. Perhaps there is a better way.
ctx sdk.Context
storeKey sdk.StoreKey
transientKey sdk.StoreKey
paramSpace paramtypes.Subspace
accountKeeper AccountKeeper
bankKeeper BankKeeper
// array that hold 'live' objects, which will get modified while processing a
// state transition
stateObjects []stateEntry
addressToObjectIndex map[ethcmn.Address]int // map from address to the index of the state objects slice
stateObjectsDirty map[ethcmn.Address]struct{}
// The refund counter, also used by state transitioning.
refund uint64
thash, bhash ethcmn.Hash
txIndex int
logSize uint
// TODO: Determine if we actually need this as we do not need preimages in
// the SDK, but it seems to be used elsewhere in Geth.
preimages []preimageEntry
hashToPreimageIndex map[ethcmn.Hash]int // map from hash to the index of the preimages slice
// DB error.
// 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 memo-ized here and will eventually be returned
// by StateDB.Commit.
dbErr error
// journal of state modifications. This is the backbone of
// Snapshot and RevertToSnapshot.
journal *journal
validRevisions []revision
nextRevisionID int
// mutex for state deep copying
lock sync.Mutex
}
// NewCommitStateDB returns a reference to a newly initialized CommitStateDB
// which implements Geth's state.StateDB interface.
//
// CONTRACT: Stores used for state must be cache-wrapped as the ordering of the
// key/value space matters in determining the merkle root.
func NewCommitStateDB(
ctx sdk.Context, storeKey, tKey sdk.StoreKey, paramSpace paramtypes.Subspace,
ak AccountKeeper, bankKeeper BankKeeper,
) *CommitStateDB {
return &CommitStateDB{
ctx: ctx,
storeKey: storeKey,
transientKey: tKey,
paramSpace: paramSpace,
accountKeeper: ak,
bankKeeper: bankKeeper,
stateObjects: []stateEntry{},
addressToObjectIndex: make(map[ethcmn.Address]int),
stateObjectsDirty: make(map[ethcmn.Address]struct{}),
preimages: []preimageEntry{},
hashToPreimageIndex: make(map[ethcmn.Hash]int),
journal: newJournal(),
}
}
// WithContext returns a Database with an updated SDK context
func (csdb *CommitStateDB) WithContext(ctx sdk.Context) *CommitStateDB {
csdb.ctx = ctx
return csdb
}
// ----------------------------------------------------------------------------
// Setters
// ----------------------------------------------------------------------------
// SetHeightHash sets the block header hash associated with a given height.
func (csdb *CommitStateDB) SetHeightHash(height uint64, hash ethcmn.Hash) {
store := prefix.NewStore(csdb.ctx.KVStore(csdb.storeKey), KeyPrefixHeightToHeaderHash)
store.Set(sdk.Uint64ToBigEndian(height), hash.Bytes())
}
// SetParams sets the evm parameters to the param space.
func (csdb *CommitStateDB) SetParams(params Params) {
csdb.paramSpace.SetParamSet(csdb.ctx, &params)
}
// SetBalance sets the balance of an account.
func (csdb *CommitStateDB) SetBalance(addr ethcmn.Address, amount *big.Int) {
so := csdb.GetOrNewStateObject(addr)
so.SetBalance(amount)
}
// AddBalance adds amount to the account associated with addr.
func (csdb *CommitStateDB) AddBalance(addr ethcmn.Address, amount *big.Int) {
so := csdb.GetOrNewStateObject(addr)
so.AddBalance(amount)
}
// SubBalance subtracts amount from the account associated with addr.
func (csdb *CommitStateDB) SubBalance(addr ethcmn.Address, amount *big.Int) {
so := csdb.GetOrNewStateObject(addr)
so.SubBalance(amount)
}
// SetNonce sets the nonce (sequence number) of an account.
func (csdb *CommitStateDB) SetNonce(addr ethcmn.Address, nonce uint64) {
so := csdb.GetOrNewStateObject(addr)
so.SetNonce(nonce)
}
// SetState sets the storage state with a key, value pair for an account.
func (csdb *CommitStateDB) SetState(addr ethcmn.Address, key, value ethcmn.Hash) {
so := csdb.GetOrNewStateObject(addr)
so.SetState(nil, key, value)
}
// SetCode sets the code for a given account.
func (csdb *CommitStateDB) SetCode(addr ethcmn.Address, code []byte) {
so := csdb.GetOrNewStateObject(addr)
so.SetCode(ethcrypto.Keccak256Hash(code), code)
}
// ----------------------------------------------------------------------------
// Transaction logs
// Required for upgrade logic or ease of querying.
// NOTE: we use BinaryLengthPrefixed since the tx logs are also included on Result data,
// which can't use BinaryBare.
// ----------------------------------------------------------------------------
// SetLogs sets the logs for a transaction in the KVStore.
func (csdb *CommitStateDB) SetLogs(hash ethcmn.Hash, logs []*ethtypes.Log) error {
store := prefix.NewStore(csdb.ctx.KVStore(csdb.storeKey), KeyPrefixLogs)
txLogs := NewTransactionLogsFromEth(hash, logs)
bz, err := ModuleCdc.MarshalBinaryBare(&txLogs)
if err != nil {
return err
}
store.Set(hash.Bytes(), bz)
csdb.logSize = uint(len(logs))
return nil
}
// DeleteLogs removes the logs from the KVStore. It is used during journal.Revert.
func (csdb *CommitStateDB) DeleteLogs(hash ethcmn.Hash) {
store := prefix.NewStore(csdb.ctx.KVStore(csdb.storeKey), KeyPrefixLogs)
store.Delete(hash.Bytes())
}
// AddLog adds a new log to the state and sets the log metadata from the state.
func (csdb *CommitStateDB) AddLog(log *ethtypes.Log) {
csdb.journal.append(addLogChange{txhash: csdb.thash})
log.TxHash = csdb.thash
log.BlockHash = csdb.bhash
log.TxIndex = uint(csdb.txIndex)
log.Index = csdb.logSize
logs, err := csdb.GetLogs(csdb.thash)
if err != nil {
// panic on unmarshal error
panic(err)
}
if err = csdb.SetLogs(csdb.thash, append(logs, log)); err != nil {
// panic on marshal error
panic(err)
}
}
// AddPreimage records a SHA3 preimage seen by the VM.
func (csdb *CommitStateDB) AddPreimage(hash ethcmn.Hash, preimage []byte) {
if _, ok := csdb.hashToPreimageIndex[hash]; !ok {
csdb.journal.append(addPreimageChange{hash: hash})
pi := make([]byte, len(preimage))
copy(pi, preimage)
csdb.preimages = append(csdb.preimages, preimageEntry{hash: hash, preimage: pi})
csdb.hashToPreimageIndex[hash] = len(csdb.preimages) - 1
}
}
// AddRefund adds gas to the refund counter.
func (csdb *CommitStateDB) AddRefund(gas uint64) {
csdb.journal.append(refundChange{prev: csdb.refund})
csdb.refund += gas
}
// SubRefund removes gas from the refund counter. It will panic if the refund
// counter goes below zero.
func (csdb *CommitStateDB) SubRefund(gas uint64) {
csdb.journal.append(refundChange{prev: csdb.refund})
if gas > csdb.refund {
panic("refund counter below zero")
}
csdb.refund -= gas
}
// ----------------------------------------------------------------------------
// Access List // TODO: deprecate
// ----------------------------------------------------------------------------
// PrepareAccessList handles the preparatory steps for executing a state transition with
// regards to both EIP-2929 and EIP-2930:
//
// - Add sender to access list (2929)
// - Add destination to access list (2929)
// - Add precompiles to access list (2929)
// - Add the contents of the optional tx access list (2930)
//
// This method should only be called if Yolov3/Berlin/2929+2930 is applicable at the current number.
func (csdb *CommitStateDB) PrepareAccessList(sender ethcmn.Address, dest *ethcmn.Address, precompiles []ethcmn.Address, txAccesses ethtypes.AccessList) {
csdb.AddAddressToAccessList(sender)
if dest != nil {
csdb.AddAddressToAccessList(*dest)
// If it's a create-tx, the destination will be added inside evm.create
}
for _, addr := range precompiles {
csdb.AddAddressToAccessList(addr)
}
for _, tuple := range txAccesses {
csdb.AddAddressToAccessList(tuple.Address)
for _, key := range tuple.StorageKeys {
csdb.AddSlotToAccessList(tuple.Address, key)
}
}
}
// AddressInAccessList returns true if the address is registered on the access list map.
func (csdb *CommitStateDB) AddressInAccessList(addr ethcmn.Address) bool {
ts := prefix.NewStore(csdb.ctx.TransientStore(csdb.transientKey), KeyPrefixTransientAccessListAddress)
return ts.Has(addr.Bytes())
}
func (csdb *CommitStateDB) SlotInAccessList(addr ethcmn.Address, slot ethcmn.Hash) (addressOk bool, slotOk bool) {
addressOk = csdb.AddressInAccessList(addr)
slotOk = csdb.addressSlotInAccessList(addr, slot)
return addressOk, slotOk
}
func (csdb *CommitStateDB) addressSlotInAccessList(addr ethcmn.Address, slot ethcmn.Hash) bool {
ts := prefix.NewStore(csdb.ctx.TransientStore(csdb.transientKey), KeyPrefixTransientAccessListSlot)
key := append(addr.Bytes(), slot.Bytes()...)
return ts.Has(key)
}
// AddAddressToAccessList adds the given address to the access list. This operation is safe to perform
// even if the feature/fork is not active yet
func (csdb *CommitStateDB) AddAddressToAccessList(addr ethcmn.Address) {
if csdb.AddressInAccessList(addr) {
return
}
ts := prefix.NewStore(csdb.ctx.TransientStore(csdb.transientKey), KeyPrefixTransientAccessListAddress)
ts.Set(addr.Bytes(), []byte{0x1})
}
// AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform
// even if the feature/fork is not active yet
func (csdb *CommitStateDB) AddSlotToAccessList(addr ethcmn.Address, slot ethcmn.Hash) {
csdb.AddAddressToAccessList(addr)
if csdb.addressSlotInAccessList(addr, slot) {
return
}
ts := prefix.NewStore(csdb.ctx.TransientStore(csdb.transientKey), KeyPrefixTransientAccessListSlot)
key := append(addr.Bytes(), slot.Bytes()...)
ts.Set(key, []byte{0x1})
}
// ----------------------------------------------------------------------------
// Getters
// ----------------------------------------------------------------------------
// GetHeightHash returns the block header hash associated with a given block height and chain epoch number.
func (csdb *CommitStateDB) GetHeightHash(height uint64) ethcmn.Hash {
store := prefix.NewStore(csdb.ctx.KVStore(csdb.storeKey), KeyPrefixHeightToHeaderHash)
key := sdk.Uint64ToBigEndian(height)
bz := store.Get(key)
if len(bz) == 0 {
return ethcmn.Hash{}
}
return ethcmn.BytesToHash(bz)
}
// GetParams returns the total set of evm parameters.
func (csdb *CommitStateDB) GetParams() (params Params) {
csdb.paramSpace.GetParamSet(csdb.ctx, &params)
return params
}
// GetBalance retrieves the balance from the given address or 0 if object not
// found.
func (csdb *CommitStateDB) GetBalance(addr ethcmn.Address) *big.Int {
so := csdb.getStateObject(addr)
if so != nil {
return so.Balance()
}
return zeroBalance
}
// GetNonce returns the nonce (sequence number) for a given account.
func (csdb *CommitStateDB) GetNonce(addr ethcmn.Address) uint64 {
so := csdb.getStateObject(addr)
if so != nil {
return so.Nonce()
}
return 0
}
// TxIndex returns the current transaction index set by Prepare.
func (csdb *CommitStateDB) TxIndex() int {
return csdb.txIndex
}
// BlockHash returns the current block hash set by Prepare.
func (csdb *CommitStateDB) BlockHash() ethcmn.Hash {
return csdb.bhash
}
// GetCode returns the code for a given account.
func (csdb *CommitStateDB) GetCode(addr ethcmn.Address) []byte {
so := csdb.getStateObject(addr)
if so != nil {
return so.Code(nil)
}
return nil
}
// GetCodeSize returns the code size for a given account.
func (csdb *CommitStateDB) GetCodeSize(addr ethcmn.Address) int {
so := csdb.getStateObject(addr)
if so == nil {
return 0
}
if so.code != nil {
return len(so.code)
}
return len(so.Code(nil))
}
// GetCodeHash returns the code hash for a given account.
func (csdb *CommitStateDB) GetCodeHash(addr ethcmn.Address) ethcmn.Hash {
so := csdb.getStateObject(addr)
if so == nil {
return ethcmn.Hash{}
}
return ethcmn.BytesToHash(so.CodeHash())
}
// GetState retrieves a value from the given account's storage store.
func (csdb *CommitStateDB) GetState(addr ethcmn.Address, hash ethcmn.Hash) ethcmn.Hash {
so := csdb.getStateObject(addr)
if so != nil {
return so.GetState(nil, hash)
}
return ethcmn.Hash{}
}
// GetCommittedState retrieves a value from the given account's committed
// storage.
func (csdb *CommitStateDB) GetCommittedState(addr ethcmn.Address, hash ethcmn.Hash) ethcmn.Hash {
so := csdb.getStateObject(addr)
if so != nil {
return so.GetCommittedState(nil, hash)
}
return ethcmn.Hash{}
}
// GetLogs returns the current logs for a given transaction hash from the KVStore.
func (csdb *CommitStateDB) GetLogs(hash ethcmn.Hash) ([]*ethtypes.Log, error) {
store := prefix.NewStore(csdb.ctx.KVStore(csdb.storeKey), KeyPrefixLogs)
bz := store.Get(hash.Bytes())
if len(bz) == 0 {
// return nil error if logs are not found
return []*ethtypes.Log{}, nil
}
var txLogs TransactionLogs
if err := ModuleCdc.UnmarshalBinaryBare(bz, &txLogs); err != nil {
return []*ethtypes.Log{}, err
}
allLogs := []*ethtypes.Log{}
for _, txLog := range txLogs.Logs {
allLogs = append(allLogs, txLog.ToEthereum())
}
return allLogs, nil
}
// AllLogs returns all the current logs in the state.
func (csdb *CommitStateDB) AllLogs() []*ethtypes.Log {
store := csdb.ctx.KVStore(csdb.storeKey)
iterator := sdk.KVStorePrefixIterator(store, KeyPrefixLogs)
defer iterator.Close()
allLogs := []*ethtypes.Log{}
for ; iterator.Valid(); iterator.Next() {
var txLogs TransactionLogs
ModuleCdc.MustUnmarshalBinaryBare(iterator.Value(), &txLogs)
for _, txLog := range txLogs.Logs {
allLogs = append(allLogs, txLog.ToEthereum())
}
}
return allLogs
}
// GetRefund returns the current value of the refund counter.
func (csdb *CommitStateDB) GetRefund() uint64 {
return csdb.refund
}
// Preimages returns a list of SHA3 preimages that have been submitted.
func (csdb *CommitStateDB) Preimages() map[ethcmn.Hash][]byte {
preimages := map[ethcmn.Hash][]byte{}
for _, pe := range csdb.preimages {
preimages[pe.hash] = pe.preimage
}
return preimages
}
// HasSuicided returns if the given account for the specified address has been
// killed.
func (csdb *CommitStateDB) HasSuicided(addr ethcmn.Address) bool {
so := csdb.getStateObject(addr)
if so != nil {
return so.suicided
}
return false
}
// StorageTrie returns nil as the state in Ethermint does not use a direct
// storage trie.
func (csdb *CommitStateDB) StorageTrie(addr ethcmn.Address) ethstate.Trie {
return nil
}
// ----------------------------------------------------------------------------
// Persistence
// ----------------------------------------------------------------------------
// Commit writes the state to the appropriate KVStores. For each state object
// in the cache, it will either be removed, or have it's code set and/or it's
// state (storage) updated. In addition, the state object (account) itself will
// be written. Finally, the root hash (version) will be returned.
func (csdb *CommitStateDB) Commit(deleteEmptyObjects bool) (ethcmn.Hash, error) {
defer csdb.clearJournalAndRefund()
// remove dirty state object entries based on the journal
for _, dirty := range csdb.journal.dirties {
csdb.stateObjectsDirty[dirty.address] = struct{}{}
}
// set the state objects
for _, stateEntry := range csdb.stateObjects {
_, isDirty := csdb.stateObjectsDirty[stateEntry.address]
switch {
case stateEntry.stateObject.suicided || (isDirty && deleteEmptyObjects && stateEntry.stateObject.empty()):
// If the state object has been removed, don't bother syncing it and just
// remove it from the store.
csdb.deleteStateObject(stateEntry.stateObject)
case isDirty:
// write any contract code associated with the state object
if stateEntry.stateObject.code != nil && stateEntry.stateObject.dirtyCode {
stateEntry.stateObject.commitCode()
stateEntry.stateObject.dirtyCode = false
}
// update the object in the KVStore
if err := csdb.updateStateObject(stateEntry.stateObject); err != nil {
return ethcmn.Hash{}, err
}
}
delete(csdb.stateObjectsDirty, stateEntry.address)
}
// NOTE: Ethereum returns the trie merkle root here, but as commitment
// actually happens in the BaseApp at EndBlocker, we do not know the root at
// this time.
return ethcmn.Hash{}, nil
}
// Finalise finalizes the state objects (accounts) state by setting their state,
// removing the csdb destructed objects and clearing the journal as well as the
// refunds.
func (csdb *CommitStateDB) Finalise(deleteEmptyObjects bool) error {
for _, dirty := range csdb.journal.dirties {
idx, exist := csdb.addressToObjectIndex[dirty.address]
if !exist {
// ripeMD is 'touched' at block 1714175, in tx:
// 0x1237f737031e40bcde4a8b7e717b2d15e3ecadfe49bb1bbc71ee9deb09c6fcf2
//
// That tx goes out of gas, and although the notion of 'touched' does not
// exist there, the touch-event will still be recorded in the journal.
// Since ripeMD is a special snowflake, it will persist in the journal even
// though the journal is reverted. In this special circumstance, it may
// exist in journal.dirties but not in stateObjects. Thus, we can safely
// ignore it here.
continue
}
stateEntry := csdb.stateObjects[idx]
if stateEntry.stateObject.suicided || (deleteEmptyObjects && stateEntry.stateObject.empty()) {
csdb.deleteStateObject(stateEntry.stateObject)
} else {
// Set all the dirty state storage items for the state object in the
// KVStore and finally set the account in the account mapper.
stateEntry.stateObject.commitState()
if err := csdb.updateStateObject(stateEntry.stateObject); err != nil {
return err
}
}
csdb.stateObjectsDirty[dirty.address] = struct{}{}
}
// invalidate journal because reverting across transactions is not allowed
csdb.clearJournalAndRefund()
return nil
}
// IntermediateRoot returns the current root hash of the state. It is called in
// between transactions to get the root hash that goes into transaction
// receipts.
//
// NOTE: The SDK has not concept or method of getting any intermediate merkle
// root as commitment of the merkle-ized tree doesn't happen until the
// BaseApps' EndBlocker.
func (csdb *CommitStateDB) IntermediateRoot(deleteEmptyObjects bool) (ethcmn.Hash, error) {
if err := csdb.Finalise(deleteEmptyObjects); err != nil {
return ethcmn.Hash{}, err
}
return ethcmn.Hash{}, nil
}
// updateStateObject writes the given state object to the store.
func (csdb *CommitStateDB) updateStateObject(so *stateObject) error {
evmDenom := csdb.GetParams().EvmDenom
// NOTE: we don't use sdk.NewCoin here to avoid panic on test importer's genesis
newBalance := sdk.Coin{Denom: evmDenom, Amount: sdk.NewIntFromBigInt(so.Balance())}
if !newBalance.IsValid() {
return fmt.Errorf("invalid balance %s", newBalance)
}
err := csdb.bankKeeper.SetBalance(csdb.ctx, so.account.GetAddress(), newBalance)
if err != nil {
return err
}
csdb.accountKeeper.SetAccount(csdb.ctx, so.account)
return nil
}
// deleteStateObject removes the given state object from the state store.
func (csdb *CommitStateDB) deleteStateObject(so *stateObject) {
so.deleted = true
csdb.accountKeeper.RemoveAccount(csdb.ctx, so.account)
}
// ----------------------------------------------------------------------------
// Snapshotting
// ----------------------------------------------------------------------------
// Snapshot returns an identifier for the current revision of the state.
func (csdb *CommitStateDB) Snapshot() int {
id := csdb.nextRevisionID
csdb.nextRevisionID++
csdb.validRevisions = append(
csdb.validRevisions,
revision{
id: id,
journalIndex: csdb.journal.length(),
},
)
return id
}
// RevertToSnapshot reverts all state changes made since the given revision.
func (csdb *CommitStateDB) RevertToSnapshot(revID int) {
// find the snapshot in the stack of valid snapshots
idx := sort.Search(len(csdb.validRevisions), func(i int) bool {
return csdb.validRevisions[i].id >= revID
})
if idx == len(csdb.validRevisions) || csdb.validRevisions[idx].id != revID {
panic(fmt.Errorf("revision ID %v cannot be reverted", revID))
}
snapshot := csdb.validRevisions[idx].journalIndex
// replay the journal to undo changes and remove invalidated snapshots
csdb.journal.revert(csdb, snapshot)
csdb.validRevisions = csdb.validRevisions[:idx]
}
// ----------------------------------------------------------------------------
// Auxiliary
// ----------------------------------------------------------------------------
// Database retrieves the low level database supporting the lower level trie
// ops. It is not used in Ethermint, so it returns nil.
func (csdb *CommitStateDB) Database() ethstate.Database {
return nil
}
// Empty returns whether the state object is either non-existent or empty
// according to the EIP161 specification (balance = nonce = code = 0).
func (csdb *CommitStateDB) Empty(addr ethcmn.Address) bool {
so := csdb.getStateObject(addr)
return so == nil || so.empty()
}
// Exist reports whether the given account address exists in the state. Notably,
// this also returns true for suicided accounts.
func (csdb *CommitStateDB) Exist(addr ethcmn.Address) bool {
return csdb.getStateObject(addr) != nil
}
// Error returns the first non-nil error the StateDB encountered.
func (csdb *CommitStateDB) Error() error {
return csdb.dbErr
}
// Suicide marks the given account as suicided and clears the account balance.
//
// The account's state object is still available until the state is committed,
// getStateObject will return a non-nil account after Suicide.
func (csdb *CommitStateDB) Suicide(addr ethcmn.Address) bool {
so := csdb.getStateObject(addr)
if so == nil {
return false
}
csdb.journal.append(suicideChange{
account: &addr,
prev: so.suicided,
prevBalance: sdk.NewIntFromBigInt(so.Balance()),
})
so.markSuicided()
so.SetBalance(new(big.Int))
return true
}
// Reset clears out all ephemeral state objects from the state db, but keeps
// the underlying account mapper and store keys to avoid reloading data for the
// next operations.
func (csdb *CommitStateDB) Reset(_ ethcmn.Hash) error {
csdb.stateObjects = []stateEntry{}
csdb.addressToObjectIndex = make(map[ethcmn.Address]int)
csdb.stateObjectsDirty = make(map[ethcmn.Address]struct{})
csdb.thash = ethcmn.Hash{}
csdb.bhash = ethcmn.Hash{}
csdb.txIndex = 0
csdb.logSize = 0
csdb.preimages = []preimageEntry{}
csdb.hashToPreimageIndex = make(map[ethcmn.Hash]int)
csdb.clearJournalAndRefund()
return nil
}
// UpdateAccounts updates the nonce and coin balances of accounts
func (csdb *CommitStateDB) UpdateAccounts() {
for _, stateEntry := range csdb.stateObjects {
address := sdk.AccAddress(stateEntry.address.Bytes())
currAccount := csdb.accountKeeper.GetAccount(csdb.ctx, address)
ethAcc, ok := currAccount.(*ethermint.EthAccount)
if !ok {
continue
}
evmDenom := csdb.GetParams().EvmDenom
balance := csdb.bankKeeper.GetBalance(csdb.ctx, address, evmDenom)
if stateEntry.stateObject.Balance() != balance.Amount.BigInt() && balance.IsValid() {
stateEntry.stateObject.balance = balance.Amount
}
if stateEntry.stateObject.Nonce() != ethAcc.GetSequence() {
stateEntry.stateObject.account = ethAcc
}
}
}
// ClearStateObjects clears cache of state objects to handle account changes outside of the EVM
func (csdb *CommitStateDB) ClearStateObjects() {
csdb.stateObjects = []stateEntry{}
csdb.addressToObjectIndex = make(map[ethcmn.Address]int)
csdb.stateObjectsDirty = make(map[ethcmn.Address]struct{})
}
func (csdb *CommitStateDB) clearJournalAndRefund() {
csdb.journal = newJournal()
csdb.validRevisions = csdb.validRevisions[:0]
csdb.refund = 0
}
// Prepare sets the current transaction hash and index and block hash which is
// used when the EVM emits new state logs.
func (csdb *CommitStateDB) Prepare(thash, bhash ethcmn.Hash, txi int) {
csdb.thash = thash
csdb.bhash = bhash
csdb.txIndex = txi
}
// CreateAccount explicitly creates a state object. If a state object with the
// address already exists the balance is carried over to the new account.
//
// CreateAccount is called during the EVM CREATE operation. The situation might
// arise that a contract does the following:
//
// 1. sends funds to sha(account ++ (nonce + 1))
// 2. tx_create(sha(account ++ nonce)) (note that this gets the address of 1)
//
// Carrying over the balance ensures that Ether doesn't disappear.
func (csdb *CommitStateDB) CreateAccount(addr ethcmn.Address) {
newobj, prevobj := csdb.createObject(addr)
if prevobj != nil {
newobj.setBalance(sdk.NewIntFromBigInt(prevobj.Balance()))
}
}
// Copy creates a deep, independent copy of the state.
//
// NOTE: Snapshots of the copied state cannot be applied to the copy.
func (csdb *CommitStateDB) Copy() *CommitStateDB {
// copy all the basic fields, initialize the memory ones
state := &CommitStateDB{}
CopyCommitStateDB(csdb, state)
return state
}
func CopyCommitStateDB(from, to *CommitStateDB) {
from.lock.Lock()
defer from.lock.Unlock()
to.ctx = from.ctx
to.storeKey = from.storeKey
to.paramSpace = from.paramSpace
to.accountKeeper = from.accountKeeper
to.bankKeeper = from.bankKeeper
to.stateObjects = []stateEntry{}
to.addressToObjectIndex = make(map[ethcmn.Address]int)
to.stateObjectsDirty = make(map[ethcmn.Address]struct{})
to.refund = from.refund
to.logSize = from.logSize
to.preimages = make([]preimageEntry, len(from.preimages))
to.hashToPreimageIndex = make(map[ethcmn.Hash]int, len(from.hashToPreimageIndex))
to.journal = newJournal()
to.thash = from.thash
to.bhash = from.bhash
to.txIndex = from.txIndex
validRevisions := make([]revision, len(from.validRevisions))
copy(validRevisions, from.validRevisions)
to.validRevisions = validRevisions
to.nextRevisionID = from.nextRevisionID
// copy the dirty states, logs, and preimages
for _, dirty := range from.journal.dirties {
// There is a case where an object is in the journal but not in the
// stateObjects: OOG after touch on ripeMD prior to Byzantium. Thus, we
// need to check for nil.
//
// Ref: https://github.com/ethereum/go-ethereum/pull/16485#issuecomment-380438527
if idx, exist := from.addressToObjectIndex[dirty.address]; exist {
to.stateObjects = append(to.stateObjects, stateEntry{
address: dirty.address,
stateObject: from.stateObjects[idx].stateObject.deepCopy(to),
})
to.addressToObjectIndex[dirty.address] = len(to.stateObjects) - 1
to.stateObjectsDirty[dirty.address] = struct{}{}
}
}
// Above, we don't copy the actual journal. This means that if the copy is
// copied, the loop above will be a no-op, since the copy's journal is empty.
// Thus, here we iterate over stateObjects, to enable copies of copies.
for addr := range from.stateObjectsDirty {
if idx, exist := to.addressToObjectIndex[addr]; !exist {
to.setStateObject(from.stateObjects[idx].stateObject.deepCopy(to))
to.stateObjectsDirty[addr] = struct{}{}
}
}
// copy pre-images
for i, preimageEntry := range from.preimages {
to.preimages[i] = preimageEntry
to.hashToPreimageIndex[preimageEntry.hash] = i
}
}
// ForEachStorage iterates over each storage items, all invoke the provided
// callback on each key, value pair.
func (csdb *CommitStateDB) ForEachStorage(addr ethcmn.Address, cb func(key, value ethcmn.Hash) (stop bool)) error {
so := csdb.getStateObject(addr)
if so == nil {
return nil
}
store := csdb.ctx.KVStore(csdb.storeKey)
prefix := AddressStoragePrefix(so.Address())
iterator := sdk.KVStorePrefixIterator(store, prefix)
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
key := ethcmn.BytesToHash(iterator.Key())
value := ethcmn.BytesToHash(iterator.Value())
if idx, dirty := so.keyToDirtyStorageIndex[key]; dirty {
// check if iteration stops
if cb(key, ethcmn.HexToHash(so.dirtyStorage[idx].Value)) {
break
}
continue
}
// check if iteration stops
if cb(key, value) {
return nil
}
}
return nil
}
// GetOrNewStateObject retrieves a state object or create a new state object if
// nil.
func (csdb *CommitStateDB) GetOrNewStateObject(addr ethcmn.Address) StateObject {
so := csdb.getStateObject(addr)
if so == nil || so.deleted {
so, _ = csdb.createObject(addr)
}
return so
}
// createObject creates a new state object. If there is an existing account with
// the given address, it is overwritten and returned as the second return value.
func (csdb *CommitStateDB) createObject(addr ethcmn.Address) (newObj, prevObj *stateObject) {
prevObj = csdb.getStateObject(addr)
acc := csdb.accountKeeper.NewAccountWithAddress(csdb.ctx, sdk.AccAddress(addr.Bytes()))
newObj = newStateObject(csdb, acc, sdk.ZeroInt())
newObj.setNonce(0) // sets the object to dirty
if prevObj == nil {
csdb.journal.append(createObjectChange{account: &addr})
} else {
csdb.journal.append(resetObjectChange{prev: prevObj})
}
csdb.setStateObject(newObj)
return newObj, prevObj
}
// setError remembers the first non-nil error it is called with.
func (csdb *CommitStateDB) setError(err error) {
if csdb.dbErr == nil {
csdb.dbErr = err
}
}
// getStateObject attempts to retrieve a state object given by the address.
// Returns nil and sets an error if not found.
func (csdb *CommitStateDB) getStateObject(addr ethcmn.Address) (stateObject *stateObject) {
if idx, found := csdb.addressToObjectIndex[addr]; found {
// prefer 'live' (cached) objects
if so := csdb.stateObjects[idx].stateObject; so != nil {
if so.deleted {
return nil
}
return so
}
}
// otherwise, attempt to fetch the account from the account mapper
acc := csdb.accountKeeper.GetAccount(csdb.ctx, sdk.AccAddress(addr.Bytes()))
if acc == nil {
csdb.setError(fmt.Errorf("no account found for address: %s", addr.String()))
return nil
}
evmDenom := csdb.GetParams().EvmDenom
balance := csdb.bankKeeper.GetBalance(csdb.ctx, acc.GetAddress(), evmDenom)
// insert the state object into the live set
so := newStateObject(csdb, acc, balance.Amount)
csdb.setStateObject(so)
return so
}
func (csdb *CommitStateDB) setStateObject(so *stateObject) {
if idx, found := csdb.addressToObjectIndex[so.Address()]; found {
// update the existing object
csdb.stateObjects[idx].stateObject = so
return
}
// append the new state object to the stateObjects slice
se := stateEntry{
address: so.Address(),
stateObject: so,
}
csdb.stateObjects = append(csdb.stateObjects, se)
csdb.addressToObjectIndex[se.address] = len(csdb.stateObjects) - 1
}
// RawDump returns a raw state dump.
//
// TODO: Implement if we need it, especially for the RPC API.
func (csdb *CommitStateDB) RawDump() ethstate.Dump {
return ethstate.Dump{}
}
type preimageEntry struct {
// hash key of the preimage entry
hash ethcmn.Hash
preimage []byte
}

View File

@ -1,739 +0,0 @@
package types_test
import (
"fmt"
"math/big"
"testing"
"github.com/stretchr/testify/suite"
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
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/app"
"github.com/cosmos/ethermint/crypto/ethsecp256k1"
ethermint "github.com/cosmos/ethermint/types"
"github.com/cosmos/ethermint/x/evm/types"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
)
type StateDBTestSuite struct {
suite.Suite
ctx sdk.Context
app *app.EthermintApp
stateDB *types.CommitStateDB
address ethcmn.Address
stateObject types.StateObject
}
func TestStateDBTestSuite(t *testing.T) {
suite.Run(t, new(StateDBTestSuite))
}
func (suite *StateDBTestSuite) SetupTest() {
checkTx := false
suite.app = app.Setup(checkTx)
suite.ctx = suite.app.BaseApp.NewContext(checkTx, tmproto.Header{Height: 1})
suite.stateDB = suite.app.EvmKeeper.CommitStateDB.WithContext(suite.ctx)
privkey, err := ethsecp256k1.GenerateKey()
suite.Require().NoError(err)
suite.address = ethcmn.BytesToAddress(privkey.PubKey().Address().Bytes())
balance := ethermint.NewPhotonCoin(sdk.ZeroInt())
acc := &ethermint.EthAccount{
BaseAccount: authtypes.NewBaseAccount(sdk.AccAddress(suite.address.Bytes()), nil, 0, 0),
CodeHash: ethcrypto.Keccak256(nil),
}
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
err = suite.app.BankKeeper.SetBalance(suite.ctx, acc.GetAddress(), balance)
suite.Require().NoError(err)
suite.stateObject = suite.stateDB.GetOrNewStateObject(suite.address)
}
func (suite *StateDBTestSuite) TestParams() {
params := suite.stateDB.GetParams()
suite.Require().Equal(types.DefaultParams(), params)
params.EvmDenom = "inj"
suite.stateDB.SetParams(params)
newParams := suite.stateDB.GetParams()
suite.Require().Equal(newParams, params)
}
func (suite *StateDBTestSuite) TestBloomFilter() {
// Prepare db for logs
tHash := ethcmn.BytesToHash([]byte{0x1})
suite.stateDB.Prepare(tHash, ethcmn.Hash{}, 0)
contractAddress := ethcmn.BigToAddress(big.NewInt(1))
log := ethtypes.Log{Address: contractAddress}
testCase := []struct {
name string
malleate func()
numLogs int
isBloom bool
}{
{
"no logs",
func() {},
0,
false,
},
{
"add log",
func() {
suite.stateDB.AddLog(&log)
},
1,
false,
},
{
"bloom",
func() {},
0,
true,
},
}
for _, tc := range testCase {
tc.malleate()
logs, err := suite.stateDB.GetLogs(tHash)
if !tc.isBloom {
suite.Require().NoError(err, tc.name)
suite.Require().Len(logs, tc.numLogs, tc.name)
if len(logs) != 0 {
suite.Require().Equal(log, *logs[0], tc.name)
}
} else {
// get logs bloom from the log
bloomInt := ethtypes.LogsBloom(logs)
bloomFilter := ethtypes.BytesToBloom(bloomInt)
suite.Require().True(ethtypes.BloomLookup(bloomFilter, contractAddress), tc.name)
suite.Require().False(ethtypes.BloomLookup(bloomFilter, ethcmn.BigToAddress(big.NewInt(2))), tc.name)
}
}
}
func (suite *StateDBTestSuite) TestStateDB_Balance() {
testCase := []struct {
name string
malleate func()
balance *big.Int
}{
{
"set balance",
func() {
suite.stateDB.SetBalance(suite.address, big.NewInt(100))
},
big.NewInt(100),
},
{
"sub balance",
func() {
suite.stateDB.SubBalance(suite.address, big.NewInt(100))
},
big.NewInt(0),
},
{
"add balance",
func() {
suite.stateDB.AddBalance(suite.address, big.NewInt(200))
},
big.NewInt(200),
},
{
"sub more than balance",
func() {
suite.stateDB.SubBalance(suite.address, big.NewInt(300))
},
big.NewInt(-100),
},
}
for _, tc := range testCase {
tc.malleate()
suite.Require().Equal(tc.balance, suite.stateDB.GetBalance(suite.address), tc.name)
}
}
func (suite *StateDBTestSuite) TestStateDBNonce() {
nonce := uint64(123)
suite.stateDB.SetNonce(suite.address, nonce)
suite.Require().Equal(nonce, suite.stateDB.GetNonce(suite.address))
}
func (suite *StateDBTestSuite) TestStateDB_Error() {
nonce := suite.stateDB.GetNonce(ethcmn.Address{})
suite.Require().Equal(0, int(nonce))
suite.Require().Error(suite.stateDB.Error())
}
func (suite *StateDBTestSuite) TestStateDB_Database() {
suite.Require().Nil(suite.stateDB.Database())
}
func (suite *StateDBTestSuite) TestStateDB_State() {
key := ethcmn.BytesToHash([]byte("foo"))
val := ethcmn.BytesToHash([]byte("bar"))
suite.stateDB.SetState(suite.address, key, val)
testCase := []struct {
name string
address ethcmn.Address
key ethcmn.Hash
value ethcmn.Hash
}{
{
"found state",
suite.address,
ethcmn.BytesToHash([]byte("foo")),
ethcmn.BytesToHash([]byte("bar")),
},
{
"state not found",
suite.address,
ethcmn.BytesToHash([]byte("key")),
ethcmn.Hash{},
},
{
"object not found",
ethcmn.Address{},
ethcmn.BytesToHash([]byte("foo")),
ethcmn.Hash{},
},
}
for _, tc := range testCase {
value := suite.stateDB.GetState(tc.address, tc.key)
suite.Require().Equal(tc.value, value, tc.name)
}
}
func (suite *StateDBTestSuite) TestStateDB_Code() {
testCase := []struct {
name string
address ethcmn.Address
code []byte
malleate func()
}{
{
"no stored code for state object",
suite.address,
nil,
func() {},
},
{
"existing address",
suite.address,
[]byte("code"),
func() {
suite.stateDB.SetCode(suite.address, []byte("code"))
},
},
{
"state object not found",
ethcmn.Address{},
nil,
func() {},
},
}
for _, tc := range testCase {
tc.malleate()
suite.Require().Equal(tc.code, suite.stateDB.GetCode(tc.address), tc.name)
suite.Require().Equal(len(tc.code), suite.stateDB.GetCodeSize(tc.address), tc.name)
}
}
func (suite *StateDBTestSuite) TestStateDB_Logs() {
testCase := []struct {
name string
log ethtypes.Log
}{
{
"state db log",
ethtypes.Log{
Address: suite.address,
Topics: []ethcmn.Hash{ethcmn.BytesToHash([]byte("topic"))},
Data: []byte("data"),
BlockNumber: 1,
TxHash: ethcmn.Hash{},
TxIndex: 1,
BlockHash: ethcmn.Hash{},
Index: 0,
Removed: false,
},
},
}
for _, tc := range testCase {
hash := ethcmn.BytesToHash([]byte("hash"))
logs := []*ethtypes.Log{&tc.log}
err := suite.stateDB.SetLogs(hash, logs)
suite.Require().NoError(err, tc.name)
dbLogs, err := suite.stateDB.GetLogs(hash)
suite.Require().NoError(err, tc.name)
suite.Require().Equal(logs, dbLogs, tc.name)
suite.stateDB.DeleteLogs(hash)
dbLogs, err = suite.stateDB.GetLogs(hash)
suite.Require().NoError(err, tc.name)
suite.Require().Empty(dbLogs, tc.name)
suite.stateDB.AddLog(&tc.log)
suite.Require().Equal(logs, suite.stateDB.AllLogs(), tc.name)
//resets state but checking to see if storekey still persists.
err = suite.stateDB.Reset(hash)
suite.Require().NoError(err, tc.name)
suite.Require().Equal(logs, suite.stateDB.AllLogs(), tc.name)
}
}
func (suite *StateDBTestSuite) TestStateDB_Preimage() {
hash := ethcmn.BytesToHash([]byte("hash"))
preimage := []byte("preimage")
suite.stateDB.AddPreimage(hash, preimage)
suite.Require().Equal(preimage, suite.stateDB.Preimages()[hash])
}
func (suite *StateDBTestSuite) TestStateDB_Refund() {
testCase := []struct {
name string
addAmount uint64
subAmount uint64
expRefund uint64
expPanic bool
}{
{
"refund 0",
0, 0, 0,
false,
},
{
"refund positive amount",
100, 0, 100,
false,
},
{
"refund panic",
100, 200, 100,
true,
},
}
for _, tc := range testCase {
suite.Run(tc.name, func() {
suite.SetupTest() // reset
suite.stateDB.AddRefund(tc.addAmount)
suite.Require().Equal(tc.addAmount, suite.stateDB.GetRefund())
if tc.expPanic {
suite.Panics(func() {
suite.stateDB.SubRefund(tc.subAmount)
})
} else {
suite.stateDB.SubRefund(tc.subAmount)
suite.Require().Equal(tc.expRefund, suite.stateDB.GetRefund())
}
})
}
}
func (suite *StateDBTestSuite) TestStateDB_CreateAccount() {
prevBalance := big.NewInt(12)
testCase := []struct {
name string
address ethcmn.Address
malleate func()
}{
{
"existing account",
suite.address,
func() {
suite.stateDB.AddBalance(suite.address, prevBalance)
},
},
{
"new account",
ethcmn.HexToAddress("0x756F45E3FA69347A9A973A725E3C98bC4db0b4c1"),
func() {
prevBalance = big.NewInt(0)
},
},
}
for _, tc := range testCase {
tc.malleate()
suite.stateDB.CreateAccount(tc.address)
suite.Require().True(suite.stateDB.Exist(tc.address), tc.name)
suite.Require().Equal(prevBalance, suite.stateDB.GetBalance(tc.address), tc.name)
}
}
func (suite *StateDBTestSuite) TestStateDB_ClearStateObj() {
priv, err := ethsecp256k1.GenerateKey()
suite.Require().NoError(err)
addr := ethcrypto.PubkeyToAddress(priv.ToECDSA().PublicKey)
suite.stateDB.CreateAccount(addr)
suite.Require().True(suite.stateDB.Exist(addr))
suite.stateDB.ClearStateObjects()
suite.Require().False(suite.stateDB.Exist(addr))
}
func (suite *StateDBTestSuite) TestStateDB_Reset() {
priv, err := ethsecp256k1.GenerateKey()
suite.Require().NoError(err)
addr := ethcrypto.PubkeyToAddress(priv.ToECDSA().PublicKey)
suite.stateDB.CreateAccount(addr)
suite.Require().True(suite.stateDB.Exist(addr))
err = suite.stateDB.Reset(ethcmn.BytesToHash(nil))
suite.Require().NoError(err)
suite.Require().False(suite.stateDB.Exist(addr))
}
func (suite *StateDBTestSuite) TestSuiteDB_Prepare() {
thash := ethcmn.BytesToHash([]byte("thash"))
bhash := ethcmn.BytesToHash([]byte("bhash"))
txi := 1
suite.stateDB.Prepare(thash, bhash, txi)
suite.Require().Equal(txi, suite.stateDB.TxIndex())
suite.Require().Equal(bhash, suite.stateDB.BlockHash())
}
func (suite *StateDBTestSuite) TestSuiteDB_CopyState() {
testCase := []struct {
name string
log ethtypes.Log
}{
{
"copy state",
ethtypes.Log{
Address: suite.address,
Topics: []ethcmn.Hash{ethcmn.BytesToHash([]byte("topic"))},
Data: []byte("data"),
BlockNumber: 1,
TxHash: ethcmn.Hash{},
TxIndex: 1,
BlockHash: ethcmn.Hash{},
Index: 0,
Removed: false,
},
},
}
for _, tc := range testCase {
hash := ethcmn.BytesToHash([]byte("hash"))
logs := []*ethtypes.Log{&tc.log}
err := suite.stateDB.SetLogs(hash, logs)
suite.Require().NoError(err, tc.name)
copyDB := suite.stateDB.Copy()
copiedDBLogs, err := copyDB.GetLogs(hash)
suite.Require().NoError(err, tc.name)
suite.Require().Equal(logs, copiedDBLogs, tc.name)
suite.Require().Equal(suite.stateDB.Exist(suite.address), copyDB.Exist(suite.address), tc.name)
}
}
func (suite *StateDBTestSuite) TestSuiteDB_Empty() {
suite.Require().True(suite.stateDB.Empty(suite.address))
suite.stateDB.SetBalance(suite.address, big.NewInt(100))
suite.Require().False(suite.stateDB.Empty(suite.address))
}
func (suite *StateDBTestSuite) TestSuiteDB_Suicide() {
testCase := []struct {
name string
amount *big.Int
expPass bool
delete bool
}{
{
"suicide zero balance",
big.NewInt(0),
false, false,
},
{
"suicide with balance",
big.NewInt(100),
true, false,
},
{
"delete",
big.NewInt(0),
true, true,
},
}
for _, tc := range testCase {
if tc.delete {
_, err := suite.stateDB.Commit(tc.delete)
suite.Require().NoError(err, tc.name)
suite.Require().False(suite.stateDB.Exist(suite.address), tc.name)
continue
}
if tc.expPass {
suite.stateDB.SetBalance(suite.address, tc.amount)
suicide := suite.stateDB.Suicide(suite.address)
suite.Require().True(suicide, tc.name)
suite.Require().True(suite.stateDB.HasSuicided(suite.address), tc.name)
} else {
//Suicide only works for an account with non-zero balance/nonce
priv, err := ethsecp256k1.GenerateKey()
suite.Require().NoError(err)
addr := ethcrypto.PubkeyToAddress(priv.ToECDSA().PublicKey)
suicide := suite.stateDB.Suicide(addr)
suite.Require().False(suicide, tc.name)
suite.Require().False(suite.stateDB.HasSuicided(addr), tc.name)
}
}
}
func (suite *StateDBTestSuite) TestCommitStateDB_Commit() {
testCase := []struct {
name string
malleate func()
deleteObjs bool
expPass bool
}{
{
"commit suicided",
func() {
ok := suite.stateDB.Suicide(suite.address)
suite.Require().True(ok)
},
true, true,
},
{
"commit with dirty value",
func() {
suite.stateDB.SetCode(suite.address, []byte("code"))
},
false, true,
},
{
"faled to update state object",
func() {
suite.stateDB.SubBalance(suite.address, big.NewInt(10))
},
false, false,
},
}
for _, tc := range testCase {
tc.malleate()
hash, err := suite.stateDB.Commit(tc.deleteObjs)
suite.Require().Equal(ethcmn.Hash{}, hash)
if !tc.expPass {
suite.Require().Error(err, tc.name)
continue
}
suite.Require().NoError(err, tc.name)
acc := suite.app.AccountKeeper.GetAccount(suite.ctx, sdk.AccAddress(suite.address.Bytes()))
if tc.deleteObjs {
suite.Require().Nil(acc, tc.name)
continue
}
suite.Require().NotNil(acc, tc.name)
ethAcc, ok := acc.(*ethermint.EthAccount)
suite.Require().True(ok)
suite.Require().Equal(ethcrypto.Keccak256([]byte("code")), ethAcc.CodeHash)
}
}
func (suite *StateDBTestSuite) TestCommitStateDB_Finalize() {
testCase := []struct {
name string
malleate func()
deleteObjs bool
expPass bool
}{
{
"finalize suicided",
func() {
ok := suite.stateDB.Suicide(suite.address)
suite.Require().True(ok)
},
true, true,
},
{
"finalize, not suicided",
func() {
suite.stateDB.AddBalance(suite.address, big.NewInt(5))
},
false, true,
},
{
"finalize, dirty storage",
func() {
suite.stateDB.SetState(suite.address, ethcmn.BytesToHash([]byte("key")), ethcmn.BytesToHash([]byte("value")))
},
false, true,
},
{
"faled to update state object",
func() {
suite.stateDB.SubBalance(suite.address, big.NewInt(10))
},
false, false,
},
}
for _, tc := range testCase {
tc.malleate()
err := suite.stateDB.Finalise(tc.deleteObjs)
if !tc.expPass {
suite.Require().Error(err, tc.name)
hash := suite.stateDB.GetCommittedState(suite.address, ethcmn.BytesToHash([]byte("key")))
suite.Require().NotEqual(ethcmn.Hash{}, hash, tc.name)
continue
}
suite.Require().NoError(err, tc.name)
acc := suite.app.AccountKeeper.GetAccount(suite.ctx, sdk.AccAddress(suite.address.Bytes()))
if tc.deleteObjs {
suite.Require().Nil(acc, tc.name)
continue
}
suite.Require().NotNil(acc, tc.name)
}
}
func (suite *StateDBTestSuite) TestCommitStateDB_GetCommittedState() {
hash := suite.stateDB.GetCommittedState(ethcmn.Address{}, ethcmn.BytesToHash([]byte("key")))
suite.Require().Equal(ethcmn.Hash{}, hash)
}
func (suite *StateDBTestSuite) TestCommitStateDB_Snapshot() {
id := suite.stateDB.Snapshot()
suite.Require().NotPanics(func() {
suite.stateDB.RevertToSnapshot(id)
})
suite.Require().Panics(func() {
suite.stateDB.RevertToSnapshot(-1)
}, "invalid revision should panic")
}
func (suite *StateDBTestSuite) TestCommitStateDB_ForEachStorage() {
var storage types.Storage
testCase := []struct {
name string
malleate func()
callback func(key, value ethcmn.Hash) (stop bool)
expValues []ethcmn.Hash
}{
{
"aggregate state",
func() {
for i := 0; i < 5; i++ {
suite.stateDB.SetState(suite.address, ethcmn.BytesToHash([]byte(fmt.Sprintf("key%d", i))), ethcmn.BytesToHash([]byte(fmt.Sprintf("value%d", i))))
}
},
func(key, value ethcmn.Hash) bool {
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")),
},
},
{
"filter state",
func() {
suite.stateDB.SetState(suite.address, ethcmn.BytesToHash([]byte("key")), ethcmn.BytesToHash([]byte("value")))
suite.stateDB.SetState(suite.address, ethcmn.BytesToHash([]byte("filterkey")), ethcmn.BytesToHash([]byte("filtervalue")))
},
func(key, value ethcmn.Hash) bool {
if value == ethcmn.BytesToHash([]byte("filtervalue")) {
storage = append(storage, types.NewState(key, value))
return true
}
return false
},
[]ethcmn.Hash{
ethcmn.BytesToHash([]byte("filtervalue")),
},
},
}
for _, tc := range testCase {
suite.Run(tc.name, func() {
suite.SetupTest() // reset
tc.malleate()
suite.stateDB.Finalise(false)
err := suite.stateDB.ForEachStorage(suite.address, tc.callback)
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([]string, len(storage))
for i := range storage {
vals[i] = storage[i].Value
}
suite.Require().ElementsMatch(tc.expValues, vals)
})
storage = types.Storage{}
}
}
func (suite *StateDBTestSuite) TestCommitStateDB_AccessList() {
addr := ethcmn.Address([20]byte{77})
hash := ethcmn.Hash([32]byte{99})
suite.Require().False(suite.stateDB.AddressInAccessList(addr))
suite.stateDB.AddAddressToAccessList(addr)
suite.Require().True(suite.stateDB.AddressInAccessList(addr))
addrIn, slotIn := suite.stateDB.SlotInAccessList(addr, hash)
suite.Require().True(addrIn)
suite.Require().False(slotIn)
suite.stateDB.AddSlotToAccessList(addr, hash)
addrIn, slotIn = suite.stateDB.SlotInAccessList(addr, hash)
suite.Require().True(addrIn)
suite.Require().True(slotIn)
}

View File

@ -5,20 +5,13 @@ import (
"github.com/gogo/protobuf/proto"
"github.com/pkg/errors"
"golang.org/x/crypto/sha3"
sdk "github.com/cosmos/cosmos-sdk/types"
ethcmn "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/crypto"
)
func rlpHash(x interface{}) (hash ethcmn.Hash) {
hasher := sha3.NewLegacyKeccak256()
_ = rlp.Encode(hasher, x)
_ = hasher.Sum(hash[:0])
return hash
}
var EmptyCodeHash = crypto.Keccak256(nil)
// EncodeTxResponse takes all of the necessary data from the EVM execution
// and returns the data as a byte slice encoded with protobuf.