diff --git a/op-supervisor/supervisor/backend/backend.go b/op-supervisor/supervisor/backend/backend.go index 192aa4547f98..9270300e5385 100644 --- a/op-supervisor/supervisor/backend/backend.go +++ b/op-supervisor/supervisor/backend/backend.go @@ -159,6 +159,24 @@ func (su *SupervisorBackend) CheckMessage(identifier types.Identifier, payloadHa return safest, nil } +func (su *SupervisorBackend) CheckMessages( + messages []types.Message, + minSafety types.SafetyLevel) error { + for _, msg := range messages { + safety, err := su.CheckMessage(msg.Identifier, msg.PayloadHash) + if err != nil { + return fmt.Errorf("failed to check message: %w", err) + } + if !safety.AtLeastAsSafe(minSafety) { + return fmt.Errorf("message %v (safety level: %v) does not meet the minimum safety %v", + msg.Identifier, + safety, + minSafety) + } + } + return nil +} + // CheckBlock checks if the block is safe according to the safety level // The block is considered safe if all logs in the block are safe // this is decided by finding the last log in the block and diff --git a/op-supervisor/supervisor/backend/mock.go b/op-supervisor/supervisor/backend/mock.go index 8e5bc95632c2..71c77b235525 100644 --- a/op-supervisor/supervisor/backend/mock.go +++ b/op-supervisor/supervisor/backend/mock.go @@ -43,6 +43,10 @@ func (m *MockBackend) CheckMessage(identifier types.Identifier, payloadHash comm return types.CrossUnsafe, nil } +func (m *MockBackend) CheckMessages(messages []types.Message, minSafety types.SafetyLevel) error { + return nil +} + func (m *MockBackend) CheckBlock(chainID *hexutil.U256, blockHash common.Hash, blockNumber hexutil.Uint64) (types.SafetyLevel, error) { return types.CrossUnsafe, nil } diff --git a/op-supervisor/supervisor/frontend/frontend.go b/op-supervisor/supervisor/frontend/frontend.go index 9804b406914a..421b231e128e 100644 --- a/op-supervisor/supervisor/frontend/frontend.go +++ b/op-supervisor/supervisor/frontend/frontend.go @@ -16,6 +16,7 @@ type AdminBackend interface { type QueryBackend interface { CheckMessage(identifier types.Identifier, payloadHash common.Hash) (types.SafetyLevel, error) + CheckMessages(messages []types.Message, minSafety types.SafetyLevel) error CheckBlock(chainID *hexutil.U256, blockHash common.Hash, blockNumber hexutil.Uint64) (types.SafetyLevel, error) } @@ -34,6 +35,14 @@ func (q *QueryFrontend) CheckMessage(identifier types.Identifier, payloadHash co return q.Supervisor.CheckMessage(identifier, payloadHash) } +// CheckMessage checks the safety-level of a collection of messages, +// and returns if the minimum safety-level is met for all messages. +func (q *QueryFrontend) CheckMessages( + messages []types.Message, + minSafety types.SafetyLevel) error { + return q.Supervisor.CheckMessages(messages, minSafety) +} + // CheckBlock checks the safety-level of an L2 block as a whole. func (q *QueryFrontend) CheckBlock(chainID *hexutil.U256, blockHash common.Hash, blockNumber hexutil.Uint64) (types.SafetyLevel, error) { return q.Supervisor.CheckBlock(chainID, blockHash, blockNumber) diff --git a/op-supervisor/supervisor/types/types.go b/op-supervisor/supervisor/types/types.go index d9da421e2fbb..54b1a8a0c92c 100644 --- a/op-supervisor/supervisor/types/types.go +++ b/op-supervisor/supervisor/types/types.go @@ -13,6 +13,11 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" ) +type Message struct { + Identifier Identifier `json:"identifier"` + PayloadHash common.Hash `json:"payloadHash"` +} + type Identifier struct { Origin common.Address BlockNumber uint64 @@ -83,6 +88,22 @@ func (lvl *SafetyLevel) UnmarshalText(text []byte) error { return nil } +// AtLeastAsSafe returns true if the receiver is at least as safe as the other SafetyLevel. +func (lvl *SafetyLevel) AtLeastAsSafe(min SafetyLevel) bool { + switch min { + case Invalid: + return true + case Unsafe: + return *lvl != Invalid + case Safe: + return *lvl == Safe || *lvl == Finalized + case Finalized: + return *lvl == Finalized + default: + return false + } +} + const ( CrossFinalized SafetyLevel = "cross-finalized" Finalized SafetyLevel = "finalized"