Skip to content

Commit

Permalink
Merge pull request #5614 from onflow/gregor/evm/batch-run
Browse files Browse the repository at this point in the history
[EVM] Batch run transactions
  • Loading branch information
devbugging authored Apr 26, 2024
2 parents 11a67d9 + 4fd401d commit 55bb6fe
Show file tree
Hide file tree
Showing 17 changed files with 1,339 additions and 73 deletions.
76 changes: 58 additions & 18 deletions fvm/evm/emulator/emulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,36 +133,76 @@ func (bl *BlockView) DirectCall(call *types.DirectCall) (*types.Result, error) {
func (bl *BlockView) RunTransaction(
tx *gethTypes.Transaction,
) (*types.Result, error) {
var res *types.Result
var err error
proc, err := bl.newProcedure()
if err != nil {
return nil, err
}
txHash := tx.Hash()

msg, err := gethCore.TransactionToMessage(
tx,
GetSigner(bl.config),
proc.config.BlockContext.BaseFee)
if err != nil {
// this is not a fatal error (e.g. due to bad signature)
// not a valid transaction
res = &types.Result{
TxType: tx.Type(),
TxHash: txHash,
}
res.SetValidationError(err)
return res, nil
return types.NewInvalidResult(tx, err), nil
}

// update tx context origin
proc.evm.TxContext.Origin = msg.From
res, err = proc.run(msg, txHash, 0, tx.Type())
res, err := proc.run(msg, tx.Hash(), 0, tx.Type())
if err != nil {
return nil, err
}
// all commmit errors (StateDB errors) has to be returned
return res, proc.commitAndFinalize()
if err := proc.commit(true); err != nil {
return nil, err
}

return res, nil
}

func (bl *BlockView) BatchRunTransactions(txs []*gethTypes.Transaction) ([]*types.Result, error) {
batchResults := make([]*types.Result, len(txs))

proc, err := bl.newProcedure()
if err != nil {
return nil, err
}

for i, tx := range txs {
msg, err := gethCore.TransactionToMessage(
tx,
GetSigner(bl.config),
proc.config.BlockContext.BaseFee)
if err != nil {
batchResults[i] = types.NewInvalidResult(tx, err)
continue
}

// update tx context origin
proc.evm.TxContext.Origin = msg.From
res, err := proc.run(msg, tx.Hash(), uint(i), tx.Type())
if err != nil {
return nil, err
}
// all commmit errors (StateDB errors) has to be returned
if err := proc.commit(false); err != nil {
return nil, err
}

// this clears state for any subsequent transaction runs
proc.state.Reset()

batchResults[i] = res
}

// finalize after all the batch transactions are executed to save resources
if err := proc.state.Finalize(); err != nil {
return nil, err
}

return batchResults, nil
}

func (bl *BlockView) newProcedure() (*procedure, error) {
Expand Down Expand Up @@ -190,9 +230,9 @@ type procedure struct {
state types.StateDB
}

// commit commits the changes to the state (with finalization)
func (proc *procedure) commitAndFinalize() error {
err := proc.state.Commit(true)
// commit commits the changes to the state (with optional finalization)
func (proc *procedure) commit(finalize bool) error {
err := proc.state.Commit(finalize)
if err != nil {
// if known types (state errors) don't do anything and return
if types.IsAFatalError(err) || types.IsAStateError(err) {
Expand Down Expand Up @@ -236,7 +276,7 @@ func (proc *procedure) mintTo(
}

// all commmit errors (StateDB errors) has to be returned
return res, proc.commitAndFinalize()
return res, proc.commit(true)
}

func (proc *procedure) withdrawFrom(
Expand Down Expand Up @@ -267,7 +307,7 @@ func (proc *procedure) withdrawFrom(
// now deduct the balance from the bridge
proc.state.SubBalance(bridge, call.Value)
// all commmit errors (StateDB errors) has to be returned
return res, proc.commitAndFinalize()
return res, proc.commit(true)
}

// deployAt deploys a contract at the given target address
Expand Down Expand Up @@ -386,7 +426,7 @@ func (proc *procedure) deployAt(
res.DeployedContractAddress = &to

proc.state.SetCode(addr, ret)
return res, proc.commitAndFinalize()
return res, proc.commit(true)
}

func (proc *procedure) runDirect(
Expand All @@ -402,7 +442,7 @@ func (proc *procedure) runDirect(
return nil, err
}
// all commmit errors (StateDB errors) has to be returned
return res, proc.commitAndFinalize()
return res, proc.commit(true)
}

// run runs a geth core.message and returns the
Expand Down
64 changes: 63 additions & 1 deletion fvm/evm/emulator/emulator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ func TestContractInteraction(t *testing.T) {
res, err := blk.RunTransaction(tx)
require.NoError(t, err)
require.NoError(t, res.VMError)
require.NoError(t, res.ValidationError)
require.Greater(t, res.GasConsumed, uint64(0))

// check the balance of coinbase
Expand All @@ -303,6 +304,66 @@ func TestContractInteraction(t *testing.T) {
})
})

t.Run("test batch running transactions", func(t *testing.T) {
account := testutils.GetTestEOAAccount(t, testutils.EOATestAccount1KeyHex)
account.SetNonce(account.Nonce() + 1)
fAddr := account.Address()
RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
RunWithNewBlockView(t, env, func(blk types.BlockView) {
_, err := blk.DirectCall(types.NewDepositCall(bridgeAccount, fAddr, amount, account.Nonce()))
require.NoError(t, err)
})
})

RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
ctx := types.NewDefaultBlockContext(blockNumber.Uint64())
ctx.GasFeeCollector = types.NewAddressFromString("coinbase-collector")
coinbaseOrgBalance := gethCommon.Big1
// small amount of money to create account
RunWithNewBlockView(t, env, func(blk types.BlockView) {
_, err := blk.DirectCall(types.NewDepositCall(bridgeAccount, ctx.GasFeeCollector, coinbaseOrgBalance, 0))
require.NoError(t, err)
})

blk, err := env.NewBlockView(ctx)
require.NoError(t, err)

const batchSize = 3
txs := make([]*gethTypes.Transaction, batchSize)
for i := range txs {
txs[i] = account.PrepareAndSignTx(
t,
testAccount.ToCommon(), // to
nil, // data
big.NewInt(1000), // amount
gethParams.TxGas, // gas limit
gethCommon.Big1, // gas fee

)
}

results, err := blk.BatchRunTransactions(txs)
require.NoError(t, err)
for _, res := range results {
require.NoError(t, res.VMError)
require.NoError(t, res.ValidationError)
require.Greater(t, res.GasConsumed, uint64(0))
}

// check the balance of coinbase
RunWithNewReadOnlyBlockView(t, env, func(blk2 types.ReadOnlyBlockView) {
bal, err := blk2.BalanceOf(ctx.GasFeeCollector)
require.NoError(t, err)
expected := gethParams.TxGas*batchSize + gethCommon.Big1.Uint64()
require.Equal(t, expected, bal.Uint64())

nonce, err := blk2.NonceOf(fAddr)
require.NoError(t, err)
require.Equal(t, batchSize+1, int(nonce))
})
})
})

t.Run("test runing transactions with dynamic fees (happy case)", func(t *testing.T) {
account := testutils.GetTestEOAAccount(t, testutils.EOATestAccount1KeyHex)
fAddr := account.Address()
Expand All @@ -312,7 +373,7 @@ func TestContractInteraction(t *testing.T) {
require.NoError(t, err)
})
})
account.SetNonce(account.Nonce() + 1)
account.SetNonce(account.Nonce() + 4)

RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
ctx := types.NewDefaultBlockContext(blockNumber.Uint64())
Expand Down Expand Up @@ -343,6 +404,7 @@ func TestContractInteraction(t *testing.T) {
res, err := blk.RunTransaction(tx)
require.NoError(t, err)
require.NoError(t, res.VMError)
require.NoError(t, res.ValidationError)
require.Greater(t, res.GasConsumed, uint64(0))
})
})
Expand Down
Loading

0 comments on commit 55bb6fe

Please sign in to comment.