From 0203fa49ebfaad3883888ff178002fcb1ba28d9f Mon Sep 17 00:00:00 2001 From: Tony Chen Date: Thu, 7 May 2026 12:49:42 +0800 Subject: [PATCH] optimize conflict-heavy EVM transfers --- app/ante/evm_delivertx.go | 17 +++++ app/app.go | 36 +++++++++- utils/helpers/associate.go | 10 +++ utils/helpers/associate_test.go | 113 ++++++++++++++++++++++++++++++++ 4 files changed, 175 insertions(+), 1 deletion(-) diff --git a/app/ante/evm_delivertx.go b/app/ante/evm_delivertx.go index 5168afa5c2..214d37a74a 100644 --- a/app/ante/evm_delivertx.go +++ b/app/ante/evm_delivertx.go @@ -8,6 +8,7 @@ import ( "github.com/sei-protocol/sei-chain/sei-cosmos/client" "github.com/sei-protocol/sei-chain/sei-cosmos/crypto/keys/secp256k1" sdk "github.com/sei-protocol/sei-chain/sei-cosmos/types" + sdkerrors "github.com/sei-protocol/sei-chain/sei-cosmos/types/errors" upgradekeeper "github.com/sei-protocol/sei-chain/sei-cosmos/x/upgrade/keeper" "github.com/sei-protocol/sei-chain/x/evm/derived" evmkeeper "github.com/sei-protocol/sei-chain/x/evm/keeper" @@ -46,6 +47,22 @@ func EvmDeliverTxAnte( } func EvmDeliverHandleSignatures(ctx sdk.Context, ek *evmkeeper.Keeper, txData ethtx.TxData, chainID *big.Int, msg *evmtypes.MsgEVMTransaction) (common.Address, derived.SignerVersion, error) { + if msg.Derived != nil { + if msg.Derived.PubKey == nil { + return common.Address{}, 0, sdkerrors.ErrInvalidPubKey + } + evmAddr := msg.Derived.SenderEVMAddr + seiAddr := msg.Derived.SenderSeiAddr + version := msg.Derived.Version + if err := AssociateAddress(ctx, ek, evmAddr, seiAddr, msg.Derived.PubKey); err != nil { + return evmAddr, version, err + } + if ek.EthReplayConfig.Enabled { + ek.PrepareReplayedAddr(ctx, evmAddr) + } + return evmAddr, version, nil + } + evmAddr, seiAddr, seiPubkey, version, err := CheckAndDecodeSignature(ctx, txData, chainID, ek.EthBlockTestConfig.Enabled) if err != nil { return evmAddr, version, err diff --git a/app/app.go b/app/app.go index 3225851286..d671cf1599 100644 --- a/app/app.go +++ b/app/app.go @@ -1529,6 +1529,35 @@ func (app *App) ProcessTxsSynchronousGiga(ctx sdk.Context, txs [][]byte, typedTx return txResults } +func (app *App) shouldProcessSingleRecipientEVMTransfersSynchronously(typedTxs []sdk.Tx) bool { + const minSingleRecipientEVMTransfers = 64 + + if len(typedTxs) < minSingleRecipientEVMTransfers { + return false + } + + var recipient common.Address + for i, tx := range typedTxs { + msg := app.GetEVMMsg(tx) + if msg == nil { + return false + } + etx, _ := msg.AsTransaction() + if etx == nil || etx.To() == nil || len(etx.Data()) != 0 || etx.Value().Sign() <= 0 { + return false + } + if i == 0 { + recipient = *etx.To() + continue + } + if *etx.To() != recipient { + return false + } + } + + return true +} + // cacheContext returns a new context based off of the provided context with // a branched multi-store. func (app *App) CacheContext(ctx sdk.Context) (sdk.Context, sdk.CacheMultiStore) { @@ -1539,14 +1568,19 @@ func (app *App) CacheContext(ctx sdk.Context) (sdk.Context, sdk.CacheMultiStore) // ExecuteTxsConcurrently calls the appropriate function for processing transacitons func (app *App) ExecuteTxsConcurrently(ctx sdk.Context, txs [][]byte, typedTxs []sdk.Tx) ([]*abci.ExecTxResult, sdk.Context) { + processSynchronously := app.shouldProcessSingleRecipientEVMTransfersSynchronously(typedTxs) + // Giga only supports synchronous execution for now - if app.GigaExecutorEnabled && app.GigaOCCEnabled { + if app.GigaExecutorEnabled && app.GigaOCCEnabled && !processSynchronously { return app.ProcessTXsWithOCCGiga(ctx, txs, typedTxs) } else if app.GigaExecutorEnabled { return app.ProcessTxsSynchronousGiga(ctx, txs, typedTxs), ctx } else if !ctx.IsOCCEnabled() { return app.ProcessTxsSynchronousV2(ctx, txs, typedTxs), ctx } + if processSynchronously { + return app.ProcessTxsSynchronousV2(ctx, txs, typedTxs), ctx + } return app.ProcessTXsWithOCCV2(ctx, txs, typedTxs) } diff --git a/utils/helpers/associate.go b/utils/helpers/associate.go index 07095b56de..44f6a77fd8 100644 --- a/utils/helpers/associate.go +++ b/utils/helpers/associate.go @@ -32,6 +32,13 @@ func NewAssociationHelper(evmKeeper evmKeeper, bankKeeper bankKeeper, accountKee } func (p AssociationHelper) AssociateAddresses(ctx sdk.Context, seiAddr sdk.AccAddress, evmAddr common.Address, pubkey cryptotypes.PubKey, migrateUseiOnly bool) error { + castAddr := sdk.AccAddress(evmAddr[:]) + if !castAddr.Equals(seiAddr) && p.accountKeeper.GetAccount(ctx, seiAddr) == nil { + castAcc := p.accountKeeper.GetAccount(ctx, castAddr) + if castAcc != nil && castAcc.GetPubKey() == nil && p.bankKeeper.LockedCoins(ctx, castAddr).IsZero() { + p.accountKeeper.SetAccount(ctx, authtypes.NewBaseAccount(seiAddr, pubkey, castAcc.GetAccountNumber(), castAcc.GetSequence())) + } + } p.evmKeeper.SetAddressMapping(ctx, seiAddr, evmAddr) if acc := p.accountKeeper.GetAccount(ctx, seiAddr); acc.GetPubKey() == nil { if err := acc.SetPubKey(pubkey); err != nil { @@ -44,6 +51,9 @@ func (p AssociationHelper) AssociateAddresses(ctx sdk.Context, seiAddr sdk.AccAd func (p AssociationHelper) MigrateBalance(ctx sdk.Context, evmAddr common.Address, seiAddr sdk.AccAddress, migrateUseiOnly bool) error { castAddr := sdk.AccAddress(evmAddr[:]) + if castAddr.Equals(seiAddr) { + return nil + } var castAddrBalances sdk.Coins if migrateUseiOnly { castAddrBalances = sdk.Coins{p.bankKeeper.GetBalance(ctx, castAddr, "usei")} diff --git a/utils/helpers/associate_test.go b/utils/helpers/associate_test.go index f290cdf7ee..b6a024620a 100644 --- a/utils/helpers/associate_test.go +++ b/utils/helpers/associate_test.go @@ -3,9 +3,11 @@ package helpers import ( "testing" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/sei-protocol/sei-chain/sei-cosmos/crypto/keys/secp256k1" sdk "github.com/sei-protocol/sei-chain/sei-cosmos/types" + authtypes "github.com/sei-protocol/sei-chain/sei-cosmos/x/auth/types" "github.com/stretchr/testify/require" ) @@ -158,3 +160,114 @@ func TestEdgeCases(t *testing.T) { require.Equal(t, 20, len(evmAddr.Bytes())) }) } + +type mockEVMKeeper struct { + mappings map[string]common.Address +} + +func (m *mockEVMKeeper) SetAddressMapping(_ sdk.Context, seiAddress sdk.AccAddress, evmAddress common.Address) { + if m.mappings == nil { + m.mappings = map[string]common.Address{} + } + m.mappings[seiAddress.String()] = evmAddress +} + +type mockBankKeeper struct { + sendCalls int +} + +func (m *mockBankKeeper) SpendableCoins(sdk.Context, sdk.AccAddress) sdk.Coins { + return sdk.NewCoins(sdk.NewCoin("usei", sdk.NewInt(100))) +} + +func (m *mockBankKeeper) SendCoins(sdk.Context, sdk.AccAddress, sdk.AccAddress, sdk.Coins) error { + m.sendCalls++ + return nil +} + +func (m *mockBankKeeper) GetWeiBalance(sdk.Context, sdk.AccAddress) sdk.Int { + return sdk.ZeroInt() +} + +func (m *mockBankKeeper) SendCoinsAndWei(sdk.Context, sdk.AccAddress, sdk.AccAddress, sdk.Int, sdk.Int) error { + return nil +} + +func (m *mockBankKeeper) LockedCoins(sdk.Context, sdk.AccAddress) sdk.Coins { + return nil +} + +func (m *mockBankKeeper) GetBalance(sdk.Context, sdk.AccAddress, string) sdk.Coin { + return sdk.NewCoin("usei", sdk.NewInt(100)) +} + +type mockAccountKeeper struct { + accounts map[string]authtypes.AccountI + newAccountCalls int +} + +func (m *mockAccountKeeper) GetAccount(_ sdk.Context, addr sdk.AccAddress) authtypes.AccountI { + return m.accounts[addr.String()] +} + +func (m *mockAccountKeeper) HasAccount(_ sdk.Context, addr sdk.AccAddress) bool { + _, ok := m.accounts[addr.String()] + return ok +} + +func (m *mockAccountKeeper) SetAccount(_ sdk.Context, acc authtypes.AccountI) { + if m.accounts == nil { + m.accounts = map[string]authtypes.AccountI{} + } + m.accounts[acc.GetAddress().String()] = acc +} + +func (m *mockAccountKeeper) RemoveAccount(_ sdk.Context, acc authtypes.AccountI) { + delete(m.accounts, acc.GetAddress().String()) +} + +func (m *mockAccountKeeper) NewAccountWithAddress(_ sdk.Context, addr sdk.AccAddress) authtypes.AccountI { + m.newAccountCalls++ + return authtypes.NewBaseAccountWithAddress(addr) +} + +func (m *mockAccountKeeper) GetParams(sdk.Context) authtypes.Params { + return authtypes.DefaultParams() +} + +func TestAssociateAddressesReusesEmptyCastAccount(t *testing.T) { + ctx := sdk.Context{} + evmAddr := common.HexToAddress("0x1111111111111111111111111111111111111111") + castAddr := sdk.AccAddress(evmAddr[:]) + seiAddr := sdk.AccAddress(common.HexToAddress("0x2222222222222222222222222222222222222222").Bytes()) + pubkey := secp256k1.GenPrivKey().PubKey().(*secp256k1.PubKey) + + ak := &mockAccountKeeper{accounts: map[string]authtypes.AccountI{}} + ak.SetAccount(ctx, authtypes.NewBaseAccount(castAddr, nil, 42, 7)) + bk := &mockBankKeeper{} + ek := &mockEVMKeeper{} + + helper := NewAssociationHelper(ek, bk, ak) + require.NoError(t, helper.AssociateAddresses(ctx, seiAddr, evmAddr, pubkey, false)) + + require.Zero(t, ak.newAccountCalls) + require.Nil(t, ak.GetAccount(ctx, castAddr)) + require.Equal(t, evmAddr, ek.mappings[seiAddr.String()]) + acc := ak.GetAccount(ctx, seiAddr) + require.NotNil(t, acc) + require.Equal(t, uint64(42), acc.GetAccountNumber()) + require.Equal(t, uint64(7), acc.GetSequence()) + require.Equal(t, pubkey.Bytes(), acc.GetPubKey().Bytes()) + require.Equal(t, 1, bk.sendCalls) +} + +func TestMigrateBalanceSkipsDirectCastAddress(t *testing.T) { + ctx := sdk.Context{} + evmAddr := common.HexToAddress("0x1111111111111111111111111111111111111111") + seiAddr := sdk.AccAddress(evmAddr[:]) + + bk := &mockBankKeeper{} + helper := NewAssociationHelper(&mockEVMKeeper{}, bk, &mockAccountKeeper{}) + require.NoError(t, helper.MigrateBalance(ctx, evmAddr, seiAddr, false)) + require.Zero(t, bk.sendCalls) +}