Repository: ethereum-optimism/optimism (Go)

Issue:
op-program: Incorrect error type in initiating message block not found leads to invalid fault proof
This was discovered during a spearbit audit. Where an untyped error propagated during hazard checks does not result in block replacement.

There are two areas where this occurs:
- when the log index of the message being executed doesn't exist - https://github.com/ethereum-optimism/optimism/blob/78e668925784a68395d82bdd09a7028618c759d6/op-program/client/interop/consolidate.go#L300C2-L300C4
- when the referenced block number of the initiating message isn't in the canonical oracle - https://github.com/ethereum-optimism/optimism/blob/78e668925784a68395d82bdd09a7028618c759d6/op-program/client/interop/consolidate.go#L356

In both cases, the error should be typed as `ErrConflict` to trigger block replacement.




Discussion:

Patch:
diff --git a/op-program/client/interop/consolidate.go b/op-program/client/interop/consolidate.go
index ee67da1187390..b9958f3908041 100644
--- a/op-program/client/interop/consolidate.go
+++ b/op-program/client/interop/consolidate.go
@@ -13,7 +13,6 @@ import (
 	"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset"
 	"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/processors"
 	supervisortypes "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
-	"github.com/ethereum/go-ethereum"
 	"github.com/ethereum/go-ethereum/common"
 	"github.com/ethereum/go-ethereum/core/rawdb"
 	ethtypes "github.com/ethereum/go-ethereum/core/types"
@@ -298,8 +297,7 @@ func (d *consolidateCheckDeps) Contains(chain eth.ChainID, query supervisortypes
 		}
 		current += uint32(len(receipt.Logs))
 	}
-	//nolint:err113 // TODO(#14988): Handle non-existent logs
-	return supervisortypes.BlockSeal{}, fmt.Errorf("log not found")
+	return supervisortypes.BlockSeal{}, fmt.Errorf("log not found: %w", supervisortypes.ErrConflict)
 }
 
 func logToLogHash(l *ethtypes.Log) common.Hash {
@@ -355,7 +353,7 @@ func (d *consolidateCheckDeps) DependencySet() depset.DependencySet {
 func (d *consolidateCheckDeps) CanonBlockByNumber(oracle l2.Oracle, blockNum uint64, chainID eth.ChainID) (*ethtypes.Block, error) {
 	head := d.canonBlocks[chainID].GetHeaderByNumber(blockNum)
 	if head == nil {
-		return nil, fmt.Errorf("head not found for chain %v: %w", chainID, ethereum.NotFound)
+		return nil, fmt.Errorf("head not found for chain %v: %w", chainID, supervisortypes.ErrConflict)
 	}
 	return d.oracle.BlockByHash(head.Hash(), chainID), nil
 }


Test patch:
diff --git a/op-program/client/interop/interop_test.go b/op-program/client/interop/interop_test.go
index 89b0a38176e5e..d52ea1857794e 100644
--- a/op-program/client/interop/interop_test.go
+++ b/op-program/client/interop/interop_test.go
@@ -340,13 +340,58 @@ func TestDeriveBlockForConsolidateStep(t *testing.T) {
 				},
 			},
 		},
+		{
+			name: "ReplaceChainB-BlockNumberTooBig",
+			testCase: consolidationTestCase{
+				logBuilderFn: func(includeBlockNumbers map[supervisortypes.ChainIndex]uint64, config *staticConfigSource) map[supervisortypes.ChainIndex][]*gethTypes.Log {
+					init1 := &gethTypes.Log{
+						Address: initiatingMessageOrigin,
+						Topics:  []common.Hash{initiatingMessageTopic},
+					}
+					init2 := &gethTypes.Log{
+						Address: initiatingMessageOrigin2,
+						Topics:  []common.Hash{initiatingMessageTopic},
+					}
+					exec := createExecMessage(1_000_000, config, chainA)
+					return map[supervisortypes.ChainIndex][]*gethTypes.Log{
+						chainA: {init1, init2},
+						chainB: {convertExecutingMessageToLog(t, exec)},
+					}
+				},
+				expectBlockReplacements: func(config *staticConfigSource) []supervisortypes.ChainIndex {
+					return []supervisortypes.ChainIndex{chainB}
+				},
+			},
+		},
+		{
+			name: "ReplaceChainB-LogIndexTooBig",
+			testCase: consolidationTestCase{
+				logBuilderFn: func(includeBlockNumbers map[supervisortypes.ChainIndex]uint64, config *staticConfigSource) map[supervisortypes.ChainIndex][]*gethTypes.Log {
+					init1 := &gethTypes.Log{
+						Address: initiatingMessageOrigin,
+						Topics:  []common.Hash{initiatingMessageTopic},
+					}
+					init2 := &gethTypes.Log{
+						Address: initiatingMessageOrigin2,
+						Topics:  []common.Hash{initiatingMessageTopic},
+					}
+					exec := createExecMessage(includeBlockNumbers[chainA], config, chainA)
+					exec.Identifier.Origin = init2.Address
+					exec.Identifier.LogIndex = 1_000_000
+					return map[supervisortypes.ChainIndex][]*gethTypes.Log{
+						chainA: {init1, init2},
+						chainB: {convertExecutingMessageToLog(t, exec)},
+					}
+				},
+				expectBlockReplacements: func(config *staticConfigSource) []supervisortypes.ChainIndex {
+					return []supervisortypes.ChainIndex{chainB}
+				},
+			},
+		},
 	}
 
 	for _, tt := range cases {
 		t.Run(tt.name, func(t *testing.T) {
-			if tt.name != "ReplaceBothChains-CascadingReorg" {
-				t.Skip()
-			}
 			runConsolidationTestCase(t, tt.testCase)
 		})
 	}