Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Problem: transient store usage not compatible with parallel tx execution #450

Merged
merged 3 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (statedb) [#446](https://github.com/crypto-org-chain/ethermint/pull/446) Re-use the cache store implementation with sdk.
* (evm) [#447](https://github.com/crypto-org-chain/ethermint/pull/447) Deduct fee through virtual bank transfer.
* (evm) [#448](https://github.com/crypto-org-chain/ethermint/pull/448) Refactor the evm transfer to be more efficient.
* (evm) [#450](https://github.com/crypto-org-chain/ethermint/pull/450) Refactor transient stores to be compatible with parallel tx execution.

### State Machine Breaking

Expand Down
2 changes: 1 addition & 1 deletion app/ante/handler_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func newEthAnteHandler(options HandlerOptions) sdk.AnteHandler {
}

// We need to setup an empty gas config so that the gas is consistent with Ethereum.
ctx, err = SetupEthContext(ctx, options.EvmKeeper)
ctx, err = SetupEthContext(ctx)
if err != nil {
return ctx, err
}
Expand Down
2 changes: 0 additions & 2 deletions app/ante/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@ type EVMKeeper interface {
ChainID() *big.Int

DeductTxCostsFromUserBalance(ctx sdk.Context, fees sdk.Coins, from common.Address) error
ResetTransientGasUsed(ctx sdk.Context)
GetTxIndexTransient(ctx sdk.Context) uint64
}

type protoTxProvider interface {
Expand Down
6 changes: 1 addition & 5 deletions app/ante/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,12 @@ import (

// SetupEthContext is adapted from SetUpContextDecorator from cosmos-sdk, it ignores gas consumption
// by setting the gas meter to infinite
func SetupEthContext(ctx sdk.Context, evmKeeper EVMKeeper) (newCtx sdk.Context, err error) {
func SetupEthContext(ctx sdk.Context) (newCtx sdk.Context, err error) {
// We need to setup an empty gas config so that the gas is consistent with Ethereum.
newCtx = ctx.WithGasMeter(storetypes.NewInfiniteGasMeter()).
WithKVGasConfig(storetypes.GasConfig{}).
WithTransientKVGasConfig(storetypes.GasConfig{})

// Reset transient gas used to prepare the execution of current cosmos tx.
// Transient gas-used is necessary to sum the gas-used of cosmos tx, when it contains multiple eth msgs.
evmKeeper.ResetTransientGasUsed(ctx)

return newCtx, nil
}

Expand Down
2 changes: 1 addition & 1 deletion app/ante/setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func (suite *AnteTestSuite) TestEthSetupContextDecorator() {

for _, tc := range testCases {
suite.Run(tc.name, func() {
ctx, err := ante.SetupEthContext(suite.ctx, suite.app.EvmKeeper)
ctx, err := ante.SetupEthContext(suite.ctx)

if tc.expPass {
suite.Require().NoError(err)
Expand Down
1 change: 1 addition & 0 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,7 @@ func NewEthermintApp(
bApp.SetVersion(version.Version)
bApp.SetInterfaceRegistry(interfaceRegistry)
bApp.SetTxEncoder(txConfig.TxEncoder())
bApp.SetTxExecutor(DefaultTxExecutor)

keys := storetypes.NewKVStoreKeys(
// SDK keys
Expand Down
21 changes: 21 additions & 0 deletions app/executor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package app

import (
"context"

storetypes "cosmossdk.io/store/types"
abci "github.com/cometbft/cometbft/abci/types"
evmtypes "github.com/evmos/ethermint/x/evm/types"
)

func DefaultTxExecutor(_ context.Context,
blockSize int,
ms storetypes.MultiStore,
deliverTxWithMultiStore func(int, storetypes.MultiStore) *abci.ExecTxResult,
) ([]*abci.ExecTxResult, error) {
results := make([]*abci.ExecTxResult, blockSize)
for i := 0; i < blockSize; i++ {
results[i] = deliverTxWithMultiStore(i, ms)
}

Check warning on line 19 in app/executor.go

View check run for this annotation

Codecov / codecov/patch

app/executor.go#L18-L19

Added lines #L18 - L19 were not covered by tests
return evmtypes.PatchTxResponses(results), nil
yihuang marked this conversation as resolved.
Show resolved Hide resolved
}
8 changes: 4 additions & 4 deletions tests/importer/importer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,10 @@ func (suite *ImporterTestSuite) TestImportBlocks() {
applyDAOHardFork(vmdb)
}

for _, tx := range block.Transactions() {
for i, tx := range block.Transactions() {

receipt, gas, err := applyTransaction(
ctx, chainConfig, chainContext, nil, gp, suite.app.EvmKeeper, vmdb, header, tx, usedGas, vmConfig,
ctx, chainConfig, chainContext, nil, gp, suite.app.EvmKeeper, vmdb, header, tx, usedGas, vmConfig, uint(i),
)
suite.Require().NoError(err, "failed to apply tx at block %d; tx: %X; gas %d; receipt:%v", block.NumberU64(), tx.Hash(), gas, receipt)
suite.Require().NotNil(receipt)
Expand Down Expand Up @@ -230,7 +230,7 @@ func applyDAOHardFork(vmdb ethvm.StateDB) {
func applyTransaction(
ctx sdk.Context, config *ethparams.ChainConfig, bc ethcore.ChainContext, author *common.Address,
gp *ethcore.GasPool, evmKeeper *evmkeeper.Keeper, vmdb *statedb.StateDB, header *ethtypes.Header,
tx *ethtypes.Transaction, usedGas *uint64, cfg ethvm.Config,
tx *ethtypes.Transaction, usedGas *uint64, cfg ethvm.Config, index uint,
) (*ethtypes.Receipt, uint64, error) {
msg, err := ethcore.TransactionToMessage(tx, ethtypes.MakeSigner(config, header.Number), sdkmath.ZeroInt().BigInt())
if err != nil {
Expand Down Expand Up @@ -271,7 +271,7 @@ func applyTransaction(
receipt.Bloom = ethtypes.CreateBloom(ethtypes.Receipts{receipt})
receipt.BlockHash = header.Hash()
receipt.BlockNumber = header.Number
receipt.TransactionIndex = uint(evmKeeper.GetTxIndexTransient(ctx))
receipt.TransactionIndex = index

return receipt, execResult.UsedGas, err
}
7 changes: 1 addition & 6 deletions x/evm/keeper/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ import (
"cosmossdk.io/store/types"

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

ethtypes "github.com/ethereum/go-ethereum/core/types"
)

// BeginBlock sets the sdk Context and EIP155 chain id to the Keeper.
Expand All @@ -35,9 +33,6 @@ func (k *Keeper) BeginBlock(ctx sdk.Context) error {
func (k *Keeper) EndBlock(ctx sdk.Context) error {
// Gas costs are handled within msg handler so costs should be ignored
infCtx := ctx.WithGasMeter(types.NewInfiniteGasMeter())

bloom := ethtypes.BytesToBloom(k.GetBlockBloomTransient(infCtx).Bytes())
k.EmitBlockBloomEvent(infCtx, bloom)

k.CollectTxBloom(infCtx)
return nil
}
27 changes: 27 additions & 0 deletions x/evm/keeper/bloom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package keeper

import (
"math/big"

"cosmossdk.io/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/evmos/ethermint/x/evm/types"
)

func (k Keeper) SetTxBloom(ctx sdk.Context, bloom []byte) {
store := ctx.KVStore(k.transientKey)
store.Set(types.TransientBloomKey(ctx.TxIndex(), ctx.MsgIndex()), bloom)
}

func (k Keeper) CollectTxBloom(ctx sdk.Context) {
store := prefix.NewStore(ctx.KVStore(k.transientKey), types.KeyPrefixTransientBloom)
it := store.Iterator(nil, nil)
defer it.Close()

bloom := new(big.Int)
for ; it.Valid(); it.Next() {
bloom.Or(bloom, big.NewInt(0).SetBytes(it.Value()))
}

k.EmitBlockBloomEvent(ctx, bloom.Bytes())
}
3 changes: 1 addition & 2 deletions x/evm/keeper/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,7 @@ func (k *Keeper) TxConfig(ctx sdk.Context, txHash common.Hash) statedb.TxConfig
return statedb.NewTxConfig(
common.BytesToHash(ctx.HeaderHash()), // BlockHash
txHash, // TxHash
uint(k.GetTxIndexTransient(ctx)), // TxIndex
uint(k.GetLogSizeTransient(ctx)), // LogIndex
0, 0,
)
}

Expand Down
57 changes: 4 additions & 53 deletions x/evm/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,11 @@ func (k Keeper) ChainID() *big.Int {
// ----------------------------------------------------------------------------

// EmitBlockBloomEvent emit block bloom events
func (k Keeper) EmitBlockBloomEvent(ctx sdk.Context, bloom ethtypes.Bloom) {
func (k Keeper) EmitBlockBloomEvent(ctx sdk.Context, bloom []byte) {
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeBlockBloom,
sdk.NewAttribute(types.AttributeKeyEthereumBloom, string(bloom.Bytes())),
sdk.NewAttribute(types.AttributeKeyEthereumBloom, string(bloom)),
),
)
}
Expand Down Expand Up @@ -180,49 +180,6 @@ func (k Keeper) SetBlockBloomTransient(ctx sdk.Context, bloom *big.Int) {
store.Set(heightBz, bloom.Bytes())
}

// ----------------------------------------------------------------------------
// Tx
// ----------------------------------------------------------------------------

// SetTxIndexTransient set the index of processing transaction
func (k Keeper) SetTxIndexTransient(ctx sdk.Context, index uint64) {
store := ctx.TransientStore(k.transientKey)
store.Set(types.KeyPrefixTransientTxIndex, sdk.Uint64ToBigEndian(index))
}

// GetTxIndexTransient returns EVM transaction index on the current block.
func (k Keeper) GetTxIndexTransient(ctx sdk.Context) uint64 {
store := ctx.TransientStore(k.transientKey)
bz := store.Get(types.KeyPrefixTransientTxIndex)
if len(bz) == 0 {
return 0
}

return sdk.BigEndianToUint64(bz)
}

// ----------------------------------------------------------------------------
// Log
// ----------------------------------------------------------------------------

// GetLogSizeTransient returns EVM log index on the current block.
func (k Keeper) GetLogSizeTransient(ctx sdk.Context) uint64 {
store := ctx.TransientStore(k.transientKey)
bz := store.Get(types.KeyPrefixTransientLogSize)
if len(bz) == 0 {
return 0
}

return sdk.BigEndianToUint64(bz)
}

// SetLogSizeTransient fetches the current EVM log index from the transient store, increases its
// value by one and then sets the new index back to the transient store.
func (k Keeper) SetLogSizeTransient(ctx sdk.Context, logSize uint64) {
store := ctx.TransientStore(k.transientKey)
store.Set(types.KeyPrefixTransientLogSize, sdk.Uint64ToBigEndian(logSize))
}

// ----------------------------------------------------------------------------
// Storage
// ----------------------------------------------------------------------------
Expand Down Expand Up @@ -349,16 +306,10 @@ func (k Keeper) getBaseFee(ctx sdk.Context, london bool) *big.Int {
return baseFee
}

// ResetTransientGasUsed reset gas used to prepare for execution of current cosmos tx, called in ante handler.
func (k Keeper) ResetTransientGasUsed(ctx sdk.Context) {
store := ctx.TransientStore(k.transientKey)
store.Delete(types.KeyPrefixTransientGasUsed)
}

// GetTransientGasUsed returns the gas used by current cosmos tx.
func (k Keeper) GetTransientGasUsed(ctx sdk.Context) uint64 {
store := ctx.TransientStore(k.transientKey)
bz := store.Get(types.KeyPrefixTransientGasUsed)
bz := store.Get(types.TransientGasUsedKey(ctx.TxIndex()))
if len(bz) == 0 {
return 0
}
Expand All @@ -369,7 +320,7 @@ func (k Keeper) GetTransientGasUsed(ctx sdk.Context) uint64 {
func (k Keeper) SetTransientGasUsed(ctx sdk.Context, gasUsed uint64) {
store := ctx.TransientStore(k.transientKey)
bz := sdk.Uint64ToBigEndian(gasUsed)
store.Set(types.KeyPrefixTransientGasUsed, bz)
store.Set(types.TransientGasUsedKey(ctx.TxIndex()), bz)
}

// AddTransientGasUsed accumulate gas used by each eth msgs included in current cosmos tx.
Expand Down
3 changes: 0 additions & 3 deletions x/evm/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ func (k *Keeper) EthereumTx(goCtx context.Context, msg *types.MsgEthereumTx) (*t
ctx := sdk.UnwrapSDKContext(goCtx)

tx := msg.AsTransaction()
txIndex := k.GetTxIndexTransient(ctx)

labels := []metrics.Label{
telemetry.NewLabel("tx_type", fmt.Sprintf("%d", tx.Type())),
Expand Down Expand Up @@ -92,8 +91,6 @@ func (k *Keeper) EthereumTx(goCtx context.Context, msg *types.MsgEthereumTx) (*t
sdk.NewAttribute(sdk.AttributeKeyAmount, tx.Value().String()),
// add event for ethereum transaction hash format
sdk.NewAttribute(types.AttributeKeyEthereumTxHash, response.Hash),
// add event for index of valid ethereum tx
sdk.NewAttribute(types.AttributeKeyTxIndex, strconv.FormatUint(txIndex, 10)),
// add event for eth tx gas used, we can't get it from cosmos tx result when it contains multiple eth tx msgs.
sdk.NewAttribute(types.AttributeKeyTxGasUsed, strconv.FormatUint(response.GasUsed, 10)),
}
Expand Down
21 changes: 2 additions & 19 deletions x/evm/keeper/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,11 +168,6 @@
//
// For relevant discussion see: https://github.com/cosmos/cosmos-sdk/discussions/9072
func (k *Keeper) ApplyTransaction(ctx sdk.Context, msgEth *types.MsgEthereumTx) (*types.MsgEthereumTxResponse, error) {
var (
bloom *big.Int
bloomReceipt ethtypes.Bloom
)

ethTx := msgEth.AsTransaction()
cfg, err := k.EVMConfig(ctx, sdk.ConsAddress(ctx.BlockHeader().ProposerAddress), k.eip155ChainID, ethTx.Hash())
if err != nil {
Expand Down Expand Up @@ -205,9 +200,7 @@

// Compute block bloom filter
if len(logs) > 0 {
bloom = k.GetBlockBloomTransient(ctx)
bloom.Or(bloom, big.NewInt(0).SetBytes(ethtypes.LogsBloom(logs)))
bloomReceipt = ethtypes.BytesToBloom(bloom.Bytes())
k.SetTxBloom(tmpCtx, ethtypes.LogsBloom(logs))
}

cumulativeGasUsed := res.GasUsed
Expand All @@ -228,14 +221,12 @@
Type: ethTx.Type(),
PostState: nil, // TODO: intermediate state root
CumulativeGasUsed: cumulativeGasUsed,
Bloom: bloomReceipt,
Logs: logs,
TxHash: cfg.TxConfig.TxHash,
ContractAddress: contractAddr,
GasUsed: res.GasUsed,
BlockHash: cfg.TxConfig.BlockHash,
BlockNumber: big.NewInt(ctx.BlockHeight()),
TransactionIndex: cfg.TxConfig.TxIndex,
}

if !res.Failed() {
Expand All @@ -258,17 +249,9 @@

// refund gas in order to match the Ethereum gas consumption instead of the default SDK one.
if err = k.RefundGas(ctx, msg, msg.GasLimit-res.GasUsed, cfg.Params.EvmDenom); err != nil {
return nil, errorsmod.Wrapf(err, "failed to refund gas leftover gas to sender %s", msg.From)
return nil, errorsmod.Wrapf(err, "failed to refund leftover gas to sender %s", msg.From)

Check warning on line 252 in x/evm/keeper/state_transition.go

View check run for this annotation

Codecov / codecov/patch

x/evm/keeper/state_transition.go#L252

Added line #L252 was not covered by tests
}

if len(receipt.Logs) > 0 {
// Update transient block bloom filter
k.SetBlockBloomTransient(ctx, receipt.Bloom.Big())
yihuang marked this conversation as resolved.
Show resolved Hide resolved
k.SetLogSizeTransient(ctx, uint64(cfg.TxConfig.LogIndex)+uint64(len(receipt.Logs)))
}

k.SetTxIndexTransient(ctx, uint64(cfg.TxConfig.TxIndex)+1)

totalGasUsed, err := k.AddTransientGasUsed(ctx, res.GasUsed)
if err != nil {
return nil, errorsmod.Wrap(err, "failed to add transient gas used")
Expand Down
21 changes: 17 additions & 4 deletions x/evm/types/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
package types

import (
"encoding/binary"

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

Expand Down Expand Up @@ -46,8 +48,6 @@
// prefix bytes for the EVM transient store
const (
prefixTransientBloom = iota + 1
prefixTransientTxIndex
prefixTransientLogSize
prefixTransientGasUsed
)

Expand All @@ -61,8 +61,6 @@
// Transient Store key prefixes
var (
KeyPrefixTransientBloom = []byte{prefixTransientBloom}
KeyPrefixTransientTxIndex = []byte{prefixTransientTxIndex}
KeyPrefixTransientLogSize = []byte{prefixTransientLogSize}
KeyPrefixTransientGasUsed = []byte{prefixTransientGasUsed}
)

Expand All @@ -75,3 +73,18 @@
func StateKey(address common.Address, key []byte) []byte {
return append(AddressStoragePrefix(address), key...)
}

func TransientGasUsedKey(txIndex int) []byte {
var key [9]byte
key[0] = prefixTransientGasUsed
binary.BigEndian.PutUint64(key[1:], uint64(txIndex))
return key[:]

Check warning on line 81 in x/evm/types/key.go

View check run for this annotation

Codecov / codecov/patch

x/evm/types/key.go#L77-L81

Added lines #L77 - L81 were not covered by tests
}

func TransientBloomKey(txIndex, msgIndex int) []byte {
var key [1 + 8 + 8]byte
key[0] = prefixTransientBloom
binary.BigEndian.PutUint64(key[1:], uint64(txIndex))
binary.BigEndian.PutUint64(key[9:], uint64(msgIndex))
return key[:]

Check warning on line 89 in x/evm/types/key.go

View check run for this annotation

Codecov / codecov/patch

x/evm/types/key.go#L84-L89

Added lines #L84 - L89 were not covered by tests
}
Loading
Loading