Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
25 changes: 6 additions & 19 deletions evmcore/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,9 +386,7 @@ func (r *transactionRunner) runTransactionBundle(
processedBundle.Count++
}
}
// the processed bundles are all processed nested bundles and this bundle itself
processedBundles := append(runner.processedBundles, processedBundle)
return runner.processedTransactions, processedBundles, core_types.TransactionResultSuccessful
return runner.processedTransactions, []ProcessedBundle{processedBundle}, core_types.TransactionResultSuccessful
}

// bundleTransactionRunner is an adapter implementing the bundle.TransactionRunner
Expand All @@ -397,18 +395,11 @@ type bundleTransactionRunner struct {
ctxt *runContext
txOffset int
processedTransactions []ProcessedTransaction
processedBundles []ProcessedBundle
}

type bundleTransactionRunnerSnapshot struct {
stateDbSnapshot int
processedBundleSnapshot int
}

func (b *bundleTransactionRunner) Run(tx *types.Transaction) core_types.TransactionResult {
processed, bundles, result := runTransaction(b.ctxt, tx, b.txOffset)
processed, _, result := runTransaction(b.ctxt, tx, b.txOffset)
b.processedTransactions = append(b.processedTransactions, processed...)
b.processedBundles = append(b.processedBundles, bundles...)

if result != core_types.TransactionResultInvalid {
for _, p := range processed {
Expand All @@ -421,16 +412,12 @@ func (b *bundleTransactionRunner) Run(tx *types.Transaction) core_types.Transact
return result
}

func (b *bundleTransactionRunner) CreateSnapshot() bundleTransactionRunnerSnapshot {
return bundleTransactionRunnerSnapshot{
stateDbSnapshot: b.ctxt.statedb.InterTxSnapshot(),
processedBundleSnapshot: len(b.processedBundles),
}
func (b *bundleTransactionRunner) CreateSnapshot() int {
return b.ctxt.statedb.InterTxSnapshot()
}

func (b *bundleTransactionRunner) RevertToSnapshot(snapshot bundleTransactionRunnerSnapshot) {
b.ctxt.statedb.RevertToInterTxSnapshot(snapshot.stateDbSnapshot)
b.processedBundles = b.processedBundles[:snapshot.processedBundleSnapshot]
func (b *bundleTransactionRunner) RevertToSnapshot(id int) {
b.ctxt.statedb.RevertToInterTxSnapshot(id)
}

// _evm is an interface to an EVM instance that can be used to run a single
Expand Down
146 changes: 15 additions & 131 deletions evmcore/state_processor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2038,110 +2038,6 @@ func TestRunTransactionBundle_RunBundleSuccessful_ReturnsBundleOnlyTransactionAn
require.Equal(t, core_types.TransactionResultSuccessful, result)
}

func TestRunTransactionBundle_ReturnsListOfBundlesThatWillBePartOfTheCurrentBlock(t *testing.T) {
signer := types.LatestSignerForChainID(big.NewInt(1))
key, err := crypto.GenerateKey()
require.NoError(t, err)

oneTxEnvelope, oneTxPlan := bundle.NewBuilder(signer).
With(bundle.Step(key, &types.AccessListTx{Gas: 29_000})).
BuildEnvelopeAndPlan()

inner1Envelope, inner1Plan := bundle.NewBuilder(signer).
With(bundle.Step(key, &types.AccessListTx{Gas: 29_000})).
BuildEnvelopeAndPlan()
inner2Envelope, inner2Plan := bundle.NewBuilder(signer).
With(bundle.Step(key, &types.AccessListTx{Gas: 29_001})).
BuildEnvelopeAndPlan()
twoNestedBundlesEnvelope, twoNestedBundlesPlan := bundle.NewBuilder(signer).
With(
bundle.Step(key, inner1Envelope),
bundle.Step(key, inner2Envelope),
).
BuildEnvelopeAndPlan()

tests := map[string]struct {
envelope *types.Transaction
results []uint64
expectedBundles []ProcessedBundle
}{
"successful bundle with one tx": {
envelope: oneTxEnvelope,
results: []uint64{
types.ReceiptStatusSuccessful,
},
expectedBundles: []ProcessedBundle{
{ExecutionPlanHash: oneTxPlan.Hash(), Position: 0, Count: 1},
},
},
"failed bundle with one tx": {
envelope: oneTxEnvelope,
results: []uint64{
types.ReceiptStatusFailed,
},
expectedBundles: []ProcessedBundle{
{ExecutionPlanHash: oneTxPlan.Hash(), Position: 0, Count: 0},
},
},
"successful bundle with two successful nested bundles": {
envelope: twoNestedBundlesEnvelope,
results: []uint64{
types.ReceiptStatusSuccessful,
types.ReceiptStatusSuccessful,
},
expectedBundles: []ProcessedBundle{
{ExecutionPlanHash: inner1Plan.Hash(), Position: 0, Count: 1},
{ExecutionPlanHash: inner2Plan.Hash(), Position: 1, Count: 1},
{ExecutionPlanHash: twoNestedBundlesPlan.Hash(), Position: 0, Count: 2},
},
},
"failed bundle with one successful and one failed nested bundle": {
envelope: twoNestedBundlesEnvelope,
results: []uint64{
types.ReceiptStatusSuccessful,
types.ReceiptStatusFailed,
},
expectedBundles: []ProcessedBundle{
{ExecutionPlanHash: twoNestedBundlesPlan.Hash(), Position: 0, Count: 0},
},
},
}

for name, test := range tests {
t.Run(name, func(t *testing.T) {
ctrl := gomock.NewController(t)
state := state.NewMockStateDB(ctrl)
evm := NewMock_evm(ctrl)

state.EXPECT().InterTxSnapshot().Return(1).AnyTimes()
state.EXPECT().RevertToInterTxSnapshot(1).AnyTimes()

calls := []any{}
for _, result := range test.results {
calls = append(calls,
evm.EXPECT().runWithBaseFeeCheck(gomock.Any(), gomock.Any(), gomock.Any()).
Return(ProcessedTransaction{Transaction: &types.Transaction{}, Receipt: &types.Receipt{Status: result}}),
)
}
gomock.InOrder(calls...)

runner := &transactionRunner{evm: evm}

context := &runContext{
statedb: state,
signer: types.LatestSignerForChainID(big.NewInt(1)),
baseFee: big.NewInt(1),
upgrades: opera.Upgrades{TransactionBundles: true},
blockNumber: &big.Int{},
runner: runner,
}

_, bundles, _ := runner.runTransactionBundle(context, test.envelope, 0)
require.Equal(t, test.expectedBundles, bundles)
})
}
}

func TestRunRegularTransaction_InternalTransactions_SkipsTransactionChecksTrue(t *testing.T) {

maxTxGas := uint64(1_500_000)
Expand Down Expand Up @@ -2480,42 +2376,30 @@ func TestBundleTransactionRunner_Run_IncrementsOffsetByNumberOfNonNullReceiptsIf
require.Equal(t, bundleTransactionRunner.txOffset, startOffset+1+2)
}

func TestBundleTransactionRunner_CreateSnapshot_RecordsStateDBSnapshotAndLengthOfProcessedBundles(t *testing.T) {
func TestBundleTransactionRunner_CreateSnapshot_CallsInterTxSnapshotOnStateDb(t *testing.T) {
ctrl := gomock.NewController(t)
statedb := state.NewMockStateDB(ctrl)
statedb.EXPECT().InterTxSnapshot().Return(42)
state := state.NewMockStateDB(ctrl)

runner := bundleTransactionRunner{
ctxt: &runContext{
statedb: statedb,
},
processedBundles: []ProcessedBundle{{}, {}}, // length of 2
}
state.EXPECT().InterTxSnapshot().Return(123)

ctxt := &runContext{statedb: state}
bundleTransactionRunner := &bundleTransactionRunner{ctxt: ctxt}

snapshot := runner.CreateSnapshot()
require.Equal(t, 42, snapshot.stateDbSnapshot)
require.Equal(t, 2, snapshot.processedBundleSnapshot)
snapshotId := bundleTransactionRunner.CreateSnapshot()
require.Equal(t, 123, snapshotId)
}

func TestBundleTransactionRunner_RevertToSnapshot_RevertsStateDBAndTruncatesProcessedBundles(t *testing.T) {
func TestBundleTransactionRunner_RevertToSnapshot_CallsRevertToInterTxSnapshotOnStateDb(t *testing.T) {
ctrl := gomock.NewController(t)
statedb := state.NewMockStateDB(ctrl)
statedb.EXPECT().RevertToInterTxSnapshot(42)
state := state.NewMockStateDB(ctrl)

runner := bundleTransactionRunner{
ctxt: &runContext{
statedb: statedb,
},
processedBundles: []ProcessedBundle{{Position: 0}, {Position: 1}, {Position: 2}, {Position: 3}}, // length of 4
}
snapshotId := 123
state.EXPECT().RevertToInterTxSnapshot(snapshotId)

snapshot := bundleTransactionRunnerSnapshot{
stateDbSnapshot: 42,
processedBundleSnapshot: 2,
}
ctxt := &runContext{statedb: state}
bundleTransactionRunner := &bundleTransactionRunner{ctxt: ctxt}

runner.RevertToSnapshot(snapshot)
require.Equal(t, []ProcessedBundle{{Position: 0}, {Position: 1}}, runner.processedBundles)
bundleTransactionRunner.RevertToSnapshot(snapshotId)
}

// --- Utility functions for creating test transactions ---
Expand Down
18 changes: 9 additions & 9 deletions gossip/blockproc/bundle/bundle_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ import (
//
// This is the canonical implementation of the bundle execution logic, which
// defines the semantic of the execution flags.
func RunBundle[S any](
func RunBundle(
bundle *TransactionBundle,
runner TransactionRunner[S],
runner TransactionRunner,
) bool {
snapshot := runner.CreateSnapshot()

Expand All @@ -52,17 +52,17 @@ func RunBundle[S any](
// TransactionRunner defines an interface for running individual transactions
// within a bundle and obtaining their results, as used by the RunBundle
// function to determine the overall success of the bundle execution.
type TransactionRunner[S any] interface {
type TransactionRunner interface {
Run(tx *types.Transaction) core_types.TransactionResult
CreateSnapshot() S
RevertToSnapshot(id S)
CreateSnapshot() int
RevertToSnapshot(id int)
}

// runAllOfBundle executes all transactions in the bundle and returns true if
// all transactions are considered successful, false otherwise.
func runAllOfBundle[S any](
func runAllOfBundle(
bundle *TransactionBundle,
runner TransactionRunner[S],
runner TransactionRunner,
) bool {
for _, tx := range bundle.Transactions {
result := runner.Run(tx)
Expand All @@ -76,9 +76,9 @@ func runAllOfBundle[S any](
// runOneOfBundle executes the transactions in the bundle and stops at the first
// successful transaction. It returns true if at least one transaction is
// considered successful, false otherwise.
func runOneOfBundle[S any](
func runOneOfBundle(
bundle *TransactionBundle,
runner TransactionRunner[S],
runner TransactionRunner,
) bool {
for _, tx := range bundle.Transactions {
result := runner.Run(tx)
Expand Down
4 changes: 2 additions & 2 deletions gossip/blockproc/bundle/bundle_processor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func Test_RunBundle_RevertsToSnapshotOnFailure(t *testing.T) {

func Test_runAllOfBundle_ReturnsTrueForEmptyBundle(t *testing.T) {
emptyBundle := &TransactionBundle{Transactions: nil}
result := runAllOfBundle[any](emptyBundle, nil)
result := runAllOfBundle(emptyBundle, nil)
require.True(t, result)
}

Expand Down Expand Up @@ -168,7 +168,7 @@ func Test_runAllOfBundle_StopsAtFirstNonToleratedTransaction(t *testing.T) {

func Test_runOneOfBundle_ReturnsFalseForEmptyBundle(t *testing.T) {
emptyBundle := &TransactionBundle{Transactions: nil}
result := runOneOfBundle[any](emptyBundle, nil)
result := runOneOfBundle(emptyBundle, nil)
require.False(t, result)
}

Expand Down
Loading