Skip to content

Commit 87b0a27

Browse files
authored
interop: New sequencer executing message check API (#521)
* interop: New sequencer executing message check API * interop: Use struct for rpc timestamp input
1 parent e9fce87 commit 87b0a27

File tree

7 files changed

+63
-40
lines changed

7 files changed

+63
-40
lines changed

core/txpool/ingress_filters.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@ type IngressFilter interface {
1717
}
1818

1919
type interopFilter struct {
20-
logsFn func(tx *types.Transaction) ([]*types.Log, error)
21-
checkFn func(ctx context.Context, ems []interoptypes.Message, safety interoptypes.SafetyLevel) error
20+
logsFn func(tx *types.Transaction) (logs []*types.Log, logTimestamp uint64, err error)
21+
checkFn func(ctx context.Context, ems []interoptypes.Message, safety interoptypes.SafetyLevel, emsTimestamp uint64) error
2222
}
2323

2424
func NewInteropFilter(
25-
logsFn func(tx *types.Transaction) ([]*types.Log, error),
26-
checkFn func(ctx context.Context, ems []interoptypes.Message, safety interoptypes.SafetyLevel) error) IngressFilter {
25+
logsFn func(tx *types.Transaction) ([]*types.Log, uint64, error),
26+
checkFn func(ctx context.Context, ems []interoptypes.Message, safety interoptypes.SafetyLevel, emsTimestamp uint64) error) IngressFilter {
2727
return &interopFilter{
2828
logsFn: logsFn,
2929
checkFn: checkFn,
@@ -33,7 +33,7 @@ func NewInteropFilter(
3333
// FilterTx implements IngressFilter.FilterTx
3434
// it gets logs checks for message safety based on the function provided
3535
func (f *interopFilter) FilterTx(ctx context.Context, tx *types.Transaction) bool {
36-
logs, err := f.logsFn(tx)
36+
logs, logTimestamp, err := f.logsFn(tx)
3737
if err != nil {
3838
log.Debug("Failed to retrieve logs of tx", "txHash", tx.Hash(), "err", err)
3939
return false // default to deny if logs cannot be retrieved
@@ -55,5 +55,5 @@ func (f *interopFilter) FilterTx(ctx context.Context, tx *types.Transaction) boo
5555
// check with the supervisor if the transaction should be allowed given the executing messages
5656
// the message can be unsafe (discovered only via P2P unsafe blocks), but it must be cross-valid
5757
// so CrossUnsafe is used here
58-
return f.checkFn(ctx, ems, interoptypes.CrossUnsafe) == nil
58+
return f.checkFn(ctx, ems, interoptypes.CrossUnsafe, logTimestamp) == nil
5959
}

core/txpool/ingress_filters_test.go

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ func TestInteropFilter(t *testing.T) {
2424
Data: []byte{},
2525
})
2626
t.Run("Tx has no logs", func(t *testing.T) {
27-
logFn := func(tx *types.Transaction) ([]*types.Log, error) {
28-
return []*types.Log{}, nil
27+
logFn := func(tx *types.Transaction) ([]*types.Log, uint64, error) {
28+
return []*types.Log{}, 0, nil
2929
}
30-
checkFn := func(ctx context.Context, ems []interoptypes.Message, safety interoptypes.SafetyLevel) error {
30+
checkFn := func(ctx context.Context, ems []interoptypes.Message, safety interoptypes.SafetyLevel, emsTimestamp uint64) error {
3131
// make this return error, but it won't be called because logs are empty
3232
return errors.New("error")
3333
}
@@ -36,10 +36,10 @@ func TestInteropFilter(t *testing.T) {
3636
require.True(t, filter.FilterTx(context.Background(), tx))
3737
})
3838
t.Run("Tx errored when getting logs", func(t *testing.T) {
39-
logFn := func(tx *types.Transaction) ([]*types.Log, error) {
40-
return []*types.Log{}, errors.New("error")
39+
logFn := func(tx *types.Transaction) ([]*types.Log, uint64, error) {
40+
return []*types.Log{}, 0, errors.New("error")
4141
}
42-
checkFn := func(ctx context.Context, ems []interoptypes.Message, safety interoptypes.SafetyLevel) error {
42+
checkFn := func(ctx context.Context, ems []interoptypes.Message, safety interoptypes.SafetyLevel, emsTimestamp uint64) error {
4343
// make this return error, but it won't be called because logs retrieval errored
4444
return errors.New("error")
4545
}
@@ -48,13 +48,13 @@ func TestInteropFilter(t *testing.T) {
4848
require.False(t, filter.FilterTx(context.Background(), tx))
4949
})
5050
t.Run("Tx has no executing messages", func(t *testing.T) {
51-
logFn := func(tx *types.Transaction) ([]*types.Log, error) {
51+
logFn := func(tx *types.Transaction) ([]*types.Log, uint64, error) {
5252
l1 := &types.Log{
5353
Topics: []common.Hash{common.BytesToHash([]byte("topic1"))},
5454
}
55-
return []*types.Log{l1}, nil
55+
return []*types.Log{l1}, 0, nil
5656
}
57-
checkFn := func(ctx context.Context, ems []interoptypes.Message, safety interoptypes.SafetyLevel) error {
57+
checkFn := func(ctx context.Context, ems []interoptypes.Message, safety interoptypes.SafetyLevel, emsTimestamp uint64) error {
5858
// make this return error, but it won't be called because logs retrieval doesn't have executing messages
5959
return errors.New("error")
6060
}
@@ -78,11 +78,11 @@ func TestInteropFilter(t *testing.T) {
7878
for i := 0; i < 32*5; i++ {
7979
l1.Data = append(l1.Data, 0)
8080
}
81-
logFn := func(tx *types.Transaction) ([]*types.Log, error) {
82-
return []*types.Log{l1}, nil
81+
logFn := func(tx *types.Transaction) ([]*types.Log, uint64, error) {
82+
return []*types.Log{l1}, 0, nil
8383
}
8484
var spyEMs []interoptypes.Message
85-
checkFn := func(ctx context.Context, ems []interoptypes.Message, safety interoptypes.SafetyLevel) error {
85+
checkFn := func(ctx context.Context, ems []interoptypes.Message, safety interoptypes.SafetyLevel, emsTimestamp uint64) error {
8686
spyEMs = ems
8787
return nil
8888
}
@@ -109,11 +109,11 @@ func TestInteropFilter(t *testing.T) {
109109
for i := 0; i < 32*5; i++ {
110110
l1.Data = append(l1.Data, 0)
111111
}
112-
logFn := func(tx *types.Transaction) ([]*types.Log, error) {
113-
return []*types.Log{l1}, nil
112+
logFn := func(tx *types.Transaction) ([]*types.Log, uint64, error) {
113+
return []*types.Log{l1}, 0, nil
114114
}
115115
var spyEMs []interoptypes.Message
116-
checkFn := func(ctx context.Context, ems []interoptypes.Message, safety interoptypes.SafetyLevel) error {
116+
checkFn := func(ctx context.Context, ems []interoptypes.Message, safety interoptypes.SafetyLevel, emsTimestamp uint64) error {
117117
spyEMs = ems
118118
return errors.New("error")
119119
}
@@ -150,7 +150,7 @@ func TestInteropFilterRPCFailures(t *testing.T) {
150150
for _, tt := range tests {
151151
t.Run(tt.name, func(t *testing.T) {
152152
// Create mock log function that always returns our test log
153-
logFn := func(tx *types.Transaction) ([]*types.Log, error) {
153+
logFn := func(tx *types.Transaction) ([]*types.Log, uint64, error) {
154154
log := &types.Log{
155155
Address: params.InteropCrossL2InboxAddress,
156156
Topics: []common.Hash{
@@ -159,11 +159,11 @@ func TestInteropFilterRPCFailures(t *testing.T) {
159159
},
160160
Data: make([]byte, 32*5),
161161
}
162-
return []*types.Log{log}, nil
162+
return []*types.Log{log}, 0, nil
163163
}
164164

165165
// Create mock check function that simulates RPC failures
166-
checkFn := func(ctx context.Context, ems []interoptypes.Message, safety interoptypes.SafetyLevel) error {
166+
checkFn := func(ctx context.Context, ems []interoptypes.Message, safety interoptypes.SafetyLevel, emsTimestamp uint64) error {
167167
if tt.networkErr {
168168
return &net.OpError{Op: "dial", Err: errors.New("connection refused")}
169169
}

core/types/interoptypes/interop.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,3 +167,26 @@ const (
167167
Unsafe SafetyLevel = "unsafe"
168168
Invalid SafetyLevel = "invalid"
169169
)
170+
171+
type ExecutingDescriptor struct {
172+
Timestamp uint64
173+
}
174+
175+
type executingDescriptorMarshaling struct {
176+
Timestamp hexutil.Uint64 `json:"timestamp"`
177+
}
178+
179+
func (ed ExecutingDescriptor) MarshalJSON() ([]byte, error) {
180+
var enc executingDescriptorMarshaling
181+
enc.Timestamp = hexutil.Uint64(ed.Timestamp)
182+
return json.Marshal(&enc)
183+
}
184+
185+
func (ed *ExecutingDescriptor) UnmarshalJSON(input []byte) error {
186+
var dec executingDescriptorMarshaling
187+
if err := json.Unmarshal(input, &dec); err != nil {
188+
return err
189+
}
190+
ed.Timestamp = uint64(dec.Timestamp)
191+
return nil
192+
}

eth/interop.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,45 +12,45 @@ import (
1212
"github.com/ethereum/go-ethereum/internal/ethapi"
1313
)
1414

15-
func (s *Ethereum) CheckMessages(ctx context.Context, messages []interoptypes.Message, minSafety interoptypes.SafetyLevel) error {
15+
func (s *Ethereum) CheckMessages(ctx context.Context, messages []interoptypes.Message, minSafety interoptypes.SafetyLevel, executingTimestamp uint64) error {
1616
if s.interopRPC == nil {
1717
return errors.New("cannot check interop messages, no RPC available")
1818
}
19-
return s.interopRPC.CheckMessages(ctx, messages, minSafety)
19+
return s.interopRPC.CheckMessages(ctx, messages, minSafety, interoptypes.ExecutingDescriptor{Timestamp: executingTimestamp})
2020
}
2121

2222
// SimLogs simulates the logs that would be generated by a transaction if it were executed on the current state.
2323
// This is used by the interop filter to determine if a transaction should be allowed.
2424
// if errors are encountered, no logs are returned.
25-
func (s *Ethereum) SimLogs(tx *types.Transaction) ([]*types.Log, error) {
25+
func (s *Ethereum) SimLogs(tx *types.Transaction) ([]*types.Log, uint64, error) {
2626
chainConfig := s.APIBackend.ChainConfig()
2727
if !chainConfig.IsOptimism() {
28-
return nil, errors.New("expected OP-Stack chain config, SimLogs is an OP-Stack feature")
28+
return nil, 0, errors.New("expected OP-Stack chain config, SimLogs is an OP-Stack feature")
2929
}
3030
header := s.BlockChain().CurrentBlock()
3131
if chainConfig.InteropTime == nil {
32-
return nil, errors.New("expected Interop fork to be configured, SimLogs is unavailable pre-interop")
32+
return nil, 0, errors.New("expected Interop fork to be configured, SimLogs is unavailable pre-interop")
3333
}
3434
state, err := s.BlockChain().StateAt(header.Root)
3535
if err != nil {
36-
return nil, fmt.Errorf("state %s (block %d) is unavailable for log simulation: %w", header.Root, header.Number.Uint64(), err)
36+
return nil, 0, fmt.Errorf("state %s (block %d) is unavailable for log simulation: %w", header.Root, header.Number.Uint64(), err)
3737
}
3838
var vmConf vm.Config
3939
signer := types.MakeSigner(chainConfig, header.Number, header.Time)
4040
message, err := core.TransactionToMessage(tx, signer, header.BaseFee)
4141
if err != nil {
42-
return nil, fmt.Errorf("cannot convert tx to message for log simulation: %w", err)
42+
return nil, 0, fmt.Errorf("cannot convert tx to message for log simulation: %w", err)
4343
}
4444
chainCtx := ethapi.NewChainContext(context.Background(), s.APIBackend)
4545
blockCtx := core.NewEVMBlockContext(header, chainCtx, &header.Coinbase, chainConfig, state)
4646
vmenv := vm.NewEVM(blockCtx, state, chainConfig, vmConf)
4747
state.SetTxContext(tx.Hash(), 0)
4848
result, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(header.GasLimit))
4949
if err != nil {
50-
return nil, fmt.Errorf("failed to execute tx: %w", err)
50+
return nil, 0, fmt.Errorf("failed to execute tx: %w", err)
5151
}
5252
if result.Failed() { // failed txs do not have log events
53-
return nil, nil
53+
return nil, 0, nil
5454
}
55-
return state.GetLogs(tx.Hash(), header.Number.Uint64(), header.Hash()), nil
55+
return state.GetLogs(tx.Hash(), header.Number.Uint64(), header.Hash()), header.Time, nil
5656
}

eth/interop/interop.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,10 @@ func NewInteropClient(rpcEndpoint string) *InteropClient {
4848
}
4949

5050
// CheckMessages checks if the given messages meet the given minimum safety level.
51-
func (cl *InteropClient) CheckMessages(ctx context.Context, messages []interoptypes.Message, minSafety interoptypes.SafetyLevel) error {
51+
func (cl *InteropClient) CheckMessages(ctx context.Context, messages []interoptypes.Message, minSafety interoptypes.SafetyLevel, executingDescriptor interoptypes.ExecutingDescriptor) error {
5252
// we lazy-dial the endpoint, so we can start geth, and build blocks, without supervisor endpoint availability.
5353
if err := cl.maybeDial(ctx); err != nil { // a single dial attempt is made, the next call may retry.
5454
return err
5555
}
56-
return cl.client.CallContext(ctx, nil, "supervisor_checkMessages", messages, minSafety)
56+
return cl.client.CallContext(ctx, nil, "supervisor_checkMessages", messages, minSafety, executingDescriptor)
5757
}

miner/miner.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ type BackendWithHistoricalState interface {
4848
}
4949

5050
type BackendWithInterop interface {
51-
CheckMessages(ctx context.Context, messages []interoptypes.Message, minSafety interoptypes.SafetyLevel) error
51+
CheckMessages(ctx context.Context, messages []interoptypes.Message, minSafety interoptypes.SafetyLevel, executingTimestamp uint64) error
5252
}
5353

5454
// Config is the configuration parameters of mining.

miner/worker.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ func (miner *Miner) applyTransaction(env *environment, tx *types.Transaction) (*
447447
return fmt.Errorf("cannot get logs from StateDB type %T", evm.StateDB)
448448
}
449449
logs := logInspector.GetLogs(tx.Hash(), env.header.Number.Uint64(), common.Hash{})
450-
return miner.checkInterop(env.rpcCtx, tx, result.Failed(), logs)
450+
return miner.checkInterop(env.rpcCtx, tx, result.Failed(), logs, env.header.Time)
451451
},
452452
}
453453
}
@@ -459,7 +459,7 @@ func (miner *Miner) applyTransaction(env *environment, tx *types.Transaction) (*
459459
return receipt, err
460460
}
461461

462-
func (miner *Miner) checkInterop(ctx context.Context, tx *types.Transaction, failed bool, logs []*types.Log) error {
462+
func (miner *Miner) checkInterop(ctx context.Context, tx *types.Transaction, failed bool, logs []*types.Log, logTimestamp uint64) error {
463463
if tx.Type() == types.DepositTxType {
464464
return nil // deposit-txs are always safe
465465
}
@@ -483,7 +483,7 @@ func (miner *Miner) checkInterop(ctx context.Context, tx *types.Transaction, fai
483483
if len(executingMessages) == 0 {
484484
return nil // avoid an RPC check if there are no executing messages to verify.
485485
}
486-
if err := b.CheckMessages(ctx, executingMessages, interoptypes.CrossUnsafe); err != nil {
486+
if err := b.CheckMessages(ctx, executingMessages, interoptypes.CrossUnsafe, logTimestamp); err != nil {
487487
if ctx.Err() != nil { // don't reject transactions permanently on RPC timeouts etc.
488488
log.Debug("CheckMessages timed out", "err", ctx.Err())
489489
return err

0 commit comments

Comments
 (0)