From a306ca262fbcf8000e9d581b731b9b16dd166cdf Mon Sep 17 00:00:00 2001 From: Nazarii Denha Date: Tue, 25 Jun 2024 13:37:16 +0200 Subject: [PATCH 1/7] handle l1 rpc error --- core/state_transition.go | 5 +++++ core/vm/contracts.go | 16 ++++++++++++---- miner/worker.go | 7 ++++++- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/core/state_transition.go b/core/state_transition.go index 3a79ae2d9db9..49d24768212c 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -17,6 +17,7 @@ package core import ( + "errors" "fmt" "math" "math/big" @@ -412,6 +413,10 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value) stateTransitionEvmCallExecutionTimer.Update(time.Since(evmCallStart)) } + var errL1 *vm.ErrL1RPCError + if errors.As(vmerr, &errL1) { + return nil, vmerr + } // no refunds for l1 messages and system txs if st.msg.IsL1MessageTx() || st.msg.IsSystemTx() { diff --git a/core/vm/contracts.go b/core/vm/contracts.go index c52d47f63f12..237d89907014 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -1172,6 +1172,8 @@ func (c *l1sload) RequiredGas(input []byte) uint64 { } func (c *l1sload) Run(state StateDB, input []byte) ([]byte, error) { + const l1ClientRequestAttempts = 3 + if c.l1Client == nil { log.Error("No L1Client in the l1sload") return nil, ErrNoL1Client @@ -1197,10 +1199,16 @@ func (c *l1sload) Run(state StateDB, input []byte) ([]byte, error) { address := common.BytesToAddress(input[32:52]) key := common.BytesToHash(input[52:84]) - res, err := c.l1Client.StorageAt(context.Background(), address, key, block) - if err != nil { - return nil, &ErrL1RPCError{err: err} + var res []byte + var err error + for i := 0; i < l1ClientRequestAttempts; i++ { + res, err = c.l1Client.StorageAt(context.Background(), address, key, block) + if err != nil { + continue + } else { + return res, nil + } } + return nil, &ErrL1RPCError{err: err} - return res, nil } diff --git a/miner/worker.go b/miner/worker.go index 3450e3ec12c6..d3d925dcbff1 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -34,6 +34,7 @@ import ( "github.com/scroll-tech/go-ethereum/core/rawdb" "github.com/scroll-tech/go-ethereum/core/state" "github.com/scroll-tech/go-ethereum/core/types" + "github.com/scroll-tech/go-ethereum/core/vm" "github.com/scroll-tech/go-ethereum/event" "github.com/scroll-tech/go-ethereum/log" "github.com/scroll-tech/go-ethereum/metrics" @@ -1134,6 +1135,7 @@ loop: w.current.state.SetTxContext(tx.Hash(), w.current.tcount) logs, traces, err := w.commitTransaction(tx, coinbase) + var errL1 *vm.ErrL1RPCError switch { case errors.Is(err, core.ErrGasLimitReached) && tx.IsL1MessageTx(): // If this block already contains some L1 messages, @@ -1167,7 +1169,10 @@ loop: // Reorg notification data race between the transaction pool and miner, skip account = log.Trace("Skipping account with hight nonce", "sender", from, "nonce", tx.Nonce()) txs.Pop() - + case errors.As(err, &errL1): + // Skip the current transaction failed on L1Sload precompile with L1RpcError without shifting in the next from the account + log.Trace("Skipping transaction failed on L1Sload precompile with L1RpcError", "sender", from) + txs.Pop() case errors.Is(err, nil): // Everything ok, collect the logs and shift in the next transaction from the same account coalescedLogs = append(coalescedLogs, logs...) From d3adddf6213e1ac35d70e349893807eef1008e9b Mon Sep 17 00:00:00 2001 From: Nazarii Denha Date: Mon, 22 Jul 2024 10:50:47 +0200 Subject: [PATCH 2/7] add flag to precompile to determine whether it called form worker, add test --- core/state_transition.go | 2 + core/vm/contracts.go | 42 ++++++++++++++------- core/vm/interpreter.go | 12 +++++- miner/worker.go | 11 +++++- miner/worker_test.go | 79 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 130 insertions(+), 16 deletions(-) diff --git a/core/state_transition.go b/core/state_transition.go index 49d24768212c..e45b91f00a70 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -413,6 +413,8 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value) stateTransitionEvmCallExecutionTimer.Update(time.Since(evmCallStart)) } + + // This error can only be caught if CallerType in vm config is worker, worker will reinsert tx into txpool in case of this error var errL1 *vm.ErrL1RPCError if errors.As(vmerr, &errL1) { return nil, vmerr diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 3d761d8ea339..7e7449829fa7 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -22,6 +22,7 @@ import ( "encoding/binary" "errors" "math/big" + "time" "github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/common/math" @@ -145,7 +146,7 @@ func PrecompiledContractsDescartes(cfg Config) map[common.Address]PrecompiledCon common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, common.BytesToAddress([]byte{9}): &blake2FDisabled{}, // TODO final contract address to be decided - common.BytesToAddress([]byte{1, 1}): &l1sload{l1Client: cfg.L1Client}, + common.BytesToAddress([]byte{1, 1}): &l1sload{l1Client: cfg.L1Client, callerType: cfg.CallerType}, } } @@ -1164,7 +1165,8 @@ func (c *bls12381MapG2) Run(state StateDB, input []byte) ([]byte, error) { // L1SLoad precompiled type l1sload struct { - l1Client L1Client + l1Client L1Client + callerType CallerType } // RequiredGas returns the gas required to execute the pre-compiled contract. @@ -1179,7 +1181,8 @@ func (c *l1sload) RequiredGas(input []byte) uint64 { } func (c *l1sload) Run(state StateDB, input []byte) ([]byte, error) { - const l1ClientRequestAttempts = 3 + log.Info("l1sload", "input", input) + const l1ClientMaxRetries = 3 if c.l1Client == nil { log.Error("No L1Client in the l1sload") @@ -1200,16 +1203,29 @@ func (c *l1sload) Run(state StateDB, input []byte) ([]byte, error) { keys[i] = common.BytesToHash(input[20+32*i : 52+32*i]) } - var res []byte - var err error - for i := 0; i < l1ClientRequestAttempts; i++ { - res, err = c.l1Client.StoragesAt(context.Background(), address, keys, block) - if err != nil { - continue - } else { - return res, nil + // if caller type is non-worker then we can retry request multiple times and return err, the tx will be reinserted in tx poll + // otherwise, we should retry requests forever + if c.callerType == CallerTypeNonWorker { + for { + res, err := c.l1Client.StoragesAt(context.Background(), address, keys, block) + if err == nil { + return res, nil + } + // wait before retrying + time.Sleep(100 * time.Millisecond) + log.Warn("L1 client request error", "err", err) } + } else { + var innerErr error + for i := 0; i < l1ClientMaxRetries; i++ { + res, err := c.l1Client.StoragesAt(context.Background(), address, keys, block) + if err != nil { + innerErr = err + continue + } else { + return res, nil + } + } + return nil, &ErrL1RPCError{err: innerErr} } - return nil, &ErrL1RPCError{err: err} - } diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index aafe5cf25119..b4d1d8149277 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -44,9 +44,19 @@ type Config struct { ExtraEips []int // Additional EIPS that are to be enabled - L1Client L1Client // L1 RPC client + L1Client L1Client // L1 RPC client + CallerType CallerType // caller type is used in L1Sload precompile to determine whether to retry RPC call forever in case of error } +type CallerType int + +const ( + // NonWorker + CallerTypeNonWorker CallerType = iota + // Worker + CallerTypeWorker +) + // ScopeContext contains the things that are per-call, such as stack and memory, // but not transients like pc and gas type ScopeContext struct { diff --git a/miner/worker.go b/miner/worker.go index d3d925dcbff1..0d49d617c717 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1007,9 +1007,13 @@ func (w *worker) commitTransaction(tx *types.Transaction, coinbase common.Addres // create new snapshot for `core.ApplyTransaction` snap := w.current.state.Snapshot() + // todo: apply this changes to new worker when merged with upstream + // make a copy of vm config and change caller type to worker + var vmConf vm.Config = *w.chain.GetVMConfig() + vmConf.CallerType = vm.CallerTypeWorker var receipt *types.Receipt common.WithTimer(l2CommitTxApplyTimer, func() { - receipt, err = core.ApplyTransaction(w.chainConfig, w.chain, &coinbase, w.current.gasPool, w.current.state, w.current.header, tx, &w.current.header.GasUsed, *w.chain.GetVMConfig()) + receipt, err = core.ApplyTransaction(w.chainConfig, w.chain, &coinbase, w.current.gasPool, w.current.state, w.current.header, tx, &w.current.header.GasUsed, vmConf) }) if err != nil { w.current.state.RevertToSnapshot(snap) @@ -1169,10 +1173,13 @@ loop: // Reorg notification data race between the transaction pool and miner, skip account = log.Trace("Skipping account with hight nonce", "sender", from, "nonce", tx.Nonce()) txs.Pop() + case errors.As(err, &errL1): - // Skip the current transaction failed on L1Sload precompile with L1RpcError without shifting in the next from the account + // Skip the current transaction failed on L1Sload precompile with L1RpcError without shifting in the next from the account, this tx will be left in txpool and retried in future block log.Trace("Skipping transaction failed on L1Sload precompile with L1RpcError", "sender", from) + atomic.AddInt32(&w.newTxs, int32(1)) txs.Pop() + case errors.Is(err, nil): // Everything ok, collect the logs and shift in the next transaction from the same account coalescedLogs = append(coalescedLogs, logs...) diff --git a/miner/worker_test.go b/miner/worker_test.go index e20dc00dae47..c998fe160dc5 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -17,6 +17,8 @@ package miner import ( + "context" + "errors" "math" "math/big" "math/rand" @@ -1198,6 +1200,83 @@ func TestPrioritizeOverflowTx(t *testing.T) { } } +type mockL1Client struct { + failList []bool +} + +func (c *mockL1Client) StoragesAt(ctx context.Context, account common.Address, keys []common.Hash, blockNumber *big.Int) ([]byte, error) { + if len(c.failList) == 0 { + return common.Hash{}.Bytes(), nil + } + failed := c.failList[0] + c.failList = c.failList[1:] + if failed { + return nil, errors.New("error") + } else { + return common.Hash{}.Bytes(), nil + } +} + +func TestL1SloadFailedTxReexecuted(t *testing.T) { + assert := assert.New(t) + + var ( + chainConfig = params.AllCliqueProtocolChanges + db = rawdb.NewMemoryDatabase() + engine = clique.New(chainConfig.Clique, db) + ) + + chainConfig.Clique = ¶ms.CliqueConfig{Period: 1, Epoch: 30000} + chainConfig.LondonBlock = big.NewInt(0) + chainConfig.DescartesBlock = big.NewInt(0) + + w, b := newTestWorker(t, chainConfig, engine, db, 0) + // GetStoragesAt will shouldn't fail 2 times on block tracing and should fail then on tx executing + w.chain.GetVMConfig().L1Client = &mockL1Client{failList: []bool{false, false, true, true, true}} + defer w.close() + + // This test chain imports the mined blocks. + db2 := rawdb.NewMemoryDatabase() + b.genesis.MustCommit(db2) + chain, _ := core.NewBlockChain(db2, nil, b.chain.Config(), engine, vm.Config{ + Debug: true, + Tracer: vm.NewStructLogger(&vm.LogConfig{EnableMemory: true, EnableReturnData: true})}, nil, nil) + defer chain.Stop() + chain.GetVMConfig().L1Client = &mockL1Client{} + + // Ignore empty commit here for less noise. + w.skipSealHook = func(task *task) bool { + return len(task.receipts) == 0 + } + + // Wait for mined blocks. + sub := w.mux.Subscribe(core.NewMinedBlockEvent{}) + defer sub.Unsubscribe() + + // Define tx that calls L1Sload + l1SlaodAddress := common.BytesToAddress([]byte{1, 1}) + input := make([]byte, 52) + tx, _ := types.SignTx(types.NewTransaction(b.txPool.Nonce(testBankAddress), l1SlaodAddress, big.NewInt(0), 25208, big.NewInt(10*params.InitialBaseFee), input), types.HomesteadSigner{}, testBankKey) + + // Process 2 transactions with gas order: tx0 > tx1, tx1 will overflow. + b.txPool.AddRemote(tx) + // b.txPool.AddLocal(b.newRandomTx(false)) + w.start() + + select { + case ev := <-sub.Chan(): + w.stop() + block := ev.Data.(core.NewMinedBlockEvent).Block + assert.Equal(1, len(block.Transactions())) + assert.Equal(tx.Hash(), block.Transactions()[0].Hash()) + if _, err := chain.InsertChain([]*types.Block{block}); err != nil { + t.Fatalf("failed to insert new mined block %d: %v", block.NumberU64(), err) + } + case <-time.After(3 * time.Second): // Worker needs 1s to include new changes. + t.Fatalf("timeout") + } +} + func TestSkippedTransactionDatabaseEntries(t *testing.T) { assert := assert.New(t) From 8f10e58f081143187a46f42ac962a9a1bffc40dc Mon Sep 17 00:00:00 2001 From: Nazarii Denha Date: Mon, 22 Jul 2024 11:20:19 +0200 Subject: [PATCH 3/7] comment test --- miner/worker_test.go | 154 +++++++++++++++++++++---------------------- 1 file changed, 76 insertions(+), 78 deletions(-) diff --git a/miner/worker_test.go b/miner/worker_test.go index c998fe160dc5..8091a7124074 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -17,8 +17,6 @@ package miner import ( - "context" - "errors" "math" "math/big" "math/rand" @@ -1200,82 +1198,82 @@ func TestPrioritizeOverflowTx(t *testing.T) { } } -type mockL1Client struct { - failList []bool -} - -func (c *mockL1Client) StoragesAt(ctx context.Context, account common.Address, keys []common.Hash, blockNumber *big.Int) ([]byte, error) { - if len(c.failList) == 0 { - return common.Hash{}.Bytes(), nil - } - failed := c.failList[0] - c.failList = c.failList[1:] - if failed { - return nil, errors.New("error") - } else { - return common.Hash{}.Bytes(), nil - } -} - -func TestL1SloadFailedTxReexecuted(t *testing.T) { - assert := assert.New(t) - - var ( - chainConfig = params.AllCliqueProtocolChanges - db = rawdb.NewMemoryDatabase() - engine = clique.New(chainConfig.Clique, db) - ) - - chainConfig.Clique = ¶ms.CliqueConfig{Period: 1, Epoch: 30000} - chainConfig.LondonBlock = big.NewInt(0) - chainConfig.DescartesBlock = big.NewInt(0) - - w, b := newTestWorker(t, chainConfig, engine, db, 0) - // GetStoragesAt will shouldn't fail 2 times on block tracing and should fail then on tx executing - w.chain.GetVMConfig().L1Client = &mockL1Client{failList: []bool{false, false, true, true, true}} - defer w.close() - - // This test chain imports the mined blocks. - db2 := rawdb.NewMemoryDatabase() - b.genesis.MustCommit(db2) - chain, _ := core.NewBlockChain(db2, nil, b.chain.Config(), engine, vm.Config{ - Debug: true, - Tracer: vm.NewStructLogger(&vm.LogConfig{EnableMemory: true, EnableReturnData: true})}, nil, nil) - defer chain.Stop() - chain.GetVMConfig().L1Client = &mockL1Client{} - - // Ignore empty commit here for less noise. - w.skipSealHook = func(task *task) bool { - return len(task.receipts) == 0 - } - - // Wait for mined blocks. - sub := w.mux.Subscribe(core.NewMinedBlockEvent{}) - defer sub.Unsubscribe() - - // Define tx that calls L1Sload - l1SlaodAddress := common.BytesToAddress([]byte{1, 1}) - input := make([]byte, 52) - tx, _ := types.SignTx(types.NewTransaction(b.txPool.Nonce(testBankAddress), l1SlaodAddress, big.NewInt(0), 25208, big.NewInt(10*params.InitialBaseFee), input), types.HomesteadSigner{}, testBankKey) - - // Process 2 transactions with gas order: tx0 > tx1, tx1 will overflow. - b.txPool.AddRemote(tx) - // b.txPool.AddLocal(b.newRandomTx(false)) - w.start() - - select { - case ev := <-sub.Chan(): - w.stop() - block := ev.Data.(core.NewMinedBlockEvent).Block - assert.Equal(1, len(block.Transactions())) - assert.Equal(tx.Hash(), block.Transactions()[0].Hash()) - if _, err := chain.InsertChain([]*types.Block{block}); err != nil { - t.Fatalf("failed to insert new mined block %d: %v", block.NumberU64(), err) - } - case <-time.After(3 * time.Second): // Worker needs 1s to include new changes. - t.Fatalf("timeout") - } -} +// type mockL1Client struct { +// failList []bool +// } + +// func (c *mockL1Client) StoragesAt(ctx context.Context, account common.Address, keys []common.Hash, blockNumber *big.Int) ([]byte, error) { +// if len(c.failList) == 0 { +// return common.Hash{}.Bytes(), nil +// } +// failed := c.failList[0] +// c.failList = c.failList[1:] +// if failed { +// return nil, errors.New("error") +// } else { +// return common.Hash{}.Bytes(), nil +// } +// } + +// func TestL1SloadFailedTxReexecuted(t *testing.T) { +// assert := assert.New(t) + +// var ( +// chainConfig = params.AllCliqueProtocolChanges +// db = rawdb.NewMemoryDatabase() +// engine = clique.New(chainConfig.Clique, db) +// ) + +// chainConfig.Clique = ¶ms.CliqueConfig{Period: 1, Epoch: 30000} +// chainConfig.LondonBlock = big.NewInt(0) +// chainConfig.DescartesBlock = big.NewInt(0) + +// w, b := newTestWorker(t, chainConfig, engine, db, 0) +// // GetStoragesAt will shouldn't fail 2 times on block tracing and should fail then on tx executing +// w.chain.GetVMConfig().L1Client = &mockL1Client{failList: []bool{false, false, true, true, true}} +// defer w.close() + +// // This test chain imports the mined blocks. +// db2 := rawdb.NewMemoryDatabase() +// b.genesis.MustCommit(db2) +// chain, _ := core.NewBlockChain(db2, nil, b.chain.Config(), engine, vm.Config{ +// Debug: true, +// Tracer: vm.NewStructLogger(&vm.LogConfig{EnableMemory: true, EnableReturnData: true})}, nil, nil) +// defer chain.Stop() +// chain.GetVMConfig().L1Client = &mockL1Client{} + +// // Ignore empty commit here for less noise. +// w.skipSealHook = func(task *task) bool { +// return len(task.receipts) == 0 +// } + +// // Wait for mined blocks. +// sub := w.mux.Subscribe(core.NewMinedBlockEvent{}) +// defer sub.Unsubscribe() + +// // Define tx that calls L1Sload +// l1SlaodAddress := common.BytesToAddress([]byte{1, 1}) +// input := make([]byte, 52) +// tx, _ := types.SignTx(types.NewTransaction(b.txPool.Nonce(testBankAddress), l1SlaodAddress, big.NewInt(0), 25208, big.NewInt(10*params.InitialBaseFee), input), types.HomesteadSigner{}, testBankKey) + +// // Process 2 transactions with gas order: tx0 > tx1, tx1 will overflow. +// b.txPool.AddRemote(tx) +// // b.txPool.AddLocal(b.newRandomTx(false)) +// w.start() + +// select { +// case ev := <-sub.Chan(): +// w.stop() +// block := ev.Data.(core.NewMinedBlockEvent).Block +// assert.Equal(1, len(block.Transactions())) +// assert.Equal(tx.Hash(), block.Transactions()[0].Hash()) +// if _, err := chain.InsertChain([]*types.Block{block}); err != nil { +// t.Fatalf("failed to insert new mined block %d: %v", block.NumberU64(), err) +// } +// case <-time.After(3 * time.Second): // Worker needs 1s to include new changes. +// t.Fatalf("timeout") +// } +// } func TestSkippedTransactionDatabaseEntries(t *testing.T) { assert := assert.New(t) From 712fd3bc6025cd01639c06898ca67f63810e6215 Mon Sep 17 00:00:00 2001 From: Nazarii Denha Date: Mon, 22 Jul 2024 11:26:39 +0200 Subject: [PATCH 4/7] trigger worker test --- miner/worker_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/miner/worker_test.go b/miner/worker_test.go index 8091a7124074..d521b8797f13 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -1196,6 +1196,12 @@ func TestPrioritizeOverflowTx(t *testing.T) { case <-time.After(3 * time.Second): // Worker needs 1s to include new changes. t.Fatalf("timeout") } + t.Fatalf("a") +} + +func TestTrigger(t *testing.T) { + assert := assert.New(t) + assert.Equal(1, 1) } // type mockL1Client struct { From 11ad39879dfbc529367b3a1489d68244b23c6d9b Mon Sep 17 00:00:00 2001 From: Nazarii Denha Date: Mon, 22 Jul 2024 11:39:02 +0200 Subject: [PATCH 5/7] test --- miner/worker_test.go | 155 +++++++++++++++++++++---------------------- 1 file changed, 75 insertions(+), 80 deletions(-) diff --git a/miner/worker_test.go b/miner/worker_test.go index d521b8797f13..1df2a47a1f5b 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -17,6 +17,8 @@ package miner import ( + "context" + "errors" "math" "math/big" "math/rand" @@ -1196,90 +1198,83 @@ func TestPrioritizeOverflowTx(t *testing.T) { case <-time.After(3 * time.Second): // Worker needs 1s to include new changes. t.Fatalf("timeout") } - t.Fatalf("a") } -func TestTrigger(t *testing.T) { - assert := assert.New(t) - assert.Equal(1, 1) +type mockL1Client struct { + failList []bool } -// type mockL1Client struct { -// failList []bool -// } - -// func (c *mockL1Client) StoragesAt(ctx context.Context, account common.Address, keys []common.Hash, blockNumber *big.Int) ([]byte, error) { -// if len(c.failList) == 0 { -// return common.Hash{}.Bytes(), nil -// } -// failed := c.failList[0] -// c.failList = c.failList[1:] -// if failed { -// return nil, errors.New("error") -// } else { -// return common.Hash{}.Bytes(), nil -// } -// } - -// func TestL1SloadFailedTxReexecuted(t *testing.T) { -// assert := assert.New(t) - -// var ( -// chainConfig = params.AllCliqueProtocolChanges -// db = rawdb.NewMemoryDatabase() -// engine = clique.New(chainConfig.Clique, db) -// ) - -// chainConfig.Clique = ¶ms.CliqueConfig{Period: 1, Epoch: 30000} -// chainConfig.LondonBlock = big.NewInt(0) -// chainConfig.DescartesBlock = big.NewInt(0) - -// w, b := newTestWorker(t, chainConfig, engine, db, 0) -// // GetStoragesAt will shouldn't fail 2 times on block tracing and should fail then on tx executing -// w.chain.GetVMConfig().L1Client = &mockL1Client{failList: []bool{false, false, true, true, true}} -// defer w.close() - -// // This test chain imports the mined blocks. -// db2 := rawdb.NewMemoryDatabase() -// b.genesis.MustCommit(db2) -// chain, _ := core.NewBlockChain(db2, nil, b.chain.Config(), engine, vm.Config{ -// Debug: true, -// Tracer: vm.NewStructLogger(&vm.LogConfig{EnableMemory: true, EnableReturnData: true})}, nil, nil) -// defer chain.Stop() -// chain.GetVMConfig().L1Client = &mockL1Client{} - -// // Ignore empty commit here for less noise. -// w.skipSealHook = func(task *task) bool { -// return len(task.receipts) == 0 -// } - -// // Wait for mined blocks. -// sub := w.mux.Subscribe(core.NewMinedBlockEvent{}) -// defer sub.Unsubscribe() - -// // Define tx that calls L1Sload -// l1SlaodAddress := common.BytesToAddress([]byte{1, 1}) -// input := make([]byte, 52) -// tx, _ := types.SignTx(types.NewTransaction(b.txPool.Nonce(testBankAddress), l1SlaodAddress, big.NewInt(0), 25208, big.NewInt(10*params.InitialBaseFee), input), types.HomesteadSigner{}, testBankKey) - -// // Process 2 transactions with gas order: tx0 > tx1, tx1 will overflow. -// b.txPool.AddRemote(tx) -// // b.txPool.AddLocal(b.newRandomTx(false)) -// w.start() - -// select { -// case ev := <-sub.Chan(): -// w.stop() -// block := ev.Data.(core.NewMinedBlockEvent).Block -// assert.Equal(1, len(block.Transactions())) -// assert.Equal(tx.Hash(), block.Transactions()[0].Hash()) -// if _, err := chain.InsertChain([]*types.Block{block}); err != nil { -// t.Fatalf("failed to insert new mined block %d: %v", block.NumberU64(), err) -// } -// case <-time.After(3 * time.Second): // Worker needs 1s to include new changes. -// t.Fatalf("timeout") -// } -// } +func (c *mockL1Client) StoragesAt(ctx context.Context, account common.Address, keys []common.Hash, blockNumber *big.Int) ([]byte, error) { + if len(c.failList) == 0 { + return common.Hash{}.Bytes(), nil + } + failed := c.failList[0] + c.failList = c.failList[1:] + if failed { + return nil, errors.New("error") + } else { + return common.Hash{}.Bytes(), nil + } +} + +func TestL1SloadFailedTxReexecuted(t *testing.T) { + assert := assert.New(t) + + var ( + chainConfig = params.AllCliqueProtocolChanges + db = rawdb.NewMemoryDatabase() + engine = clique.New(chainConfig.Clique, db) + ) + + chainConfig.Clique = ¶ms.CliqueConfig{Period: 1, Epoch: 30000} + chainConfig.LondonBlock = big.NewInt(0) + chainConfig.DescartesBlock = big.NewInt(0) + + w, b := newTestWorker(t, chainConfig, engine, db, 0) + // GetStoragesAt shouldn't fail 2 times on block tracing and should fail then on tx executing + w.chain.GetVMConfig().L1Client = &mockL1Client{failList: []bool{false, false, true, true, true}} + defer w.close() + + // This test chain imports the mined blocks. + db2 := rawdb.NewMemoryDatabase() + b.genesis.MustCommit(db2) + chain, _ := core.NewBlockChain(db2, nil, b.chain.Config(), engine, vm.Config{ + Debug: true, + Tracer: vm.NewStructLogger(&vm.LogConfig{EnableMemory: true, EnableReturnData: true})}, nil, nil) + defer chain.Stop() + chain.GetVMConfig().L1Client = &mockL1Client{} + + // Ignore empty commit here for less noise. + w.skipSealHook = func(task *task) bool { + return len(task.receipts) == 0 + } + + // Wait for mined blocks. + sub := w.mux.Subscribe(core.NewMinedBlockEvent{}) + defer sub.Unsubscribe() + + // Define tx that calls L1Sload + l1SlaodAddress := common.BytesToAddress([]byte{1, 1}) + input := make([]byte, 52) + tx, _ := types.SignTx(types.NewTransaction(b.txPool.Nonce(testBankAddress), l1SlaodAddress, big.NewInt(0), 25208, big.NewInt(10*params.InitialBaseFee), input), types.HomesteadSigner{}, testBankKey) + + // Process 2 transactions with gas order: tx0 > tx1, tx1 will overflow. + b.txPool.AddLocal(tx) + w.start() + + select { + case ev := <-sub.Chan(): + w.stop() + block := ev.Data.(core.NewMinedBlockEvent).Block + assert.Equal(1, len(block.Transactions())) + assert.Equal(tx.Hash(), block.Transactions()[0].Hash()) + if _, err := chain.InsertChain([]*types.Block{block}); err != nil { + t.Fatalf("failed to insert new mined block %d: %v", block.NumberU64(), err) + } + case <-time.After(3 * time.Second): // Worker needs 1s to include new changes. + t.Fatalf("timeout") + } +} func TestSkippedTransactionDatabaseEntries(t *testing.T) { assert := assert.New(t) From c28fe4bee2b437cf2ede81d47e180c992df60db1 Mon Sep 17 00:00:00 2001 From: Nazarii Denha Date: Mon, 22 Jul 2024 11:46:05 +0200 Subject: [PATCH 6/7] fix comment --- miner/worker_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/miner/worker_test.go b/miner/worker_test.go index 1df2a47a1f5b..91ff199b2641 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -1258,7 +1258,7 @@ func TestL1SloadFailedTxReexecuted(t *testing.T) { input := make([]byte, 52) tx, _ := types.SignTx(types.NewTransaction(b.txPool.Nonce(testBankAddress), l1SlaodAddress, big.NewInt(0), 25208, big.NewInt(10*params.InitialBaseFee), input), types.HomesteadSigner{}, testBankKey) - // Process 2 transactions with gas order: tx0 > tx1, tx1 will overflow. + // Process l1sload tx b.txPool.AddLocal(tx) w.start() From edeb6d904919bf61e13c0ec6982a40eb87d10a00 Mon Sep 17 00:00:00 2001 From: Nazarii Denha Date: Mon, 22 Jul 2024 12:20:08 +0200 Subject: [PATCH 7/7] handle err in tracing --- miner/worker.go | 2 +- miner/worker_test.go | 8 +++++--- rollup/tracing/tracing.go | 13 ++++++++----- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/miner/worker.go b/miner/worker.go index 0d49d617c717..3bbc848c202b 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -847,7 +847,7 @@ func (w *worker) makeCurrent(parent *types.Block, header *types.Header) error { // don't commit the state during tracing for circuit capacity checker, otherwise we cannot revert. // and even if we don't commit the state, the `refund` value will still be correct, as explained in `CommitTransaction` commitStateAfterApply := false - traceEnv, err := tracing.CreateTraceEnv(w.chainConfig, w.chain, w.engine, w.eth.ChainDb(), state, w.chain.GetVMConfig().L1Client, parent, + traceEnv, err := tracing.CreateTraceEnv(w.chainConfig, w.chain, w.engine, w.eth.ChainDb(), state, w.chain.GetVMConfig().L1Client, vm.CallerTypeWorker, parent, // new block with a placeholder tx, for traceEnv's ExecutionResults length & TxStorageTraces length types.NewBlockWithHeader(header).WithBody([]*types.Transaction{types.NewTx(&types.LegacyTx{})}, nil), commitStateAfterApply) diff --git a/miner/worker_test.go b/miner/worker_test.go index 91ff199b2641..53230b1bc6cc 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -1231,8 +1231,10 @@ func TestL1SloadFailedTxReexecuted(t *testing.T) { chainConfig.DescartesBlock = big.NewInt(0) w, b := newTestWorker(t, chainConfig, engine, db, 0) - // GetStoragesAt shouldn't fail 2 times on block tracing and should fail then on tx executing - w.chain.GetVMConfig().L1Client = &mockL1Client{failList: []bool{false, false, true, true, true}} + // GetStoragesAt should fail at tracing request 2 times (3 retries for each), commitTransaction will fail during tracing and will be retried in next work + // after that GetStoragesAt shouls pass tracing 2 times and then fail on execution tx (3 retries) + // after that tx will be retried again and executed without fails + w.chain.GetVMConfig().L1Client = &mockL1Client{failList: []bool{true, true, true, true, true, true, false, false, true, true, true}} defer w.close() // This test chain imports the mined blocks. @@ -1271,7 +1273,7 @@ func TestL1SloadFailedTxReexecuted(t *testing.T) { if _, err := chain.InsertChain([]*types.Block{block}); err != nil { t.Fatalf("failed to insert new mined block %d: %v", block.NumberU64(), err) } - case <-time.After(3 * time.Second): // Worker needs 1s to include new changes. + case <-time.After(5 * time.Second): // Worker needs 1s to include new changes. t.Fatalf("timeout") } } diff --git a/rollup/tracing/tracing.go b/rollup/tracing/tracing.go index 6589d5f7e05a..a82025602d23 100644 --- a/rollup/tracing/tracing.go +++ b/rollup/tracing/tracing.go @@ -47,7 +47,7 @@ func NewTracerWrapper() *TracerWrapper { // CreateTraceEnvAndGetBlockTrace wraps the whole block tracing logic for a block func (tw *TracerWrapper) CreateTraceEnvAndGetBlockTrace(chainConfig *params.ChainConfig, chainContext core.ChainContext, engine consensus.Engine, chaindb ethdb.Database, statedb *state.StateDB, l1Client vm.L1Client, parent *types.Block, block *types.Block, commitAfterApply bool) (*types.BlockTrace, error) { - traceEnv, err := CreateTraceEnv(chainConfig, chainContext, engine, chaindb, statedb, l1Client, parent, block, commitAfterApply) + traceEnv, err := CreateTraceEnv(chainConfig, chainContext, engine, chaindb, statedb, l1Client, vm.CallerTypeNonWorker, parent, block, commitAfterApply) if err != nil { return nil, err } @@ -60,6 +60,7 @@ type TraceEnv struct { commitAfterApply bool chainConfig *params.ChainConfig l1Client vm.L1Client + callerType vm.CallerType coinbase common.Address @@ -99,13 +100,14 @@ type txTraceTask struct { index int } -func CreateTraceEnvHelper(chainConfig *params.ChainConfig, logConfig *vm.LogConfig, l1Client vm.L1Client, blockCtx vm.BlockContext, startL1QueueIndex uint64, coinbase common.Address, statedb *state.StateDB, rootBefore common.Hash, block *types.Block, commitAfterApply bool) *TraceEnv { +func CreateTraceEnvHelper(chainConfig *params.ChainConfig, logConfig *vm.LogConfig, l1Client vm.L1Client, callerType vm.CallerType, blockCtx vm.BlockContext, startL1QueueIndex uint64, coinbase common.Address, statedb *state.StateDB, rootBefore common.Hash, block *types.Block, commitAfterApply bool) *TraceEnv { return &TraceEnv{ logConfig: logConfig, commitAfterApply: commitAfterApply, chainConfig: chainConfig, coinbase: coinbase, l1Client: l1Client, + callerType: callerType, signer: types.MakeSigner(chainConfig, block.Number()), state: statedb, blockCtx: blockCtx, @@ -122,7 +124,7 @@ func CreateTraceEnvHelper(chainConfig *params.ChainConfig, logConfig *vm.LogConf } } -func CreateTraceEnv(chainConfig *params.ChainConfig, chainContext core.ChainContext, engine consensus.Engine, chaindb ethdb.Database, statedb *state.StateDB, l1Client vm.L1Client, parent *types.Block, block *types.Block, commitAfterApply bool) (*TraceEnv, error) { +func CreateTraceEnv(chainConfig *params.ChainConfig, chainContext core.ChainContext, engine consensus.Engine, chaindb ethdb.Database, statedb *state.StateDB, l1Client vm.L1Client, callerType vm.CallerType, parent *types.Block, block *types.Block, commitAfterApply bool) (*TraceEnv, error) { var coinbase common.Address var err error @@ -160,6 +162,7 @@ func CreateTraceEnv(chainConfig *params.ChainConfig, chainContext core.ChainCont EnableReturnData: true, }, l1Client, + callerType, core.NewEVMBlockContext(block.Header(), chainContext, chainConfig, nil), *startL1QueueIndex, coinbase, @@ -231,7 +234,7 @@ func (env *TraceEnv) GetBlockTrace(block *types.Block) (*types.BlockTrace, error // Generate the next state snapshot fast without tracing msg, _ := tx.AsMessage(env.signer, block.BaseFee()) env.state.SetTxContext(tx.Hash(), i) - vmenv := vm.NewEVM(env.blockCtx, core.NewEVMTxContext(msg), env.state, env.chainConfig, vm.Config{L1Client: env.l1Client}) + vmenv := vm.NewEVM(env.blockCtx, core.NewEVMTxContext(msg), env.state, env.chainConfig, vm.Config{L1Client: env.l1Client, CallerType: env.callerType}) l1DataFee, err := fees.CalculateL1DataFee(tx, env.state) if err != nil { failed = err @@ -332,7 +335,7 @@ func (env *TraceEnv) getTxResult(state *state.StateDB, index int, block *types.B structLogger := vm.NewStructLogger(env.logConfig) tracer := NewMuxTracer(structLogger, callTracer, prestateTracer) // Run the transaction with tracing enabled. - vmenv := vm.NewEVM(env.blockCtx, txContext, state, env.chainConfig, vm.Config{L1Client: env.l1Client, Debug: true, Tracer: tracer, NoBaseFee: true}) + vmenv := vm.NewEVM(env.blockCtx, txContext, state, env.chainConfig, vm.Config{L1Client: env.l1Client, Debug: true, Tracer: tracer, NoBaseFee: true, CallerType: env.callerType}) // Call Prepare to clear out the statedb access list state.SetTxContext(txctx.TxHash, txctx.TxIndex)