Skip to content

Commit

Permalink
feat: sync evm nonce with cosmos (#54)
Browse files Browse the repository at this point in the history
* sync evm nonce with cosmos

* add test

* format proto
  • Loading branch information
beer-1 authored Aug 29, 2024
1 parent 8d5d08b commit a65d532
Show file tree
Hide file tree
Showing 13 changed files with 367 additions and 162 deletions.
2 changes: 1 addition & 1 deletion app/ante/ante.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
ante.NewValidateSigCountDecorator(options.AccountKeeper),
ante.NewSigGasConsumeDecorator(options.AccountKeeper, sigGasConsumer),
NewSigVerificationDecorator(options.AccountKeeper, options.EVMKeeper, options.SignModeHandler),
ante.NewIncrementSequenceDecorator(options.AccountKeeper),
NewIncrementSequenceDecorator(options.AccountKeeper),
ibcante.NewRedundantRelayDecorator(options.IBCkeeper),
auctionante.NewAuctionDecorator(options.AuctionKeeper, options.TxEncoder, options.MevLane),
}
Expand Down
83 changes: 83 additions & 0 deletions app/ante/sigverify.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (

"github.com/initia-labs/initia/crypto/ethsecp256k1"
evmkeeper "github.com/initia-labs/minievm/x/evm/keeper"
evmtypes "github.com/initia-labs/minievm/x/evm/types"
)

// SigVerificationDecorator verifies all signatures for a tx and return an error if any are invalid. Note,
Expand Down Expand Up @@ -198,3 +199,85 @@ func consumeMultisignatureVerificationGas(

return nil
}

// IncrementSequenceDecorator handles incrementing sequences of all signers.
// Use the IncrementSequenceDecorator decorator to prevent replay attacks. Note,
// there is need to execute IncrementSequenceDecorator on RecheckTx since
// BaseApp.Commit() will set the check state based on the latest header.
//
// NOTE: Since CheckTx and DeliverTx state are managed separately, subsequent and
// sequential txs orginating from the same account cannot be handled correctly in
// a reliable way unless sequence numbers are managed and tracked manually by a
// client. It is recommended to instead use multiple messages in a tx.
//
// NOTE: When we execute evm messages, it whould handle sequence number increment internally,
// so we need to decrease sequence number if it is used in EVM.
type IncrementSequenceDecorator struct {
ak authante.AccountKeeper
}

func NewIncrementSequenceDecorator(ak authante.AccountKeeper) IncrementSequenceDecorator {
return IncrementSequenceDecorator{
ak: ak,
}
}

func (isd IncrementSequenceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
sigTx, ok := tx.(authsigning.SigVerifiableTx)
if !ok {
return ctx, errorsmod.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type")
}

// increment sequence of all signers
signers, err := sigTx.GetSigners()
if err != nil {
return sdk.Context{}, err
}

for _, signer := range signers {
acc := isd.ak.GetAccount(ctx, signer)
if err := acc.SetSequence(acc.GetSequence() + 1); err != nil {
panic(err)
}

isd.ak.SetAccount(ctx, acc)
}

// decrement sequence of all signers which is used in EVM
// when we execute evm messages, it whould handle sequence number increment.
if simulate || ctx.ExecMode() == sdk.ExecModeFinalize {
signerMap := make(map[string]bool)
for _, msg := range tx.GetMsgs() {
var caller string
switch msg := msg.(type) {
case *evmtypes.MsgCreate:
caller = msg.Sender
case *evmtypes.MsgCreate2:
caller = msg.Sender
case *evmtypes.MsgCall:
caller = msg.Sender
default:
continue
}

if _, ok := signerMap[caller]; ok {
continue
}
signerMap[caller] = true

callerAccAddr, err := isd.ak.AddressCodec().StringToBytes(caller)
if err != nil {
return ctx, err
}

acc := isd.ak.GetAccount(ctx, callerAccAddr)
if err := acc.SetSequence(acc.GetSequence() - 1); err != nil {
panic(err)
}

isd.ak.SetAccount(ctx, acc)
}
}

return next(ctx, tx, simulate)
}
97 changes: 97 additions & 0 deletions app/ante/sigverify_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package ante_test

import (
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/initia-labs/minievm/app/ante"
evmtypes "github.com/initia-labs/minievm/x/evm/types"
)

func (suite *AnteTestSuite) TestIncrementSequenceDecorator() {
suite.SetupTest() // setup
suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder()

priv, _, addr := testdata.KeyTestPubAddr()
acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr)
suite.NoError(acc.SetAccountNumber(uint64(50)))
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)

msgs := []sdk.Msg{testdata.NewTestMsg(addr)}
suite.NoError(suite.txBuilder.SetMsgs(msgs...))
privs := []cryptotypes.PrivKey{priv}
accNums := []uint64{suite.app.AccountKeeper.GetAccount(suite.ctx, addr).GetAccountNumber()}
accSeqs := []uint64{suite.app.AccountKeeper.GetAccount(suite.ctx, addr).GetSequence()}
feeAmount := testdata.NewTestFeeAmount()
gasLimit := testdata.NewTestGasLimit()
suite.txBuilder.SetFeeAmount(feeAmount)
suite.txBuilder.SetGasLimit(gasLimit)

tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID())
suite.NoError(err)

isd := ante.NewIncrementSequenceDecorator(suite.app.AccountKeeper)
antehandler := sdk.ChainAnteDecorators(isd)

testCases := []struct {
ctx sdk.Context
simulate bool
expectedSeq uint64
}{
{suite.ctx.WithIsReCheckTx(true), false, 1},
{suite.ctx.WithIsCheckTx(true).WithIsReCheckTx(false), false, 2},
{suite.ctx.WithIsReCheckTx(true), false, 3},
{suite.ctx.WithIsReCheckTx(true), false, 4},
{suite.ctx.WithIsReCheckTx(true), true, 5},
}

for i, tc := range testCases {
_, err := antehandler(tc.ctx, tx, tc.simulate)
suite.NoError(err, "unexpected error; tc #%d, %v", i, tc)
suite.Equal(tc.expectedSeq, suite.app.AccountKeeper.GetAccount(suite.ctx, addr).GetSequence())
}
}

func (suite *AnteTestSuite) TestIncrementSequenceDecorator_EVMMessages() {
suite.SetupTest() // setup
suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder()

priv, _, addr := testdata.KeyTestPubAddr()
acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addr)
suite.NoError(acc.SetAccountNumber(uint64(50)))
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)

msgs := []sdk.Msg{&evmtypes.MsgCreate{Sender: addr.String()}, &evmtypes.MsgCreate2{Sender: addr.String()}, &evmtypes.MsgCall{Sender: addr.String()}}
suite.NoError(suite.txBuilder.SetMsgs(msgs...))
privs := []cryptotypes.PrivKey{priv}
accNums := []uint64{suite.app.AccountKeeper.GetAccount(suite.ctx, addr).GetAccountNumber()}
accSeqs := []uint64{suite.app.AccountKeeper.GetAccount(suite.ctx, addr).GetSequence()}
feeAmount := testdata.NewTestFeeAmount()
gasLimit := testdata.NewTestGasLimit()
suite.txBuilder.SetFeeAmount(feeAmount)
suite.txBuilder.SetGasLimit(gasLimit)

tx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.ctx.ChainID())
suite.NoError(err)

isd := ante.NewIncrementSequenceDecorator(suite.app.AccountKeeper)
antehandler := sdk.ChainAnteDecorators(isd)

testCases := []struct {
ctx sdk.Context
simulate bool
expectedSeq uint64
}{
{suite.ctx.WithExecMode(sdk.ExecModeCheck), false, 1},
{suite.ctx.WithExecMode(sdk.ExecModeCheck), true, 1},
{suite.ctx.WithExecMode(sdk.ExecModeFinalize), false, 1},
{suite.ctx.WithExecMode(sdk.ExecModeCheck), false, 2},
}

for i, tc := range testCases {
_, err := antehandler(tc.ctx, tx, tc.simulate)
suite.NoError(err, "unexpected error; tc #%d, %v", i, tc)
suite.Equal(tc.expectedSeq, suite.app.AccountKeeper.GetAccount(suite.ctx, addr).GetSequence())
}
}
11 changes: 10 additions & 1 deletion jsonrpc/backend/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core"
coretypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rpc"

Expand Down Expand Up @@ -78,6 +79,10 @@ func (b *JSONRPCBackend) SendTx(tx *coretypes.Transaction) error {
accSeq = acc.GetSequence()
}

if accSeq > txSeq {
return fmt.Errorf("%w: next nonce %v, tx nonce %v", core.ErrNonceTooLow, accSeq, txSeq)
}

b.logger.Debug("enqueue tx", "sender", senderHex, "txSeq", txSeq, "accSeq", accSeq)
cacheKey := fmt.Sprintf("%s-%d", senderHex, txSeq)
_ = b.queuedTxs.Add(cacheKey, txBytes)
Expand Down Expand Up @@ -161,8 +166,12 @@ func (b *JSONRPCBackend) GetTransactionCount(address common.Address, blockNrOrHa
if blockNumber == rpc.PendingBlockNumber {
queryCtx = b.app.GetContextForCheckTx(nil)
} else {
if blockNumber < 0 {
blockNumber = 0
}

var err error
queryCtx, err = b.app.CreateQueryContext(0, false)
queryCtx, err = b.app.CreateQueryContext(blockNumber.Int64(), false)
if err != nil {
return nil, err
}
Expand Down
5 changes: 3 additions & 2 deletions proto/minievm/evm/v1/auth.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ syntax = "proto3";
package minievm.evm.v1;

import "amino/amino.proto";
import "gogoproto/gogo.proto";
import "cosmos/auth/v1beta1/auth.proto";
import "gogoproto/gogo.proto";

option go_package = "github.com/initia-labs/minievm/x/evm/types";

Expand All @@ -13,6 +13,7 @@ message ContractAccount {
option (gogoproto.goproto_getters) = false;

cosmos.auth.v1beta1.BaseAccount base_account = 1 [(gogoproto.embed) = true];
bytes code_hash = 2;
}

// ShorthandAccount defines an account of shorthand address
Expand All @@ -26,4 +27,4 @@ message ShorthandAccount {

cosmos.auth.v1beta1.BaseAccount base_account = 1 [(gogoproto.embed) = true];
string original_address = 2;
}
}
12 changes: 2 additions & 10 deletions x/evm/keeper/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,16 +226,11 @@ func (k Keeper) EVMStaticCallWithTracer(ctx context.Context, caller common.Addre

// EVMCall executes an EVM call with the given input data.
func (k Keeper) EVMCall(ctx context.Context, caller common.Address, contractAddr common.Address, inputBz []byte, value *uint256.Int) ([]byte, types.Logs, error) {
return k.EVMCallWithTracer(ctx, caller, contractAddr, inputBz, value, false, nil)
}

// EVMCallWithNonceIncrement executes an EVM call with the given input data and increment the nonce of the caller.
func (k Keeper) EVMCallWithNonceIncrement(ctx context.Context, caller common.Address, contractAddr common.Address, inputBz []byte, value *uint256.Int) ([]byte, types.Logs, error) {
return k.EVMCallWithTracer(ctx, caller, contractAddr, inputBz, value, true, nil)
return k.EVMCallWithTracer(ctx, caller, contractAddr, inputBz, value, nil)
}

// EVMCallWithTracer executes an EVM call with the given input data and tracer.
func (k Keeper) EVMCallWithTracer(ctx context.Context, caller common.Address, contractAddr common.Address, inputBz []byte, value *uint256.Int, increseNonce bool, tracer *tracing.Hooks) ([]byte, types.Logs, error) {
func (k Keeper) EVMCallWithTracer(ctx context.Context, caller common.Address, contractAddr common.Address, inputBz []byte, value *uint256.Int, tracer *tracing.Hooks) ([]byte, types.Logs, error) {
ctx, evm, err := k.CreateEVM(ctx, caller, tracer)
if err != nil {
return nil, nil, err
Expand All @@ -249,9 +244,6 @@ func (k Keeper) EVMCallWithTracer(ctx context.Context, caller common.Address, co

rules := evm.ChainConfig().Rules(evm.Context.BlockNumber, evm.Context.Random != nil, evm.Context.Time)
evm.StateDB.Prepare(rules, caller, types.NullAddress, &contractAddr, append(vm.ActivePrecompiles(rules), k.precompiles.toAddrs()...), nil)
if increseNonce {
evm.StateDB.SetNonce(caller, evm.StateDB.GetNonce(caller)+1)
}

retBz, gasRemaining, err := evm.Call(
vm.AccountRef(caller),
Expand Down
22 changes: 21 additions & 1 deletion x/evm/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,13 +142,33 @@ func (ms *msgServerImpl) Create2(ctx context.Context, msg *types.MsgCreate2) (*t
}, nil
}

// increaseNonce increases the nonce of the given account.
func (ms *msgServerImpl) increaseNonce(ctx context.Context, caller sdk.AccAddress) error {
senderAcc := ms.accountKeeper.GetAccount(ctx, caller)
if senderAcc == nil {
senderAcc = ms.accountKeeper.NewAccountWithAddress(ctx, caller)
}
if err := senderAcc.SetSequence(senderAcc.GetSequence() + 1); err != nil {
return err
}
ms.accountKeeper.SetAccount(ctx, senderAcc)
return nil
}

// Call implements types.MsgServer.
func (ms *msgServerImpl) Call(ctx context.Context, msg *types.MsgCall) (*types.MsgCallResponse, error) {
sender, err := ms.ac.StringToBytes(msg.Sender)
if err != nil {
return nil, err
}

// increase nonce before execution like evm does
//
// NOTE: evm only increases nonce at Call not Create, so we should do the same.
if err := ms.increaseNonce(ctx, sender); err != nil {
return nil, err
}

contractAddr, err := types.ContractAddressFromString(ms.ac, msg.ContractAddr)
if err != nil {
return nil, err
Expand All @@ -168,7 +188,7 @@ func (ms *msgServerImpl) Call(ctx context.Context, msg *types.MsgCall) (*types.M
return nil, types.ErrInvalidValue.Wrap("value is out of range")
}

retBz, logs, err := ms.EVMCallWithNonceIncrement(ctx, caller, contractAddr, inputBz, value)
retBz, logs, err := ms.EVMCall(ctx, caller, contractAddr, inputBz, value)
if err != nil {
return nil, types.ErrEVMCallFailed.Wrap(err.Error())
}
Expand Down
2 changes: 1 addition & 1 deletion x/evm/keeper/query_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func (qs *queryServerImpl) Call(ctx context.Context, req *types.QueryCallRequest
// if contract address is not provided, then it's a contract creation
retBz, _, logs, err = qs.EVMCreateWithTracer(sdkCtx, caller, inputBz, value, nil, tracer)
} else {
retBz, logs, err = qs.EVMCallWithTracer(sdkCtx, caller, contractAddr, inputBz, value, false, tracer)
retBz, logs, err = qs.EVMCallWithTracer(sdkCtx, caller, contractAddr, inputBz, value, tracer)

}

Expand Down
5 changes: 0 additions & 5 deletions x/evm/state/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,11 @@ import (
)

var (
AccountKeyPrefix = []byte("account")
CodeKeyPrefix = []byte("code")
CodeSizeKeyPrefix = []byte("codesize")
StateKeyPrefix = []byte("state")
)

func accountKey(addr common.Address) []byte {
return append(addr.Bytes(), AccountKeyPrefix...)
}

func codeKey(addr common.Address, codeHash []byte) []byte {
return append(addr.Bytes(), append(CodeKeyPrefix, codeHash...)...)
}
Expand Down
35 changes: 0 additions & 35 deletions x/evm/state/state_account.go

This file was deleted.

Loading

0 comments on commit a65d532

Please sign in to comment.