Skip to content

Commit d3adddf

Browse files
committed
add flag to precompile to determine whether it called form worker, add test
1 parent aac7d47 commit d3adddf

File tree

5 files changed

+130
-16
lines changed

5 files changed

+130
-16
lines changed

core/state_transition.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,8 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
413413
ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value)
414414
stateTransitionEvmCallExecutionTimer.Update(time.Since(evmCallStart))
415415
}
416+
417+
// This error can only be caught if CallerType in vm config is worker, worker will reinsert tx into txpool in case of this error
416418
var errL1 *vm.ErrL1RPCError
417419
if errors.As(vmerr, &errL1) {
418420
return nil, vmerr

core/vm/contracts.go

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"encoding/binary"
2323
"errors"
2424
"math/big"
25+
"time"
2526

2627
"github.com/scroll-tech/go-ethereum/common"
2728
"github.com/scroll-tech/go-ethereum/common/math"
@@ -145,7 +146,7 @@ func PrecompiledContractsDescartes(cfg Config) map[common.Address]PrecompiledCon
145146
common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{},
146147
common.BytesToAddress([]byte{9}): &blake2FDisabled{},
147148
// TODO final contract address to be decided
148-
common.BytesToAddress([]byte{1, 1}): &l1sload{l1Client: cfg.L1Client},
149+
common.BytesToAddress([]byte{1, 1}): &l1sload{l1Client: cfg.L1Client, callerType: cfg.CallerType},
149150
}
150151
}
151152

@@ -1164,7 +1165,8 @@ func (c *bls12381MapG2) Run(state StateDB, input []byte) ([]byte, error) {
11641165

11651166
// L1SLoad precompiled
11661167
type l1sload struct {
1167-
l1Client L1Client
1168+
l1Client L1Client
1169+
callerType CallerType
11681170
}
11691171

11701172
// RequiredGas returns the gas required to execute the pre-compiled contract.
@@ -1179,7 +1181,8 @@ func (c *l1sload) RequiredGas(input []byte) uint64 {
11791181
}
11801182

11811183
func (c *l1sload) Run(state StateDB, input []byte) ([]byte, error) {
1182-
const l1ClientRequestAttempts = 3
1184+
log.Info("l1sload", "input", input)
1185+
const l1ClientMaxRetries = 3
11831186

11841187
if c.l1Client == nil {
11851188
log.Error("No L1Client in the l1sload")
@@ -1200,16 +1203,29 @@ func (c *l1sload) Run(state StateDB, input []byte) ([]byte, error) {
12001203
keys[i] = common.BytesToHash(input[20+32*i : 52+32*i])
12011204
}
12021205

1203-
var res []byte
1204-
var err error
1205-
for i := 0; i < l1ClientRequestAttempts; i++ {
1206-
res, err = c.l1Client.StoragesAt(context.Background(), address, keys, block)
1207-
if err != nil {
1208-
continue
1209-
} else {
1210-
return res, nil
1206+
// if caller type is non-worker then we can retry request multiple times and return err, the tx will be reinserted in tx poll
1207+
// otherwise, we should retry requests forever
1208+
if c.callerType == CallerTypeNonWorker {
1209+
for {
1210+
res, err := c.l1Client.StoragesAt(context.Background(), address, keys, block)
1211+
if err == nil {
1212+
return res, nil
1213+
}
1214+
// wait before retrying
1215+
time.Sleep(100 * time.Millisecond)
1216+
log.Warn("L1 client request error", "err", err)
12111217
}
1218+
} else {
1219+
var innerErr error
1220+
for i := 0; i < l1ClientMaxRetries; i++ {
1221+
res, err := c.l1Client.StoragesAt(context.Background(), address, keys, block)
1222+
if err != nil {
1223+
innerErr = err
1224+
continue
1225+
} else {
1226+
return res, nil
1227+
}
1228+
}
1229+
return nil, &ErrL1RPCError{err: innerErr}
12121230
}
1213-
return nil, &ErrL1RPCError{err: err}
1214-
12151231
}

core/vm/interpreter.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,19 @@ type Config struct {
4444

4545
ExtraEips []int // Additional EIPS that are to be enabled
4646

47-
L1Client L1Client // L1 RPC client
47+
L1Client L1Client // L1 RPC client
48+
CallerType CallerType // caller type is used in L1Sload precompile to determine whether to retry RPC call forever in case of error
4849
}
4950

51+
type CallerType int
52+
53+
const (
54+
// NonWorker
55+
CallerTypeNonWorker CallerType = iota
56+
// Worker
57+
CallerTypeWorker
58+
)
59+
5060
// ScopeContext contains the things that are per-call, such as stack and memory,
5161
// but not transients like pc and gas
5262
type ScopeContext struct {

miner/worker.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,9 +1007,13 @@ func (w *worker) commitTransaction(tx *types.Transaction, coinbase common.Addres
10071007
// create new snapshot for `core.ApplyTransaction`
10081008
snap := w.current.state.Snapshot()
10091009

1010+
// todo: apply this changes to new worker when merged with upstream
1011+
// make a copy of vm config and change caller type to worker
1012+
var vmConf vm.Config = *w.chain.GetVMConfig()
1013+
vmConf.CallerType = vm.CallerTypeWorker
10101014
var receipt *types.Receipt
10111015
common.WithTimer(l2CommitTxApplyTimer, func() {
1012-
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())
1016+
receipt, err = core.ApplyTransaction(w.chainConfig, w.chain, &coinbase, w.current.gasPool, w.current.state, w.current.header, tx, &w.current.header.GasUsed, vmConf)
10131017
})
10141018
if err != nil {
10151019
w.current.state.RevertToSnapshot(snap)
@@ -1169,10 +1173,13 @@ loop:
11691173
// Reorg notification data race between the transaction pool and miner, skip account =
11701174
log.Trace("Skipping account with hight nonce", "sender", from, "nonce", tx.Nonce())
11711175
txs.Pop()
1176+
11721177
case errors.As(err, &errL1):
1173-
// Skip the current transaction failed on L1Sload precompile with L1RpcError without shifting in the next from the account
1178+
// 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
11741179
log.Trace("Skipping transaction failed on L1Sload precompile with L1RpcError", "sender", from)
1180+
atomic.AddInt32(&w.newTxs, int32(1))
11751181
txs.Pop()
1182+
11761183
case errors.Is(err, nil):
11771184
// Everything ok, collect the logs and shift in the next transaction from the same account
11781185
coalescedLogs = append(coalescedLogs, logs...)

miner/worker_test.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package miner
1818

1919
import (
20+
"context"
21+
"errors"
2022
"math"
2123
"math/big"
2224
"math/rand"
@@ -1198,6 +1200,83 @@ func TestPrioritizeOverflowTx(t *testing.T) {
11981200
}
11991201
}
12001202

1203+
type mockL1Client struct {
1204+
failList []bool
1205+
}
1206+
1207+
func (c *mockL1Client) StoragesAt(ctx context.Context, account common.Address, keys []common.Hash, blockNumber *big.Int) ([]byte, error) {
1208+
if len(c.failList) == 0 {
1209+
return common.Hash{}.Bytes(), nil
1210+
}
1211+
failed := c.failList[0]
1212+
c.failList = c.failList[1:]
1213+
if failed {
1214+
return nil, errors.New("error")
1215+
} else {
1216+
return common.Hash{}.Bytes(), nil
1217+
}
1218+
}
1219+
1220+
func TestL1SloadFailedTxReexecuted(t *testing.T) {
1221+
assert := assert.New(t)
1222+
1223+
var (
1224+
chainConfig = params.AllCliqueProtocolChanges
1225+
db = rawdb.NewMemoryDatabase()
1226+
engine = clique.New(chainConfig.Clique, db)
1227+
)
1228+
1229+
chainConfig.Clique = &params.CliqueConfig{Period: 1, Epoch: 30000}
1230+
chainConfig.LondonBlock = big.NewInt(0)
1231+
chainConfig.DescartesBlock = big.NewInt(0)
1232+
1233+
w, b := newTestWorker(t, chainConfig, engine, db, 0)
1234+
// GetStoragesAt will shouldn't fail 2 times on block tracing and should fail then on tx executing
1235+
w.chain.GetVMConfig().L1Client = &mockL1Client{failList: []bool{false, false, true, true, true}}
1236+
defer w.close()
1237+
1238+
// This test chain imports the mined blocks.
1239+
db2 := rawdb.NewMemoryDatabase()
1240+
b.genesis.MustCommit(db2)
1241+
chain, _ := core.NewBlockChain(db2, nil, b.chain.Config(), engine, vm.Config{
1242+
Debug: true,
1243+
Tracer: vm.NewStructLogger(&vm.LogConfig{EnableMemory: true, EnableReturnData: true})}, nil, nil)
1244+
defer chain.Stop()
1245+
chain.GetVMConfig().L1Client = &mockL1Client{}
1246+
1247+
// Ignore empty commit here for less noise.
1248+
w.skipSealHook = func(task *task) bool {
1249+
return len(task.receipts) == 0
1250+
}
1251+
1252+
// Wait for mined blocks.
1253+
sub := w.mux.Subscribe(core.NewMinedBlockEvent{})
1254+
defer sub.Unsubscribe()
1255+
1256+
// Define tx that calls L1Sload
1257+
l1SlaodAddress := common.BytesToAddress([]byte{1, 1})
1258+
input := make([]byte, 52)
1259+
tx, _ := types.SignTx(types.NewTransaction(b.txPool.Nonce(testBankAddress), l1SlaodAddress, big.NewInt(0), 25208, big.NewInt(10*params.InitialBaseFee), input), types.HomesteadSigner{}, testBankKey)
1260+
1261+
// Process 2 transactions with gas order: tx0 > tx1, tx1 will overflow.
1262+
b.txPool.AddRemote(tx)
1263+
// b.txPool.AddLocal(b.newRandomTx(false))
1264+
w.start()
1265+
1266+
select {
1267+
case ev := <-sub.Chan():
1268+
w.stop()
1269+
block := ev.Data.(core.NewMinedBlockEvent).Block
1270+
assert.Equal(1, len(block.Transactions()))
1271+
assert.Equal(tx.Hash(), block.Transactions()[0].Hash())
1272+
if _, err := chain.InsertChain([]*types.Block{block}); err != nil {
1273+
t.Fatalf("failed to insert new mined block %d: %v", block.NumberU64(), err)
1274+
}
1275+
case <-time.After(3 * time.Second): // Worker needs 1s to include new changes.
1276+
t.Fatalf("timeout")
1277+
}
1278+
}
1279+
12011280
func TestSkippedTransactionDatabaseEntries(t *testing.T) {
12021281
assert := assert.New(t)
12031282

0 commit comments

Comments
 (0)