evm: various fixes (#319)

* evm: use prefix stores, move SetBlockBloom to EndBlock, bug fixes

* add logs to genesis state

* log tests

* commit and finalize on InitGenesis

* validate TransactionLogs

* changelog

* fix test-import

* fix lint

* add more tests
This commit is contained in:
Federico Kunze 2020-06-04 06:40:21 -04:00 committed by GitHub
parent 446eb0a3b7
commit 427e96c1de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 352 additions and 197 deletions

View File

@ -56,6 +56,11 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (`x/evm`) [\#255](https://github.com/ChainSafe/ethermint/pull/255) Add missing `GenesisState` fields and support `ExportGenesis` functionality. * (`x/evm`) [\#255](https://github.com/ChainSafe/ethermint/pull/255) Add missing `GenesisState` fields and support `ExportGenesis` functionality.
* [\#272](https://github.com/ChainSafe/ethermint/pull/272) Add `Logger` for evm module. * [\#272](https://github.com/ChainSafe/ethermint/pull/272) Add `Logger` for evm module.
* [\#317](https://github.com/ChainSafe/ethermint/pull/317) `GenesisAccount` validation. * [\#317](https://github.com/ChainSafe/ethermint/pull/317) `GenesisAccount` validation.
* (`x/evm`) [\#319](https://github.com/ChainSafe/ethermint/pull/319) Verious evm improvements:
* Add transaction `[]*ethtypes.Logs` to evm's `GenesisState` to persist logs after an upgrade.
* Remove evm `CodeKey` and `BlockKey`in favor of a prefix `Store`.
* Set `BlockBloom` during `EndBlock` instead of `BeginBlock`.
* `Commit` state object and `Finalize` storage after `InitGenesis` setup.
### Features ### Features
@ -73,5 +78,6 @@ Ref: https://keepachangelog.com/en/1.0.0/
### Bug Fixes ### Bug Fixes
* (`x/evm`) [\#319](https://github.com/ChainSafe/ethermint/pull/319) Fix `SetBlockHash` that was setting the incorrect height during `BeginBlock`.
* (x/evm) [\#176](https://github.com/ChainSafe/ethermint/issues/176) Updated Web3 transaction hash from using RLP hash. Now all transaction hashes exposed are amino hashes. * (x/evm) [\#176](https://github.com/ChainSafe/ethermint/issues/176) Updated Web3 transaction hash from using RLP hash. Now all transaction hashes exposed are amino hashes.
* Removes `Hash()` (RLP) function from `MsgEthereumTx` to avoid confusion or misuse in future. * Removes `Hash()` (RLP) function from `MsgEthereumTx` to avoid confusion or misuse in future.

View File

@ -163,7 +163,7 @@ test-rpc:
@${GO_MOD} go test -v --vet=off ./tests/rpc_test @${GO_MOD} go test -v --vet=off ./tests/rpc_test
it-tests: it-tests:
./scripts/integration-test-all.sh -q 1 -z 1 -s 10 ./scripts/integration-test-all.sh -q 1 -z 1 -s 2
godocs: godocs:
@echo "--> Wait a few seconds and visit http://localhost:6060/pkg/github.com/cosmos/ethermint" @echo "--> Wait a few seconds and visit http://localhost:6060/pkg/github.com/cosmos/ethermint"

View File

@ -147,10 +147,9 @@ func NewEthermintApp(
keys := sdk.NewKVStoreKeys( keys := sdk.NewKVStoreKeys(
bam.MainStoreKey, auth.StoreKey, bank.StoreKey, staking.StoreKey, bam.MainStoreKey, auth.StoreKey, bank.StoreKey, staking.StoreKey,
supply.StoreKey, mint.StoreKey, distr.StoreKey, slashing.StoreKey, supply.StoreKey, mint.StoreKey, distr.StoreKey, slashing.StoreKey,
gov.StoreKey, params.StoreKey, evidence.StoreKey, evm.CodeKey, evm.StoreKey, gov.StoreKey, params.StoreKey, evidence.StoreKey, evm.StoreKey,
faucet.StoreKey, faucet.StoreKey,
) )
blockKey := sdk.NewKVStoreKey(evm.BlockKey)
tkeys := sdk.NewTransientStoreKeys(params.TStoreKey) tkeys := sdk.NewTransientStoreKeys(params.TStoreKey)
@ -203,7 +202,7 @@ func NewEthermintApp(
app.subspaces[crisis.ModuleName], invCheckPeriod, app.SupplyKeeper, auth.FeeCollectorName, app.subspaces[crisis.ModuleName], invCheckPeriod, app.SupplyKeeper, auth.FeeCollectorName,
) )
app.EvmKeeper = evm.NewKeeper( app.EvmKeeper = evm.NewKeeper(
app.cdc, blockKey, keys[evm.CodeKey], keys[evm.StoreKey], app.AccountKeeper, app.cdc, keys[evm.StoreKey], app.AccountKeeper,
app.BankKeeper, app.BankKeeper,
) )
// TODO: use protobuf // TODO: use protobuf
@ -299,10 +298,6 @@ func NewEthermintApp(
app.MountKVStores(keys) app.MountKVStores(keys)
app.MountTransientStores(tkeys) app.MountTransientStores(tkeys)
// Mount block hash mapping key as DB (no need for historical queries)
// TODO: why does this need to be always StoreTypeDB?
app.MountStore(blockKey, sdk.StoreTypeDB)
// initialize BaseApp // initialize BaseApp
app.SetInitChainer(app.InitChainer) app.SetInitChainer(app.InitChainer)
app.SetBeginBlocker(app.BeginBlocker) app.SetBeginBlocker(app.BeginBlocker)

View File

@ -51,7 +51,6 @@ var (
accKey = sdk.NewKVStoreKey(auth.StoreKey) accKey = sdk.NewKVStoreKey(auth.StoreKey)
storeKey = sdk.NewKVStoreKey(evmtypes.StoreKey) storeKey = sdk.NewKVStoreKey(evmtypes.StoreKey)
codeKey = sdk.NewKVStoreKey(evmtypes.CodeKey)
logger = tmlog.NewNopLogger() logger = tmlog.NewNopLogger()
@ -107,7 +106,7 @@ func createAndTestGenesis(t *testing.T, cms sdk.CommitMultiStore, ak auth.Accoun
ms := cms.CacheMultiStore() ms := cms.CacheMultiStore()
ctx := sdk.NewContext(ms, abci.Header{}, false, logger) ctx := sdk.NewContext(ms, abci.Header{}, false, logger)
stateDB := evmtypes.NewCommitStateDB(ctx, codeKey, storeKey, ak, bk) stateDB := evmtypes.NewCommitStateDB(ctx, storeKey, ak, bk)
// sort the addresses and insertion of key/value pairs matters // sort the addresses and insertion of key/value pairs matters
genAddrs := make([]string, len(genBlock.Alloc)) genAddrs := make([]string, len(genBlock.Alloc))
@ -189,7 +188,7 @@ func TestImportBlocks(t *testing.T) {
bk := bank.NewBaseKeeper(appCodec, bankKey, ak, bankSubspace, nil) bk := bank.NewBaseKeeper(appCodec, bankKey, ak, bankSubspace, nil)
// mount stores // mount stores
keys := []*sdk.KVStoreKey{accKey, bankKey, storeKey, codeKey} keys := []*sdk.KVStoreKey{accKey, bankKey, storeKey}
for _, key := range keys { for _, key := range keys {
cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, nil) cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, nil)
} }
@ -280,7 +279,7 @@ func TestImportBlocks(t *testing.T) {
// nolint: interfacer // nolint: interfacer
func createStateDB(ctx sdk.Context, ak auth.AccountKeeper, bk bank.Keeper) *evmtypes.CommitStateDB { func createStateDB(ctx sdk.Context, ak auth.AccountKeeper, bk bank.Keeper) *evmtypes.CommitStateDB {
return evmtypes.NewCommitStateDB(ctx, codeKey, storeKey, ak, bk) return evmtypes.NewCommitStateDB(ctx, storeKey, ak, bk)
} }
// accumulateRewards credits the coinbase of the given block with the mining // accumulateRewards credits the coinbase of the given block with the mining

View File

@ -118,7 +118,7 @@ func (e *EthermintBackend) getEthBlockByNumber(height int64, fullTx bool) (map[s
} }
} }
res, _, err := e.cliCtx.Query(fmt.Sprintf("custom/%s/%s/%s", types.ModuleName, evm.QueryLogsBloom, strconv.FormatInt(block.Block.Height, 10))) res, _, err := e.cliCtx.Query(fmt.Sprintf("custom/%s/%s/%s", types.ModuleName, evm.QueryBloom, strconv.FormatInt(block.Block.Height, 10)))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -301,10 +301,12 @@ func TestEth_GetTransactionReceipt(t *testing.T) {
param := []string{hash.String()} param := []string{hash.String()}
rpcRes := call(t, "eth_getTransactionReceipt", param) rpcRes := call(t, "eth_getTransactionReceipt", param)
require.Nil(t, rpcRes.Error)
receipt := make(map[string]interface{}) receipt := make(map[string]interface{})
err := json.Unmarshal(rpcRes.Result, &receipt) err := json.Unmarshal(rpcRes.Result, &receipt)
require.NoError(t, err) require.NoError(t, err)
require.NotEmpty(t, receipt)
require.Equal(t, "0x1", receipt["status"].(string)) require.Equal(t, "0x1", receipt["status"].(string))
require.Equal(t, []interface{}{}, receipt["logs"].([]interface{})) require.Equal(t, []interface{}{}, receipt["logs"].([]interface{}))
} }

View File

@ -10,23 +10,23 @@ import (
ethtypes "github.com/ethereum/go-ethereum/core/types" ethtypes "github.com/ethereum/go-ethereum/core/types"
) )
// BeginBlock sets the Bloom and Hash mappings and resets the Bloom filter and // BeginBlock sets the block hash -> block height map and resets the Bloom filter and
// the transaction count to 0. // the transaction count to 0.
func BeginBlock(k Keeper, ctx sdk.Context, req abci.RequestBeginBlock) { func BeginBlock(k Keeper, ctx sdk.Context, req abci.RequestBeginBlock) {
if req.Header.LastBlockId.GetHash() == nil || req.Header.GetHeight() < 1 { if req.Header.LastBlockId.GetHash() == nil || req.Header.GetHeight() < 1 {
return return
} }
// Consider removing this when using evm as module without web3 API k.SetBlockHash(ctx, req.Header.LastBlockId.GetHash(), req.Header.GetHeight()-1)
bloom := ethtypes.BytesToBloom(k.Bloom.Bytes())
k.SetBlockBloomMapping(ctx, bloom, req.Header.GetHeight()) // reset counters
k.SetBlockHashMapping(ctx, req.Header.LastBlockId.GetHash(), req.Header.GetHeight())
k.Bloom = big.NewInt(0) k.Bloom = big.NewInt(0)
k.TxCount = 0 k.TxCount = 0
} }
// EndBlock updates the accounts and commits states objects to the KV Store // EndBlock updates the accounts and commits states objects to the KV Store.
func EndBlock(k Keeper, ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { //
func EndBlock(k Keeper, ctx sdk.Context, req abci.RequestEndBlock) []abci.ValidatorUpdate {
// Gas costs are handled within msg handler so costs should be ignored // Gas costs are handled within msg handler so costs should be ignored
ctx = ctx.WithBlockGasMeter(sdk.NewInfiniteGasMeter()) ctx = ctx.WithBlockGasMeter(sdk.NewInfiniteGasMeter())
@ -42,5 +42,8 @@ func EndBlock(k Keeper, ctx sdk.Context, _ abci.RequestEndBlock) []abci.Validato
// Clear accounts cache after account data has been committed // Clear accounts cache after account data has been committed
k.CommitStateDB.ClearStateObjects() k.CommitStateDB.ClearStateObjects()
bloom := ethtypes.BytesToBloom(k.Bloom.Bytes())
k.SetBlockBloom(ctx, ctx.BlockHeight(), bloom)
return []abci.ValidatorUpdate{} return []abci.ValidatorUpdate{}
} }

View File

@ -9,8 +9,6 @@ import (
const ( const (
ModuleName = types.ModuleName ModuleName = types.ModuleName
StoreKey = types.StoreKey StoreKey = types.StoreKey
CodeKey = types.StoreKey
BlockKey = types.BlockKey
RouterKey = types.RouterKey RouterKey = types.RouterKey
QueryProtocolVersion = types.QueryProtocolVersion QueryProtocolVersion = types.QueryProtocolVersion
QueryBalance = types.QueryBalance QueryBalance = types.QueryBalance
@ -20,7 +18,7 @@ const (
QueryNonce = types.QueryNonce QueryNonce = types.QueryNonce
QueryHashToHeight = types.QueryHashToHeight QueryHashToHeight = types.QueryHashToHeight
QueryTransactionLogs = types.QueryTransactionLogs QueryTransactionLogs = types.QueryTransactionLogs
QueryLogsBloom = types.QueryLogsBloom QueryBloom = types.QueryBloom
QueryLogs = types.QueryLogs QueryLogs = types.QueryLogs
QueryAccount = types.QueryAccount QueryAccount = types.QueryAccount
) )

View File

@ -13,15 +13,33 @@ import (
// InitGenesis initializes genesis state based on exported genesis // InitGenesis initializes genesis state based on exported genesis
func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) []abci.ValidatorUpdate { func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) []abci.ValidatorUpdate {
for _, account := range data.Accounts { for _, account := range data.Accounts {
csdb := k.CommitStateDB.WithContext(ctx)
// FIXME: this will override bank InitGenesis balance! // FIXME: this will override bank InitGenesis balance!
csdb.SetBalance(account.Address, account.Balance) k.SetBalance(ctx, account.Address, account.Balance)
csdb.SetCode(account.Address, account.Code) k.SetCode(ctx, account.Address, account.Code)
for _, storage := range account.Storage { for _, storage := range account.Storage {
csdb.SetState(account.Address, storage.Key, storage.Value) k.SetState(ctx, account.Address, storage.Key, storage.Value)
} }
} }
// TODO: Commit?
var err error
for _, txLog := range data.TxsLogs {
err = k.SetLogs(ctx, txLog.Hash, txLog.Logs)
if err != nil {
panic(err)
}
}
// set state objects and code to store
_, err = k.Commit(ctx, false)
if err != nil {
panic(err)
}
// set storage to store
err = k.Finalise(ctx, true)
if err != nil {
panic(err)
}
return []abci.ValidatorUpdate{} return []abci.ValidatorUpdate{}
} }
@ -59,5 +77,8 @@ func ExportGenesis(ctx sdk.Context, k Keeper, ak types.AccountKeeper) GenesisSta
ethGenAccounts = append(ethGenAccounts, genAccount) ethGenAccounts = append(ethGenAccounts, genAccount)
} }
return GenesisState{Accounts: ethGenAccounts} return GenesisState{
Accounts: ethGenAccounts,
TxsLogs: k.GetAllTxLogs(ctx),
}
} }

View File

@ -74,7 +74,10 @@ func handleMsgEthereumTx(ctx sdk.Context, k Keeper, msg types.MsgEthereumTx) (*s
k.Bloom.Or(k.Bloom, executionResult.Bloom) k.Bloom.Or(k.Bloom, executionResult.Bloom)
// update transaction logs in KVStore // update transaction logs in KVStore
k.SetTransactionLogs(ctx, txHash, executionResult.Logs) err = k.SetLogs(ctx, common.BytesToHash(txHash), executionResult.Logs)
if err != nil {
panic(err)
}
// log successful execution // log successful execution
k.Logger(ctx).Info(executionResult.Result.Log) k.Logger(ctx).Info(executionResult.Result.Log)
@ -147,7 +150,10 @@ func handleMsgEthermint(ctx sdk.Context, k Keeper, msg types.MsgEthermint) (*sdk
k.Bloom.Or(k.Bloom, executionResult.Bloom) k.Bloom.Or(k.Bloom, executionResult.Bloom)
// update transaction logs in KVStore // update transaction logs in KVStore
k.SetTransactionLogs(ctx, txHash, executionResult.Logs) err = k.SetLogs(ctx, common.BytesToHash(txHash), executionResult.Logs)
if err != nil {
panic(err)
}
// log successful execution // log successful execution
k.Logger(ctx).Info(executionResult.Result.Log) k.Logger(ctx).Info(executionResult.Result.Log)

View File

@ -236,9 +236,10 @@ func (suite *EvmTestSuite) TestHandlerLogs() {
suite.Require().Equal(len(resultData.Logs[0].Topics), 2) suite.Require().Equal(len(resultData.Logs[0].Topics), 2)
hash := []byte{1} hash := []byte{1}
suite.app.EvmKeeper.SetTransactionLogs(suite.ctx, hash, resultData.Logs) err = suite.app.EvmKeeper.SetLogs(suite.ctx, ethcmn.BytesToHash(hash), resultData.Logs)
suite.Require().NoError(err)
logs, err := suite.app.EvmKeeper.GetTransactionLogs(suite.ctx, hash) logs, err := suite.app.EvmKeeper.GetLogs(suite.ctx, ethcmn.BytesToHash(hash))
suite.Require().NoError(err, "failed to get logs") suite.Require().NoError(err, "failed to get logs")
suite.Require().Equal(logs, resultData.Logs) suite.Require().Equal(logs, resultData.Logs)
@ -273,7 +274,7 @@ func (suite *EvmTestSuite) TestQueryTxLogs() {
// get logs by tx hash // get logs by tx hash
hash := resultData.TxHash.Bytes() hash := resultData.TxHash.Bytes()
logs, err := suite.app.EvmKeeper.GetTransactionLogs(suite.ctx, hash) logs, err := suite.app.EvmKeeper.GetLogs(suite.ctx, ethcmn.BytesToHash(hash))
suite.Require().NoError(err, "failed to get logs") suite.Require().NoError(err, "failed to get logs")
suite.Require().Equal(logs, resultData.Logs) suite.Require().Equal(logs, resultData.Logs)

View File

@ -2,20 +2,19 @@ package keeper
import ( import (
"encoding/binary" "encoding/binary"
"errors"
"fmt" "fmt"
"math/big"
"github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/libs/log"
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/ethermint/x/evm/types" "github.com/cosmos/ethermint/x/evm/types"
ethcmn "github.com/ethereum/go-ethereum/common" ethcmn "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types" ethtypes "github.com/ethereum/go-ethereum/core/types"
"math/big"
) )
// Keeper wraps the CommitStateDB, allowing us to pass in SDK context while adhering // Keeper wraps the CommitStateDB, allowing us to pass in SDK context while adhering
@ -23,9 +22,13 @@ import (
type Keeper struct { type Keeper struct {
// Amino codec // Amino codec
cdc *codec.Codec cdc *codec.Codec
// Store key required to update the block bloom filter mappings needed for the // Store key required for the EVM Prefix KVStore. It is required by:
// Web3 API // - storing Account's Storage State
blockKey sdk.StoreKey // - storing Account's Code
// - storing transaction Logs
// - storing block height -> bloom filter map. Needed for the Web3 API.
// - storing block hash -> block height map. Needed for the Web3 API.
storeKey sdk.StoreKey
CommitStateDB *types.CommitStateDB CommitStateDB *types.CommitStateDB
// Transaction counter in a block. Used on StateSB's Prepare function. // Transaction counter in a block. Used on StateSB's Prepare function.
// It is reset to 0 every block on BeginBlock so there's no point in storing the counter // It is reset to 0 every block on BeginBlock so there's no point in storing the counter
@ -36,13 +39,12 @@ type Keeper struct {
// NewKeeper generates new evm module keeper // NewKeeper generates new evm module keeper
func NewKeeper( func NewKeeper(
cdc *codec.Codec, blockKey, codeKey, storeKey sdk.StoreKey, cdc *codec.Codec, storeKey sdk.StoreKey, ak types.AccountKeeper, bk types.BankKeeper,
ak types.AccountKeeper, bk types.BankKeeper,
) Keeper { ) Keeper {
return Keeper{ return Keeper{
cdc: cdc, cdc: cdc,
blockKey: blockKey, storeKey: storeKey,
CommitStateDB: types.NewCommitStateDB(sdk.Context{}, codeKey, storeKey, ak, bk), CommitStateDB: types.NewCommitStateDB(sdk.Context{}, storeKey, ak, bk),
TxCount: 0, TxCount: 0,
Bloom: big.NewInt(0), Bloom: big.NewInt(0),
} }
@ -55,68 +57,66 @@ func (k Keeper) Logger(ctx sdk.Context) log.Logger {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Block hash mapping functions // Block hash mapping functions
// May be removed when using only as module (only required by rpc api) // Required by Web3 API.
// TODO: remove once tendermint support block queries by hash.
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// GetBlockHashMapping gets block height from block consensus hash // GetBlockHash gets block height from block consensus hash
func (k Keeper) GetBlockHashMapping(ctx sdk.Context, hash []byte) (int64, error) { func (k Keeper) GetBlockHash(ctx sdk.Context, hash []byte) (int64, bool) {
store := ctx.KVStore(k.blockKey) store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixBlockHash)
bz := store.Get(hash) bz := store.Get(hash)
if len(bz) == 0 { if len(bz) == 0 {
return 0, fmt.Errorf("block with hash '%s' not found", ethcmn.BytesToHash(hash).Hex()) return 0, false
} }
height := binary.BigEndian.Uint64(bz) height := binary.BigEndian.Uint64(bz)
return int64(height), nil return int64(height), true
} }
// SetBlockHashMapping sets the mapping from block consensus hash to block height // SetBlockHash sets the mapping from block consensus hash to block height
func (k Keeper) SetBlockHashMapping(ctx sdk.Context, hash []byte, height int64) { func (k Keeper) SetBlockHash(ctx sdk.Context, hash []byte, height int64) {
store := ctx.KVStore(k.blockKey) store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixBlockHash)
bz := sdk.Uint64ToBigEndian(uint64(height)) bz := sdk.Uint64ToBigEndian(uint64(height))
store.Set(hash, bz) store.Set(hash, bz)
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Block bloom bits mapping functions // Block bloom bits mapping functions
// May be removed when using only as module (only required by rpc api) // Required by Web3 API.
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// GetBlockBloomMapping gets bloombits from block height // GetBlockBloom gets bloombits from block height
func (k Keeper) GetBlockBloomMapping(ctx sdk.Context, height int64) (ethtypes.Bloom, error) { func (k Keeper) GetBlockBloom(ctx sdk.Context, height int64) (ethtypes.Bloom, bool) {
store := ctx.KVStore(k.blockKey) store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixBloom)
heightBz := sdk.Uint64ToBigEndian(uint64(height)) bz := store.Get(types.BloomKey(height))
bz := store.Get(types.BloomKey(heightBz))
if len(bz) == 0 { if len(bz) == 0 {
return ethtypes.Bloom{}, fmt.Errorf("block at height %d not found", height) return ethtypes.Bloom{}, false
} }
return ethtypes.BytesToBloom(bz), nil return ethtypes.BytesToBloom(bz), true
} }
// SetBlockBloomMapping sets the mapping from block height to bloom bits // SetBlockBloom sets the mapping from block height to bloom bits
func (k Keeper) SetBlockBloomMapping(ctx sdk.Context, bloom ethtypes.Bloom, height int64) { func (k Keeper) SetBlockBloom(ctx sdk.Context, height int64, bloom ethtypes.Bloom) {
store := ctx.KVStore(k.blockKey) store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixBloom)
heightBz := sdk.Uint64ToBigEndian(uint64(height)) store.Set(types.BloomKey(height), bloom.Bytes())
store.Set(types.BloomKey(heightBz), bloom.Bytes())
} }
// SetTransactionLogs sets the transaction's logs in the KVStore // GetAllTxLogs return all the transaction logs from the store.
func (k *Keeper) SetTransactionLogs(ctx sdk.Context, hash []byte, logs []*ethtypes.Log) { func (k Keeper) GetAllTxLogs(ctx sdk.Context) []types.TransactionLogs {
store := ctx.KVStore(k.blockKey) store := ctx.KVStore(k.storeKey)
bz := k.cdc.MustMarshalBinaryLengthPrefixed(logs) iterator := sdk.KVStorePrefixIterator(store, types.KeyPrefixLogs)
store.Set(types.LogsKey(hash), bz) defer iterator.Close()
}
// GetTransactionLogs gets the logs for a transaction from the KVStore
func (k *Keeper) GetTransactionLogs(ctx sdk.Context, hash []byte) ([]*ethtypes.Log, error) {
store := ctx.KVStore(k.blockKey)
bz := store.Get(types.LogsKey(hash))
if len(bz) == 0 {
return nil, errors.New("cannot get transaction logs")
}
txsLogs := []types.TransactionLogs{}
for ; iterator.Valid(); iterator.Next() {
hash := ethcmn.BytesToHash(iterator.Key())
var logs []*ethtypes.Log var logs []*ethtypes.Log
k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &logs) k.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &logs)
return logs, nil
// add a new entry
txLog := types.NewTransactionLogs(hash, logs)
txsLogs = append(txsLogs, txLog)
}
return txsLogs
} }

View File

@ -46,22 +46,49 @@ func TestKeeperTestSuite(t *testing.T) {
} }
func (suite *KeeperTestSuite) TestTransactionLogs() { func (suite *KeeperTestSuite) TestTransactionLogs() {
ethHash := ethcmn.BytesToHash(hash)
log := &ethtypes.Log{ log := &ethtypes.Log{
Address: address, Address: address,
Data: []byte("log"), Data: []byte("log"),
BlockNumber: 10, BlockNumber: 10,
} }
log2 := &ethtypes.Log{
Address: address,
Data: []byte("log2"),
BlockNumber: 11,
}
expLogs := []*ethtypes.Log{log} expLogs := []*ethtypes.Log{log}
suite.app.EvmKeeper.SetTransactionLogs(suite.ctx, hash, expLogs) err := suite.app.EvmKeeper.SetLogs(suite.ctx, ethHash, expLogs)
suite.app.EvmKeeper.AddLog(suite.ctx, expLogs[0]) suite.Require().NoError(err)
logs, err := suite.app.EvmKeeper.GetTransactionLogs(suite.ctx, hash) logs, err := suite.app.EvmKeeper.GetLogs(suite.ctx, ethHash)
suite.Require().NoError(err) suite.Require().NoError(err)
suite.Require().Equal(expLogs, logs) suite.Require().Equal(expLogs, logs)
expLogs = []*ethtypes.Log{log2, log}
// add another log under the zero hash
suite.app.EvmKeeper.AddLog(suite.ctx, log2)
logs = suite.app.EvmKeeper.AllLogs(suite.ctx) logs = suite.app.EvmKeeper.AllLogs(suite.ctx)
suite.Require().Equal(expLogs, logs) suite.Require().Equal(expLogs, logs)
// add another log under the zero hash
log3 := &ethtypes.Log{
Address: address,
Data: []byte("log3"),
BlockNumber: 10,
}
suite.app.EvmKeeper.AddLog(suite.ctx, log3)
txLogs := suite.app.EvmKeeper.GetAllTxLogs(suite.ctx)
suite.Require().Equal(2, len(txLogs))
suite.Require().Equal(ethcmn.Hash{}.String(), txLogs[0].Hash.String())
suite.Require().Equal([]*ethtypes.Log{log2, log3}, txLogs[0].Logs)
suite.Require().Equal(ethHash.String(), txLogs[1].Hash.String())
suite.Require().Equal([]*ethtypes.Log{log}, txLogs[1].Logs)
} }
func (suite *KeeperTestSuite) TestDBStorage() { func (suite *KeeperTestSuite) TestDBStorage() {
@ -73,16 +100,16 @@ func (suite *KeeperTestSuite) TestDBStorage() {
suite.app.EvmKeeper.SetCode(suite.ctx, address, []byte{0x1}) suite.app.EvmKeeper.SetCode(suite.ctx, address, []byte{0x1})
// Test block hash mapping functionality // Test block hash mapping functionality
suite.app.EvmKeeper.SetBlockHashMapping(suite.ctx, hash, 7) suite.app.EvmKeeper.SetBlockHash(suite.ctx, hash, 7)
height, err := suite.app.EvmKeeper.GetBlockHashMapping(suite.ctx, hash) height, found := suite.app.EvmKeeper.GetBlockHash(suite.ctx, hash)
suite.Require().NoError(err) suite.Require().True(found)
suite.Require().Equal(int64(7), height) suite.Require().Equal(int64(7), height)
suite.app.EvmKeeper.SetBlockHashMapping(suite.ctx, []byte{0x43, 0x32}, 8) suite.app.EvmKeeper.SetBlockHash(suite.ctx, []byte{0x43, 0x32}, 8)
// Test block height mapping functionality // Test block height mapping functionality
testBloom := ethtypes.BytesToBloom([]byte{0x1, 0x3}) testBloom := ethtypes.BytesToBloom([]byte{0x1, 0x3})
suite.app.EvmKeeper.SetBlockBloomMapping(suite.ctx, testBloom, 4) suite.app.EvmKeeper.SetBlockBloom(suite.ctx, 4, testBloom)
// Get those state transitions // Get those state transitions
suite.Require().Equal(suite.app.EvmKeeper.GetBalance(suite.ctx, address).Cmp(big.NewInt(5)), 0) suite.Require().Equal(suite.app.EvmKeeper.GetBalance(suite.ctx, address).Cmp(big.NewInt(5)), 0)
@ -90,19 +117,19 @@ func (suite *KeeperTestSuite) TestDBStorage() {
suite.Require().Equal(suite.app.EvmKeeper.GetState(suite.ctx, address, ethcmn.HexToHash("0x2")), ethcmn.HexToHash("0x3")) suite.Require().Equal(suite.app.EvmKeeper.GetState(suite.ctx, address, ethcmn.HexToHash("0x2")), ethcmn.HexToHash("0x3"))
suite.Require().Equal(suite.app.EvmKeeper.GetCode(suite.ctx, address), []byte{0x1}) suite.Require().Equal(suite.app.EvmKeeper.GetCode(suite.ctx, address), []byte{0x1})
height, err = suite.app.EvmKeeper.GetBlockHashMapping(suite.ctx, hash) height, found = suite.app.EvmKeeper.GetBlockHash(suite.ctx, hash)
suite.Require().NoError(err) suite.Require().True(found)
suite.Require().Equal(height, int64(7)) suite.Require().Equal(height, int64(7))
height, err = suite.app.EvmKeeper.GetBlockHashMapping(suite.ctx, []byte{0x43, 0x32}) height, found = suite.app.EvmKeeper.GetBlockHash(suite.ctx, []byte{0x43, 0x32})
suite.Require().NoError(err) suite.Require().True(found)
suite.Require().Equal(height, int64(8)) suite.Require().Equal(height, int64(8))
bloom, err := suite.app.EvmKeeper.GetBlockBloomMapping(suite.ctx, 4) bloom, found := suite.app.EvmKeeper.GetBlockBloom(suite.ctx, 4)
suite.Require().NoError(err) suite.Require().True(found)
suite.Require().Equal(bloom, testBloom) suite.Require().Equal(bloom, testBloom)
// commit stateDB // commit stateDB
_, err = suite.app.EvmKeeper.Commit(suite.ctx, false) _, err := suite.app.EvmKeeper.Commit(suite.ctx, false)
suite.Require().NoError(err, "failed to commit StateDB") suite.Require().NoError(err, "failed to commit StateDB")
// simulate BaseApp EndBlocker commitment // simulate BaseApp EndBlocker commitment

View File

@ -36,8 +36,8 @@ func NewQuerier(keeper Keeper) sdk.Querier {
return queryHashToHeight(ctx, path, keeper) return queryHashToHeight(ctx, path, keeper)
case types.QueryTransactionLogs: case types.QueryTransactionLogs:
return queryTransactionLogs(ctx, path, keeper) return queryTransactionLogs(ctx, path, keeper)
case types.QueryLogsBloom: case types.QueryBloom:
return queryBlockLogsBloom(ctx, path, keeper) return queryBlockBloom(ctx, path, keeper)
case types.QueryLogs: case types.QueryLogs:
return queryLogs(ctx, keeper) return queryLogs(ctx, keeper)
case types.QueryAccount: case types.QueryAccount:
@ -113,9 +113,9 @@ func queryCode(ctx sdk.Context, path []string, keeper Keeper) ([]byte, error) {
func queryHashToHeight(ctx sdk.Context, path []string, keeper Keeper) ([]byte, error) { func queryHashToHeight(ctx sdk.Context, path []string, keeper Keeper) ([]byte, error) {
blockHash := ethcmn.FromHex(path[1]) blockHash := ethcmn.FromHex(path[1])
blockNumber, err := keeper.GetBlockHashMapping(ctx, blockHash) blockNumber, found := keeper.GetBlockHash(ctx, blockHash)
if err != nil { if !found {
return []byte{}, err return []byte{}, fmt.Errorf("block height not found for hash %s", path[1])
} }
res := types.QueryResBlockNumber{Number: blockNumber} res := types.QueryResBlockNumber{Number: blockNumber}
@ -127,15 +127,15 @@ func queryHashToHeight(ctx sdk.Context, path []string, keeper Keeper) ([]byte, e
return bz, nil return bz, nil
} }
func queryBlockLogsBloom(ctx sdk.Context, path []string, keeper Keeper) ([]byte, error) { func queryBlockBloom(ctx sdk.Context, path []string, keeper Keeper) ([]byte, error) {
num, err := strconv.ParseInt(path[1], 10, 64) num, err := strconv.ParseInt(path[1], 10, 64)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not unmarshal block number: %w", err) return nil, fmt.Errorf("could not unmarshal block height: %w", err)
} }
bloom, err := keeper.GetBlockBloomMapping(ctx, num) bloom, found := keeper.GetBlockBloom(ctx, num)
if err != nil { if !found {
return nil, fmt.Errorf("failed to get block bloom mapping: %w", err) return nil, fmt.Errorf("block bloom not found for height %d", num)
} }
res := types.QueryBloomFilter{Bloom: bloom} res := types.QueryBloomFilter{Bloom: bloom}

View File

@ -46,6 +46,11 @@ func (k *Keeper) SetCode(ctx sdk.Context, addr ethcmn.Address, code []byte) {
k.CommitStateDB.WithContext(ctx).SetCode(addr, code) k.CommitStateDB.WithContext(ctx).SetCode(addr, code)
} }
// SetLogs calls CommitStateDB.SetLogs using the passed in context
func (k *Keeper) SetLogs(ctx sdk.Context, hash ethcmn.Hash, logs []*ethtypes.Log) error {
return k.CommitStateDB.WithContext(ctx).SetLogs(hash, logs)
}
// AddLog calls CommitStateDB.AddLog using the passed in context // AddLog calls CommitStateDB.AddLog using the passed in context
func (k *Keeper) AddLog(ctx sdk.Context, log *ethtypes.Log) { func (k *Keeper) AddLog(ctx sdk.Context, log *ethtypes.Log) {
k.CommitStateDB.WithContext(ctx).AddLog(log) k.CommitStateDB.WithContext(ctx).AddLog(log)
@ -149,7 +154,7 @@ func (k *Keeper) StorageTrie(ctx sdk.Context, addr ethcmn.Address) ethstate.Trie
// Persistence // Persistence
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Commit calls CommitStateDB.Commit using the passed { in context // Commit calls CommitStateDB.Commit using the passed in context
func (k *Keeper) Commit(ctx sdk.Context, deleteEmptyObjects bool) (root ethcmn.Hash, err error) { func (k *Keeper) Commit(ctx sdk.Context, deleteEmptyObjects bool) (root ethcmn.Hash, err error) {
return k.CommitStateDB.WithContext(ctx).Commit(deleteEmptyObjects) return k.CommitStateDB.WithContext(ctx).Commit(deleteEmptyObjects)
} }

View File

@ -11,10 +11,10 @@ import (
) )
type ( type (
// GenesisState defines the application's genesis state. It contains all the // GenesisState defines the evm module genesis state
// information required and accounts to initialize the blockchain.
GenesisState struct { GenesisState struct {
Accounts []GenesisAccount `json:"accounts"` Accounts []GenesisAccount `json:"accounts"`
TxsLogs []TransactionLogs `json:"txs_logs"`
} }
// GenesisStorage represents the GenesisAccount Storage map as single key value // GenesisStorage represents the GenesisAccount Storage map as single key value
@ -76,6 +76,7 @@ func NewGenesisStorage(key, value ethcmn.Hash) GenesisStorage {
func DefaultGenesisState() GenesisState { func DefaultGenesisState() GenesisState {
return GenesisState{ return GenesisState{
Accounts: []GenesisAccount{}, Accounts: []GenesisAccount{},
TxsLogs: []TransactionLogs{},
} }
} }
@ -83,6 +84,7 @@ func DefaultGenesisState() GenesisState {
// failure. // failure.
func (gs GenesisState) Validate() error { func (gs GenesisState) Validate() error {
seenAccounts := make(map[string]bool) seenAccounts := make(map[string]bool)
seenTxs := make(map[string]bool)
for _, acc := range gs.Accounts { for _, acc := range gs.Accounts {
if seenAccounts[acc.Address.String()] { if seenAccounts[acc.Address.String()] {
return fmt.Errorf("duplicated genesis account %s", acc.Address.String()) return fmt.Errorf("duplicated genesis account %s", acc.Address.String())
@ -92,5 +94,17 @@ func (gs GenesisState) Validate() error {
} }
seenAccounts[acc.Address.String()] = true seenAccounts[acc.Address.String()] = true
} }
for _, tx := range gs.TxsLogs {
if seenTxs[tx.Hash.String()] {
return fmt.Errorf("duplicated logs from transaction %s", tx.Hash.String())
}
if err := tx.Validate(); err != nil {
return fmt.Errorf("invalid logs from transaction %s: %w", tx.Hash.String(), err)
}
seenTxs[tx.Hash.String()] = true
}
return nil return nil
} }

View File

@ -204,11 +204,16 @@ func (ch refundChange) dirtied() *ethcmn.Address {
} }
func (ch addLogChange) revert(s *CommitStateDB) { func (ch addLogChange) revert(s *CommitStateDB) {
logs := s.logs[ch.txhash] logs, err := s.GetLogs(ch.txhash)
if err != nil {
// panic on unmarshal error
panic(err)
}
if len(logs) == 1 { if len(logs) == 1 {
delete(s.logs, ch.txhash) s.DeleteLogs(ch.txhash)
} else { } else if err := s.SetLogs(ch.txhash, logs[:len(logs)-1]); err != nil {
s.logs[ch.txhash] = logs[:len(logs)-1] panic(err)
} }
s.logSize-- s.logSize--

View File

@ -1,27 +1,38 @@
package types package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
ethcmn "github.com/ethereum/go-ethereum/common"
)
const ( const (
// ModuleName string name of module // ModuleName string name of module
ModuleName = "evm" ModuleName = "evm"
// StoreKey key for ethereum storage data (StateDB) // StoreKey key for ethereum storage data, account code (StateDB) or block
// related data for Web3.
// The EVM module should use a prefix store.
StoreKey = ModuleName StoreKey = ModuleName
// CodeKey key for ethereum code data
CodeKey = ModuleName + "code"
// BlockKey key
BlockKey = ModuleName + "block"
// RouterKey uses module name for routing // RouterKey uses module name for routing
RouterKey = ModuleName RouterKey = ModuleName
) )
var bloomPrefix = []byte("bloom") // KVStore key prefixes
var logsPrefix = []byte("logs") var (
KeyPrefixBlockHash = []byte{0x01}
KeyPrefixBloom = []byte{0x02}
KeyPrefixLogs = []byte{0x03}
KeyPrefixCode = []byte{0x04}
KeyPrefixStorage = []byte{0x05}
)
func BloomKey(key []byte) []byte { // BloomKey defines the store key for a block Bloom
return append(bloomPrefix, key...) func BloomKey(height int64) []byte {
return sdk.Uint64ToBigEndian(uint64(height))
} }
func LogsKey(key []byte) []byte { // AddressStoragePrefix returns a prefix to iterate over a given account storage.
return append(logsPrefix, key...) func AddressStoragePrefix(address ethcmn.Address) []byte {
return append(KeyPrefixStorage, address.Bytes()...)
} }

75
x/evm/types/logs.go Normal file
View File

@ -0,0 +1,75 @@
package types
import (
"bytes"
"errors"
"fmt"
ethcmn "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
)
// TransactionLogs define the logs generated from a transaction execution
// with a given hash. It it used for import/export data as transactions are not persisted
// on blockchain state after an upgrade.
type TransactionLogs struct {
Hash ethcmn.Hash `json:"hash"`
Logs []*ethtypes.Log `json:"logs"`
}
// NewTransactionLogs creates a new NewTransactionLogs instance.
func NewTransactionLogs(hash ethcmn.Hash, logs []*ethtypes.Log) TransactionLogs {
return TransactionLogs{
Hash: hash,
Logs: logs,
}
}
// MarshalLogs encodes an array of logs using amino
func MarshalLogs(logs []*ethtypes.Log) ([]byte, error) {
return ModuleCdc.MarshalBinaryLengthPrefixed(logs)
}
// UnmarshalLogs decodes an amino-encoded byte array into an array of logs
func UnmarshalLogs(in []byte) ([]*ethtypes.Log, error) {
logs := []*ethtypes.Log{}
err := ModuleCdc.UnmarshalBinaryLengthPrefixed(in, &logs)
return logs, err
}
// Validate performs a basic validation of a GenesisAccount fields.
func (tx TransactionLogs) Validate() error {
if bytes.Equal(tx.Hash.Bytes(), ethcmn.Hash{}.Bytes()) {
return fmt.Errorf("hash cannot be the empty %s", tx.Hash.String())
}
for i, log := range tx.Logs {
if err := ValidateLog(log); err != nil {
return fmt.Errorf("invalid log %d: %w", i, err)
}
if bytes.Equal(log.TxHash.Bytes(), tx.Hash.Bytes()) {
return fmt.Errorf("log tx hash mismatch (%s ≠ %s)", log.TxHash.String(), tx.Hash.String())
}
}
return nil
}
// ValidateLog performs a basic validation of an ethereum Log fields.
func ValidateLog(log *ethtypes.Log) error {
if log == nil {
return errors.New("log cannot be nil")
}
if bytes.Equal(log.Address.Bytes(), ethcmn.Address{}.Bytes()) {
return fmt.Errorf("log address cannot be empty %s", log.Address.String())
}
if bytes.Equal(log.BlockHash.Bytes(), ethcmn.Hash{}.Bytes()) {
return fmt.Errorf("block hash cannot be the empty %s", log.BlockHash.String())
}
if log.BlockNumber == 0 {
return errors.New("block number cannot be zero")
}
if bytes.Equal(log.TxHash.Bytes(), ethcmn.Hash{}.Bytes()) {
return fmt.Errorf("tx hash cannot be the empty %s", log.TxHash.String())
}
return nil
}

View File

@ -16,7 +16,7 @@ const (
QueryNonce = "nonce" QueryNonce = "nonce"
QueryHashToHeight = "hashToHeight" QueryHashToHeight = "hashToHeight"
QueryTransactionLogs = "transactionLogs" QueryTransactionLogs = "transactionLogs"
QueryLogsBloom = "logsBloom" QueryBloom = "bloom"
QueryLogs = "logs" QueryLogs = "logs"
QueryAccount = "account" QueryAccount = "account"
) )

View File

@ -6,6 +6,7 @@ import (
"io" "io"
"math/big" "math/big"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
@ -220,7 +221,7 @@ func (so *stateObject) markSuicided() {
// commitState commits all dirty storage to a KVStore. // commitState commits all dirty storage to a KVStore.
func (so *stateObject) commitState() { func (so *stateObject) commitState() {
ctx := so.stateDB.ctx ctx := so.stateDB.ctx
store := ctx.KVStore(so.stateDB.storeKey) store := prefix.NewStore(ctx.KVStore(so.stateDB.storeKey), KeyPrefixStorage)
for key, value := range so.dirtyStorage { for key, value := range so.dirtyStorage {
delete(so.dirtyStorage, key) delete(so.dirtyStorage, key)
@ -240,14 +241,12 @@ func (so *stateObject) commitState() {
store.Set(key.Bytes(), value.Bytes()) store.Set(key.Bytes(), value.Bytes())
} }
// TODO: Set the account (storage) root (but we probably don't need this)
} }
// commitCode persists the state object's code to the KVStore. // commitCode persists the state object's code to the KVStore.
func (so *stateObject) commitCode() { func (so *stateObject) commitCode() {
ctx := so.stateDB.ctx ctx := so.stateDB.ctx
store := ctx.KVStore(so.stateDB.codeKey) store := prefix.NewStore(ctx.KVStore(so.stateDB.storeKey), KeyPrefixCode)
store.Set(so.CodeHash(), so.code) store.Set(so.CodeHash(), so.code)
} }
@ -296,7 +295,7 @@ func (so *stateObject) Code(_ ethstate.Database) []byte {
} }
ctx := so.stateDB.ctx ctx := so.stateDB.ctx
store := ctx.KVStore(so.stateDB.codeKey) store := prefix.NewStore(ctx.KVStore(so.stateDB.storeKey), KeyPrefixCode)
code := store.Get(so.CodeHash()) code := store.Get(so.CodeHash())
if len(code) == 0 { if len(code) == 0 {
@ -334,7 +333,7 @@ func (so *stateObject) GetCommittedState(_ ethstate.Database, key ethcmn.Hash) e
// otherwise load the value from the KVStore // otherwise load the value from the KVStore
ctx := so.stateDB.ctx ctx := so.stateDB.ctx
store := ctx.KVStore(so.stateDB.storeKey) store := prefix.NewStore(ctx.KVStore(so.stateDB.storeKey), KeyPrefixStorage)
rawValue := store.Get(prefixKey.Bytes()) rawValue := store.Get(prefixKey.Bytes())
if len(rawValue) > 0 { if len(rawValue) > 0 {

View File

@ -6,6 +6,7 @@ import (
"sort" "sort"
"sync" "sync"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
emint "github.com/cosmos/ethermint/types" emint "github.com/cosmos/ethermint/types"
@ -40,8 +41,7 @@ type CommitStateDB struct {
// StateDB interface. Perhaps there is a better way. // StateDB interface. Perhaps there is a better way.
ctx sdk.Context ctx sdk.Context
codeKey sdk.StoreKey storeKey sdk.StoreKey
storeKey sdk.StoreKey // i.e storage key
accountKeeper AccountKeeper accountKeeper AccountKeeper
bankKeeper BankKeeper bankKeeper BankKeeper
@ -55,7 +55,6 @@ type CommitStateDB struct {
thash, bhash ethcmn.Hash thash, bhash ethcmn.Hash
txIndex int txIndex int
logs map[ethcmn.Hash][]*ethtypes.Log
logSize uint logSize uint
// TODO: Determine if we actually need this as we do not need preimages in // TODO: Determine if we actually need this as we do not need preimages in
@ -85,17 +84,15 @@ type CommitStateDB struct {
// CONTRACT: Stores used for state must be cache-wrapped as the ordering of the // CONTRACT: Stores used for state must be cache-wrapped as the ordering of the
// key/value space matters in determining the merkle root. // key/value space matters in determining the merkle root.
func NewCommitStateDB( func NewCommitStateDB(
ctx sdk.Context, codeKey, storeKey sdk.StoreKey, ak AccountKeeper, bk BankKeeper, ctx sdk.Context, storeKey sdk.StoreKey, ak AccountKeeper, bk BankKeeper,
) *CommitStateDB { ) *CommitStateDB {
return &CommitStateDB{ return &CommitStateDB{
ctx: ctx, ctx: ctx,
codeKey: codeKey,
storeKey: storeKey, storeKey: storeKey,
accountKeeper: ak, accountKeeper: ak,
bankKeeper: bk, bankKeeper: bk,
stateObjects: make(map[ethcmn.Address]*stateObject), stateObjects: make(map[ethcmn.Address]*stateObject),
stateObjectsDirty: make(map[ethcmn.Address]struct{}), stateObjectsDirty: make(map[ethcmn.Address]struct{}),
logs: make(map[ethcmn.Hash][]*ethtypes.Log),
preimages: make(map[ethcmn.Hash][]byte), preimages: make(map[ethcmn.Hash][]byte),
journal: newJournal(), journal: newJournal(),
} }
@ -159,20 +156,30 @@ func (csdb *CommitStateDB) SetCode(addr ethcmn.Address, code []byte) {
} }
} }
// ----------------------------------------------------------------------------
// 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. // SetLogs sets the logs for a transaction in the KVStore.
func (csdb *CommitStateDB) SetLogs(hash ethcmn.Hash, logs []*ethtypes.Log) error { func (csdb *CommitStateDB) SetLogs(hash ethcmn.Hash, logs []*ethtypes.Log) error {
store := csdb.ctx.KVStore(csdb.storeKey) store := prefix.NewStore(csdb.ctx.KVStore(csdb.storeKey), KeyPrefixLogs)
enc, err := EncodeLogs(logs) bz, err := MarshalLogs(logs)
if err != nil { if err != nil {
return err return err
} }
if len(enc) == 0 { store.Set(hash.Bytes(), bz)
csdb.logSize = uint(len(logs))
return nil return nil
} }
store.Set(LogsKey(hash[:]), enc) // DeleteLogs removes the logs from the KVStore. It is used during journal.Revert.
return nil 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. // AddLog adds a new log to the state and sets the log metadata from the state.
@ -183,8 +190,17 @@ func (csdb *CommitStateDB) AddLog(log *ethtypes.Log) {
log.BlockHash = csdb.bhash log.BlockHash = csdb.bhash
log.TxIndex = uint(csdb.txIndex) log.TxIndex = uint(csdb.txIndex)
log.Index = csdb.logSize log.Index = csdb.logSize
csdb.logs[csdb.thash] = append(csdb.logs[csdb.thash], log)
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. // AddPreimage records a SHA3 preimage seen by the VM.
@ -308,30 +324,30 @@ func (csdb *CommitStateDB) GetCommittedState(addr ethcmn.Address, hash ethcmn.Ha
// GetLogs returns the current logs for a given transaction hash from the KVStore. // GetLogs returns the current logs for a given transaction hash from the KVStore.
func (csdb *CommitStateDB) GetLogs(hash ethcmn.Hash) ([]*ethtypes.Log, error) { func (csdb *CommitStateDB) GetLogs(hash ethcmn.Hash) ([]*ethtypes.Log, error) {
if csdb.logs[hash] != nil { store := prefix.NewStore(csdb.ctx.KVStore(csdb.storeKey), KeyPrefixLogs)
return csdb.logs[hash], nil bz := store.Get(hash.Bytes())
} if len(bz) == 0 {
// return nil error if logs are not found
store := csdb.ctx.KVStore(csdb.storeKey)
encLogs := store.Get(LogsKey(hash[:]))
if len(encLogs) == 0 {
// return nil if logs are not found
return []*ethtypes.Log{}, nil return []*ethtypes.Log{}, nil
} }
return DecodeLogs(encLogs) return UnmarshalLogs(bz)
} }
// AllLogs returns all the current logs in the state. // AllLogs returns all the current logs in the state.
func (csdb *CommitStateDB) AllLogs() []*ethtypes.Log { func (csdb *CommitStateDB) AllLogs() []*ethtypes.Log {
// nolint: prealloc store := csdb.ctx.KVStore(csdb.storeKey)
iterator := sdk.KVStorePrefixIterator(store, KeyPrefixLogs)
defer iterator.Close()
allLogs := []*ethtypes.Log{}
for ; iterator.Valid(); iterator.Next() {
var logs []*ethtypes.Log var logs []*ethtypes.Log
for _, lgs := range csdb.logs { ModuleCdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &logs)
logs = append(logs, lgs...) allLogs = append(allLogs, logs...)
} }
return logs return allLogs
} }
// GetRefund returns the current value of the refund counter. // GetRefund returns the current value of the refund counter.
@ -576,7 +592,6 @@ func (csdb *CommitStateDB) Reset(_ ethcmn.Hash) error {
csdb.thash = ethcmn.Hash{} csdb.thash = ethcmn.Hash{}
csdb.bhash = ethcmn.Hash{} csdb.bhash = ethcmn.Hash{}
csdb.txIndex = 0 csdb.txIndex = 0
csdb.logs = make(map[ethcmn.Hash][]*ethtypes.Log)
csdb.logSize = 0 csdb.logSize = 0
csdb.preimages = make(map[ethcmn.Hash][]byte) csdb.preimages = make(map[ethcmn.Hash][]byte)
@ -651,14 +666,12 @@ func (csdb *CommitStateDB) Copy() *CommitStateDB {
// copy all the basic fields, initialize the memory ones // copy all the basic fields, initialize the memory ones
state := &CommitStateDB{ state := &CommitStateDB{
ctx: csdb.ctx, ctx: csdb.ctx,
codeKey: csdb.codeKey,
storeKey: csdb.storeKey, storeKey: csdb.storeKey,
accountKeeper: csdb.accountKeeper, accountKeeper: csdb.accountKeeper,
bankKeeper: csdb.bankKeeper, bankKeeper: csdb.bankKeeper,
stateObjects: make(map[ethcmn.Address]*stateObject, len(csdb.journal.dirties)), stateObjects: make(map[ethcmn.Address]*stateObject, len(csdb.journal.dirties)),
stateObjectsDirty: make(map[ethcmn.Address]struct{}, len(csdb.journal.dirties)), stateObjectsDirty: make(map[ethcmn.Address]struct{}, len(csdb.journal.dirties)),
refund: csdb.refund, refund: csdb.refund,
logs: make(map[ethcmn.Hash][]*ethtypes.Log, len(csdb.logs)),
logSize: csdb.logSize, logSize: csdb.logSize,
preimages: make(map[ethcmn.Hash][]byte), preimages: make(map[ethcmn.Hash][]byte),
journal: newJournal(), journal: newJournal(),
@ -687,16 +700,6 @@ func (csdb *CommitStateDB) Copy() *CommitStateDB {
} }
} }
// copy logs
for hash, logs := range csdb.logs {
cpy := make([]*ethtypes.Log, len(logs))
for i, l := range logs {
cpy[i] = new(ethtypes.Log)
*cpy[i] = *l
}
state.logs[hash] = cpy
}
// copy pre-images // copy pre-images
for hash, preimage := range csdb.preimages { for hash, preimage := range csdb.preimages {
state.preimages[hash] = preimage state.preimages[hash] = preimage
@ -714,12 +717,12 @@ func (csdb *CommitStateDB) ForEachStorage(addr ethcmn.Address, cb func(key, valu
} }
store := csdb.ctx.KVStore(csdb.storeKey) store := csdb.ctx.KVStore(csdb.storeKey)
iter := sdk.KVStorePrefixIterator(store, so.Address().Bytes()) iterator := sdk.KVStorePrefixIterator(store, AddressStoragePrefix(so.Address()))
defer iter.Close() defer iterator.Close()
for ; iter.Valid(); iter.Next() { for ; iterator.Valid(); iterator.Next() {
key := ethcmn.BytesToHash(iter.Key()) key := ethcmn.BytesToHash(iterator.Key())
value := iter.Value() value := ethcmn.BytesToHash(iterator.Value())
if value, dirty := so.dirtyStorage[key]; dirty { if value, dirty := so.dirtyStorage[key]; dirty {
// check if iteration stops // check if iteration stops
@ -731,7 +734,7 @@ func (csdb *CommitStateDB) ForEachStorage(addr ethcmn.Address, cb func(key, valu
} }
// check if iteration stops // check if iteration stops
if cb(key, ethcmn.BytesToHash(value)) { if cb(key, value) {
break break
} }
} }

View File

@ -88,21 +88,6 @@ func DecodeResultData(in []byte) (ResultData, error) {
return data, nil return data, nil
} }
// EncodeLogs encodes an array of logs using amino
func EncodeLogs(logs []*ethtypes.Log) ([]byte, error) {
return ModuleCdc.MarshalBinaryLengthPrefixed(logs)
}
// DecodeLogs decodes an amino-encoded byte array into an array of logs
func DecodeLogs(in []byte) ([]*ethtypes.Log, error) {
logs := []*ethtypes.Log{}
err := ModuleCdc.UnmarshalBinaryLengthPrefixed(in, &logs)
if err != nil {
return nil, err
}
return logs, nil
}
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Auxiliary // Auxiliary