diff --git a/op-e2e/system_fpp_test.go b/op-e2e/system_fpp_test.go index 4da5f75ca51c185843e6681d229ee0a160b3b19d..635276fe159a8271a181eb0224690b173b9454fb 100644 --- a/op-e2e/system_fpp_test.go +++ b/op-e2e/system_fpp_test.go @@ -6,20 +6,22 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum-optimism/optimism/op-chain-ops/genesis" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils/geth" - "github.com/ethereum-optimism/optimism/op-program/client/driver" + "github.com/ethereum-optimism/optimism/op-program/client/claim" opp "github.com/ethereum-optimism/optimism/op-program/host" oppconf "github.com/ethereum-optimism/optimism/op-program/host/config" "github.com/ethereum-optimism/optimism/op-service/client" "github.com/ethereum-optimism/optimism/op-service/sources" "github.com/ethereum-optimism/optimism/op-service/testlog" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rpc" - "github.com/stretchr/testify/require" ) func TestVerifyL2OutputRoot(t *testing.T) { @@ -320,7 +322,7 @@ func testFaultProofProgramScenario(t *testing.T, ctx context.Context, sys *Syste if s.Detached { require.Error(t, err, "exit status 1") } else { - require.ErrorIs(t, err, driver.ErrClaimNotValid) + require.ErrorIs(t, err, claim.ErrClaimNotValid) } } diff --git a/op-program/client/claim/validate.go b/op-program/client/claim/validate.go new file mode 100644 index 0000000000000000000000000000000000000000..05655208c6e78a7b4eef38e7a2b908669bd47a5d --- /dev/null +++ b/op-program/client/claim/validate.go @@ -0,0 +1,34 @@ +package claim + +import ( + "context" + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-service/eth" +) + +var ErrClaimNotValid = errors.New("invalid claim") + +type L2Source interface { + L2BlockRefByLabel(ctx context.Context, label eth.BlockLabel) (eth.L2BlockRef, error) + L2OutputRoot(uint64) (eth.Bytes32, error) +} + +func ValidateClaim(log log.Logger, l2ClaimBlockNum uint64, claimedOutputRoot eth.Bytes32, src L2Source) error { + l2Head, err := src.L2BlockRefByLabel(context.Background(), eth.Safe) + if err != nil { + return fmt.Errorf("cannot retrieve safe head: %w", err) + } + outputRoot, err := src.L2OutputRoot(min(l2ClaimBlockNum, l2Head.Number)) + if err != nil { + return fmt.Errorf("calculate L2 output root: %w", err) + } + log.Info("Validating claim", "head", l2Head, "output", outputRoot, "claim", claimedOutputRoot) + if claimedOutputRoot != outputRoot { + return fmt.Errorf("%w: claim: %v actual: %v", ErrClaimNotValid, claimedOutputRoot, outputRoot) + } + return nil +} diff --git a/op-program/client/claim/validate_test.go b/op-program/client/claim/validate_test.go new file mode 100644 index 0000000000000000000000000000000000000000..c8a9b263eba5299f1ca664152a6a76de804da6ad --- /dev/null +++ b/op-program/client/claim/validate_test.go @@ -0,0 +1,113 @@ +package claim + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/log" + + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/testlog" +) + +type mockL2 struct { + safeL2 eth.L2BlockRef + safeL2Err error + + outputRoot eth.Bytes32 + outputRootErr error + + requestedOutputRoot uint64 +} + +func (m *mockL2) L2BlockRefByLabel(ctx context.Context, label eth.BlockLabel) (eth.L2BlockRef, error) { + if label != eth.Safe { + panic("unexpected usage") + } + if m.safeL2Err != nil { + return eth.L2BlockRef{}, m.safeL2Err + } + return m.safeL2, nil +} + +func (m *mockL2) L2OutputRoot(u uint64) (eth.Bytes32, error) { + m.requestedOutputRoot = u + if m.outputRootErr != nil { + return eth.Bytes32{}, m.outputRootErr + } + return m.outputRoot, nil +} + +var _ L2Source = (*mockL2)(nil) + +func TestValidateClaim(t *testing.T) { + t.Run("Valid", func(t *testing.T) { + expected := eth.Bytes32{0x11} + l2 := &mockL2{ + outputRoot: expected, + } + logger := testlog.Logger(t, log.LevelError) + err := ValidateClaim(logger, uint64(0), expected, l2) + require.NoError(t, err) + }) + + t.Run("Valid-PriorToSafeHead", func(t *testing.T) { + expected := eth.Bytes32{0x11} + l2 := &mockL2{ + outputRoot: expected, + safeL2: eth.L2BlockRef{ + Number: 10, + }, + } + logger := testlog.Logger(t, log.LevelError) + err := ValidateClaim(logger, uint64(20), expected, l2) + require.NoError(t, err) + require.Equal(t, uint64(10), l2.requestedOutputRoot) + }) + + t.Run("Invalid", func(t *testing.T) { + l2 := &mockL2{ + outputRoot: eth.Bytes32{0x22}, + } + logger := testlog.Logger(t, log.LevelError) + err := ValidateClaim(logger, uint64(0), eth.Bytes32{0x11}, l2) + require.ErrorIs(t, err, ErrClaimNotValid) + }) + + t.Run("Invalid-PriorToSafeHead", func(t *testing.T) { + l2 := &mockL2{ + outputRoot: eth.Bytes32{0x22}, + safeL2: eth.L2BlockRef{Number: 10}, + } + logger := testlog.Logger(t, log.LevelError) + err := ValidateClaim(logger, uint64(20), eth.Bytes32{0x55}, l2) + require.ErrorIs(t, err, ErrClaimNotValid) + require.Equal(t, uint64(10), l2.requestedOutputRoot) + }) + + t.Run("Error-safe-head", func(t *testing.T) { + expectedErr := errors.New("boom") + l2 := &mockL2{ + outputRoot: eth.Bytes32{0x11}, + safeL2: eth.L2BlockRef{Number: 10}, + safeL2Err: expectedErr, + } + logger := testlog.Logger(t, log.LevelError) + err := ValidateClaim(logger, uint64(0), eth.Bytes32{0x11}, l2) + require.ErrorIs(t, err, expectedErr) + }) + t.Run("Error-output-root", func(t *testing.T) { + expectedErr := errors.New("boom") + l2 := &mockL2{ + outputRoot: eth.Bytes32{0x11}, + outputRootErr: expectedErr, + safeL2: eth.L2BlockRef{Number: 10}, + } + logger := testlog.Logger(t, log.LevelError) + err := ValidateClaim(logger, uint64(0), eth.Bytes32{0x11}, l2) + require.ErrorIs(t, err, expectedErr) + }) +} diff --git a/op-program/client/driver/driver.go b/op-program/client/driver/driver.go index 7d2235c3a45e071d715e802767cbf25167ce8e69..bbeeb99bba29b661d4133cc9082f8ed479df24bf 100644 --- a/op-program/client/driver/driver.go +++ b/op-program/client/driver/driver.go @@ -19,8 +19,6 @@ import ( "github.com/ethereum-optimism/optimism/op-service/eth" ) -var ErrClaimNotValid = errors.New("invalid claim") - type Derivation interface { Step(ctx context.Context) error } @@ -154,16 +152,3 @@ func (d *Driver) Step(ctx context.Context) error { func (d *Driver) SafeHead() eth.L2BlockRef { return d.deriver.SafeL2Head() } - -func (d *Driver) ValidateClaim(l2ClaimBlockNum uint64, claimedOutputRoot eth.Bytes32) error { - l2Head := d.SafeHead() - outputRoot, err := d.l2OutputRoot(min(l2ClaimBlockNum, l2Head.Number)) - if err != nil { - return fmt.Errorf("calculate L2 output root: %w", err) - } - d.logger.Info("Validating claim", "head", l2Head, "output", outputRoot, "claim", claimedOutputRoot) - if claimedOutputRoot != outputRoot { - return fmt.Errorf("%w: claim: %v actual: %v", ErrClaimNotValid, claimedOutputRoot, outputRoot) - } - return nil -} diff --git a/op-program/client/driver/driver_test.go b/op-program/client/driver/driver_test.go index d61484020289c23cf0cce83a9602e18c3a28e9cb..23516696bc8df76b33c8495b66b519e7447199d8 100644 --- a/op-program/client/driver/driver_test.go +++ b/op-program/client/driver/driver_test.go @@ -69,63 +69,6 @@ func TestNoError(t *testing.T) { require.NoError(t, err) } -func TestValidateClaim(t *testing.T) { - t.Run("Valid", func(t *testing.T) { - driver := createDriver(t, io.EOF) - expected := eth.Bytes32{0x11} - driver.l2OutputRoot = func(_ uint64) (eth.Bytes32, error) { - return expected, nil - } - err := driver.ValidateClaim(uint64(0), expected) - require.NoError(t, err) - }) - - t.Run("Valid-PriorToSafeHead", func(t *testing.T) { - driver := createDriverWithNextBlock(t, io.EOF, 10) - expected := eth.Bytes32{0x11} - requestedOutputRoot := uint64(0) - driver.l2OutputRoot = func(blockNum uint64) (eth.Bytes32, error) { - requestedOutputRoot = blockNum - return expected, nil - } - err := driver.ValidateClaim(uint64(20), expected) - require.NoError(t, err) - require.Equal(t, uint64(10), requestedOutputRoot) - }) - - t.Run("Invalid", func(t *testing.T) { - driver := createDriver(t, io.EOF) - driver.l2OutputRoot = func(_ uint64) (eth.Bytes32, error) { - return eth.Bytes32{0x22}, nil - } - err := driver.ValidateClaim(uint64(0), eth.Bytes32{0x11}) - require.ErrorIs(t, err, ErrClaimNotValid) - }) - - t.Run("Invalid-PriorToSafeHead", func(t *testing.T) { - driver := createDriverWithNextBlock(t, io.EOF, 10) - expected := eth.Bytes32{0x11} - requestedOutputRoot := uint64(0) - driver.l2OutputRoot = func(blockNum uint64) (eth.Bytes32, error) { - requestedOutputRoot = blockNum - return expected, nil - } - err := driver.ValidateClaim(uint64(20), eth.Bytes32{0x55}) - require.ErrorIs(t, err, ErrClaimNotValid) - require.Equal(t, uint64(10), requestedOutputRoot) - }) - - t.Run("Error", func(t *testing.T) { - driver := createDriver(t, io.EOF) - expectedErr := errors.New("boom") - driver.l2OutputRoot = func(_ uint64) (eth.Bytes32, error) { - return eth.Bytes32{}, expectedErr - } - err := driver.ValidateClaim(uint64(0), eth.Bytes32{0x11}) - require.ErrorIs(t, err, expectedErr) - }) -} - func createDriver(t *testing.T, derivationResult error) *Driver { return createDriverWithNextBlock(t, derivationResult, 0) } diff --git a/op-program/client/program.go b/op-program/client/program.go index a471941f2005399ad35c142ebe87777d1798be6d..d9c3531c46529b3a84271528e998cef51ed52399 100644 --- a/op-program/client/program.go +++ b/op-program/client/program.go @@ -13,6 +13,7 @@ import ( "github.com/ethereum-optimism/optimism/op-node/rollup" preimage "github.com/ethereum-optimism/optimism/op-preimage" + "github.com/ethereum-optimism/optimism/op-program/client/claim" cldr "github.com/ethereum-optimism/optimism/op-program/client/driver" "github.com/ethereum-optimism/optimism/op-program/client/l1" "github.com/ethereum-optimism/optimism/op-program/client/l2" @@ -26,7 +27,7 @@ func Main(logger log.Logger) { log.Info("Starting fault proof program client") preimageOracle := CreatePreimageChannel() preimageHinter := CreateHinterChannel() - if err := RunProgram(logger, preimageOracle, preimageHinter); errors.Is(err, cldr.ErrClaimNotValid) { + if err := RunProgram(logger, preimageOracle, preimageHinter); errors.Is(err, claim.ErrClaimNotValid) { log.Error("Claim is invalid", "err", err) os.Exit(1) } else if err != nil { @@ -79,7 +80,7 @@ func runDerivation(logger log.Logger, cfg *rollup.Config, l2Cfg *params.ChainCon return err } } - return d.ValidateClaim(l2ClaimBlockNum, eth.Bytes32(l2Claim)) + return claim.ValidateClaim(logger, l2ClaimBlockNum, eth.Bytes32(l2Claim), l2Source) } func CreateHinterChannel() oppio.FileChannel {