Commit 9881eda4 authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

op-program: Support agreed prestate hint in host (#13703)

* op-program: Support agreed prestate hint in host

* op-program: Fix transition state serialization in test

* op-program: Fix prefetcher_test.go

* op-program: Prevent specifying both --l2.outputroot and --l2.agreed-prestate flags

Check config is consistent

* op-program: Return safe or local safe head from derivation

Avoids relying on the safe label which won't be updated on interop chains.

* op-program: Only stop derivation if local safe reaches the target block.

* op-program: Fix safe head trace extension

* op-program: Remove unused field.
parent e86cc35a
...@@ -9,7 +9,7 @@ import ( ...@@ -9,7 +9,7 @@ import (
"github.com/ethereum-optimism/optimism/op-program/client/claim" "github.com/ethereum-optimism/optimism/op-program/client/claim"
"github.com/ethereum-optimism/optimism/op-program/client/interop/types" "github.com/ethereum-optimism/optimism/op-program/client/interop/types"
"github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/op-e2e/actions/helpers" "github.com/ethereum-optimism/optimism/op-e2e/actions/helpers"
...@@ -239,8 +239,8 @@ func TestInteropFaultProofs(gt *testing.T) { ...@@ -239,8 +239,8 @@ func TestInteropFaultProofs(gt *testing.T) {
chainBClient := actors.ChainB.Sequencer.RollupClient() chainBClient := actors.ChainB.Sequencer.RollupClient()
ctx := context.Background() ctx := context.Background()
startTimestamp := actors.ChainA.RollupCfg.Genesis.L2Time endTimestamp := actors.ChainA.RollupCfg.Genesis.L2Time + actors.ChainA.RollupCfg.BlockTime
endTimestamp := startTimestamp + actors.ChainA.RollupCfg.BlockTime startTimestamp := endTimestamp - 1
source, err := NewSuperRootSource(ctx, chainAClient, chainBClient) source, err := NewSuperRootSource(ctx, chainAClient, chainBClient)
require.NoError(t, err) require.NoError(t, err)
start, err := source.CreateSuperRoot(ctx, startTimestamp) start, err := source.CreateSuperRoot(ctx, startTimestamp)
...@@ -249,19 +249,19 @@ func TestInteropFaultProofs(gt *testing.T) { ...@@ -249,19 +249,19 @@ func TestInteropFaultProofs(gt *testing.T) {
require.NoError(t, err) require.NoError(t, err)
serializeIntermediateRoot := func(root *types.TransitionState) []byte { serializeIntermediateRoot := func(root *types.TransitionState) []byte {
data, err := rlp.EncodeToBytes(root) data, err := root.Marshal()
require.NoError(t, err) require.NoError(t, err)
return data return data
} }
num, err := actors.ChainA.RollupCfg.TargetBlockNumber(endTimestamp) endBlockNumA, err := actors.ChainA.RollupCfg.TargetBlockNumber(endTimestamp)
require.NoError(t, err) require.NoError(t, err)
chain1End, err := chainAClient.OutputAtBlock(ctx, num) chain1End, err := chainAClient.OutputAtBlock(ctx, endBlockNumA)
require.NoError(t, err) require.NoError(t, err)
num, err = actors.ChainB.RollupCfg.TargetBlockNumber(endTimestamp) endBlockNumB, err := actors.ChainB.RollupCfg.TargetBlockNumber(endTimestamp)
require.NoError(t, err) require.NoError(t, err)
chain2End, err := chainBClient.OutputAtBlock(ctx, num) chain2End, err := chainBClient.OutputAtBlock(ctx, endBlockNumB)
require.NoError(t, err) require.NoError(t, err)
step1Expected := serializeIntermediateRoot(&types.TransitionState{ step1Expected := serializeIntermediateRoot(&types.TransitionState{
...@@ -297,7 +297,6 @@ func TestInteropFaultProofs(gt *testing.T) { ...@@ -297,7 +297,6 @@ func TestInteropFaultProofs(gt *testing.T) {
agreedClaim: start.Marshal(), agreedClaim: start.Marshal(),
disputedClaim: start.Marshal(), disputedClaim: start.Marshal(),
expectValid: false, expectValid: false,
skip: true,
}, },
{ {
name: "ClaimDirectToNextTimestamp", name: "ClaimDirectToNextTimestamp",
...@@ -305,7 +304,6 @@ func TestInteropFaultProofs(gt *testing.T) { ...@@ -305,7 +304,6 @@ func TestInteropFaultProofs(gt *testing.T) {
agreedClaim: start.Marshal(), agreedClaim: start.Marshal(),
disputedClaim: end.Marshal(), disputedClaim: end.Marshal(),
expectValid: false, expectValid: false,
skip: true,
}, },
{ {
name: "FirstChainOptimisticBlock", name: "FirstChainOptimisticBlock",
...@@ -313,7 +311,6 @@ func TestInteropFaultProofs(gt *testing.T) { ...@@ -313,7 +311,6 @@ func TestInteropFaultProofs(gt *testing.T) {
agreedClaim: start.Marshal(), agreedClaim: start.Marshal(),
disputedClaim: step1Expected, disputedClaim: step1Expected,
expectValid: true, expectValid: true,
skip: true,
}, },
{ {
name: "SecondChainOptimisticBlock", name: "SecondChainOptimisticBlock",
...@@ -364,6 +361,9 @@ func TestInteropFaultProofs(gt *testing.T) { ...@@ -364,6 +361,9 @@ func TestInteropFaultProofs(gt *testing.T) {
chain1End.BlockRef.Number, chain1End.BlockRef.Number,
checkResult, checkResult,
fpHelpers.WithInteropEnabled(), fpHelpers.WithInteropEnabled(),
fpHelpers.WithAgreedPrestate(test.agreedClaim),
fpHelpers.WithL2Claim(crypto.Keccak256Hash(test.disputedClaim)),
fpHelpers.WithL2BlockNumber(endBlockNumA),
) )
}) })
} }
......
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis" "github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
e2ecfg "github.com/ethereum-optimism/optimism/op-e2e/config" e2ecfg "github.com/ethereum-optimism/optimism/op-e2e/config"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
altda "github.com/ethereum-optimism/optimism/op-alt-da" altda "github.com/ethereum-optimism/optimism/op-alt-da"
...@@ -151,6 +152,13 @@ func WithL2Claim(claim common.Hash) FixtureInputParam { ...@@ -151,6 +152,13 @@ func WithL2Claim(claim common.Hash) FixtureInputParam {
} }
} }
func WithAgreedPrestate(prestate []byte) FixtureInputParam {
return func(f *FixtureInputs) {
f.AgreedPrestate = prestate
f.L2OutputRoot = crypto.Keccak256Hash(prestate)
}
}
func WithL2BlockNumber(num uint64) FixtureInputParam { func WithL2BlockNumber(num uint64) FixtureInputParam {
return func(f *FixtureInputs) { return func(f *FixtureInputs) {
f.L2BlockNumber = num f.L2BlockNumber = num
...@@ -213,6 +221,9 @@ func NewOpProgramCfg( ...@@ -213,6 +221,9 @@ func NewOpProgramCfg(
dfault.DataDir = t.TempDir() dfault.DataDir = t.TempDir()
dfault.DataFormat = hostTypes.DataFormatPebble dfault.DataFormat = hostTypes.DataFormatPebble
} }
if fi.InteropEnabled {
dfault.AgreedPrestate = fi.AgreedPrestate
}
dfault.InteropEnabled = fi.InteropEnabled dfault.InteropEnabled = fi.InteropEnabled
return dfault return dfault
} }
...@@ -44,6 +44,7 @@ type FixtureInputs struct { ...@@ -44,6 +44,7 @@ type FixtureInputs struct {
L2OutputRoot common.Hash `toml:"l2-output-root"` L2OutputRoot common.Hash `toml:"l2-output-root"`
L2ChainID uint64 `toml:"l2-chain-id"` L2ChainID uint64 `toml:"l2-chain-id"`
L1Head common.Hash `toml:"l1-head"` L1Head common.Hash `toml:"l1-head"`
AgreedPrestate []byte `toml:"agreed-prestate"`
InteropEnabled bool `toml:"use-interop"` InteropEnabled bool `toml:"use-interop"`
} }
......
...@@ -83,7 +83,7 @@ func RunFaultProofProgram(t helpers.Testing, logger log.Logger, l1 *helpers.L1Mi ...@@ -83,7 +83,7 @@ func RunFaultProofProgram(t helpers.Testing, logger log.Logger, l1 *helpers.L1Mi
l2DebugCl := hostcommon.NewL2SourceWithClient(logger, l2Client, sources.NewDebugClient(l2RPC.CallContext)) l2DebugCl := hostcommon.NewL2SourceWithClient(logger, l2Client, sources.NewDebugClient(l2RPC.CallContext))
executor := host.MakeProgramExecutor(logger, programCfg) executor := host.MakeProgramExecutor(logger, programCfg)
return prefetcher.NewPrefetcher(logger, l1Cl, l1BlobFetcher, l2DebugCl, kv, l2ChainConfig.Config, executor), nil return prefetcher.NewPrefetcher(logger, l1Cl, l1BlobFetcher, l2DebugCl, kv, executor, cfg.AgreedPrestate), nil
}) })
err = hostcommon.FaultProofProgram(t.Ctx(), logger, programCfg, withInProcessPrefetcher) err = hostcommon.FaultProofProgram(t.Ctx(), logger, programCfg, withInProcessPrefetcher)
checkResult(t, err) checkResult(t, err)
......
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"context" "context"
"errors" "errors"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
altda "github.com/ethereum-optimism/optimism/op-alt-da" altda "github.com/ethereum-optimism/optimism/op-alt-da"
...@@ -17,7 +18,7 @@ import ( ...@@ -17,7 +18,7 @@ import (
type EndCondition interface { type EndCondition interface {
Closing() bool Closing() bool
Result() error Result() (eth.L2BlockRef, error)
} }
type Driver struct { type Driver struct {
...@@ -51,7 +52,7 @@ func NewDriver(logger log.Logger, cfg *rollup.Config, l1Source derive.L1Fetcher, ...@@ -51,7 +52,7 @@ func NewDriver(logger log.Logger, cfg *rollup.Config, l1Source derive.L1Fetcher,
logger: logger, logger: logger,
Emitter: d, Emitter: d,
closing: false, closing: false,
result: nil, result: eth.L2BlockRef{},
targetBlockNum: targetBlockNum, targetBlockNum: targetBlockNum,
} }
...@@ -73,7 +74,7 @@ func (d *Driver) Emit(ev event.Event) { ...@@ -73,7 +74,7 @@ func (d *Driver) Emit(ev event.Event) {
d.events = append(d.events, ev) d.events = append(d.events, ev)
} }
func (d *Driver) RunComplete() error { func (d *Driver) RunComplete() (eth.L2BlockRef, error) {
// Initial reset // Initial reset
d.Emit(engine.ResetEngineRequestEvent{}) d.Emit(engine.ResetEngineRequestEvent{})
...@@ -83,7 +84,7 @@ func (d *Driver) RunComplete() error { ...@@ -83,7 +84,7 @@ func (d *Driver) RunComplete() error {
return d.end.Result() return d.end.Result()
} }
if len(d.events) > 10000 { // sanity check, in case of bugs. Better than going OOM. if len(d.events) > 10000 { // sanity check, in case of bugs. Better than going OOM.
return errors.New("way too many events queued up, something is wrong") return eth.L2BlockRef{}, errors.New("way too many events queued up, something is wrong")
} }
ev := d.events[0] ev := d.events[0]
d.events = d.events[1:] d.events = d.events[1:]
......
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"errors" "errors"
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
...@@ -21,8 +22,8 @@ func (d *fakeEnd) Closing() bool { ...@@ -21,8 +22,8 @@ func (d *fakeEnd) Closing() bool {
return d.closing return d.closing
} }
func (d *fakeEnd) Result() error { func (d *fakeEnd) Result() (eth.L2BlockRef, error) {
return d.result return eth.L2BlockRef{}, d.result
} }
func TestDriver(t *testing.T) { func TestDriver(t *testing.T) {
...@@ -44,7 +45,8 @@ func TestDriver(t *testing.T) { ...@@ -44,7 +45,8 @@ func TestDriver(t *testing.T) {
d := newTestDriver(t, func(d *Driver, end *fakeEnd, ev event.Event) { d := newTestDriver(t, func(d *Driver, end *fakeEnd, ev event.Event) {
end.closing = true end.closing = true
}) })
require.NoError(t, d.RunComplete()) _, err := d.RunComplete()
require.NoError(t, err)
}) })
t.Run("insta error", func(t *testing.T) { t.Run("insta error", func(t *testing.T) {
...@@ -53,7 +55,8 @@ func TestDriver(t *testing.T) { ...@@ -53,7 +55,8 @@ func TestDriver(t *testing.T) {
end.closing = true end.closing = true
end.result = mockErr end.result = mockErr
}) })
require.ErrorIs(t, mockErr, d.RunComplete()) _, err := d.RunComplete()
require.ErrorIs(t, mockErr, err)
}) })
t.Run("success after a few events", func(t *testing.T) { t.Run("success after a few events", func(t *testing.T) {
...@@ -66,7 +69,8 @@ func TestDriver(t *testing.T) { ...@@ -66,7 +69,8 @@ func TestDriver(t *testing.T) {
count += 1 count += 1
d.Emit(TestEvent{}) d.Emit(TestEvent{})
}) })
require.NoError(t, d.RunComplete()) _, err := d.RunComplete()
require.NoError(t, err)
}) })
t.Run("error after a few events", func(t *testing.T) { t.Run("error after a few events", func(t *testing.T) {
...@@ -81,7 +85,8 @@ func TestDriver(t *testing.T) { ...@@ -81,7 +85,8 @@ func TestDriver(t *testing.T) {
count += 1 count += 1
d.Emit(TestEvent{}) d.Emit(TestEvent{})
}) })
require.ErrorIs(t, mockErr, d.RunComplete()) _, err := d.RunComplete()
require.ErrorIs(t, mockErr, err)
}) })
t.Run("exhaust events", func(t *testing.T) { t.Run("exhaust events", func(t *testing.T) {
...@@ -93,7 +98,8 @@ func TestDriver(t *testing.T) { ...@@ -93,7 +98,8 @@ func TestDriver(t *testing.T) {
count += 1 count += 1
}) })
// No further processing to be done so evaluate if the claims output root is correct. // No further processing to be done so evaluate if the claims output root is correct.
require.NoError(t, d.RunComplete()) _, err := d.RunComplete()
require.NoError(t, err)
}) })
t.Run("queued events", func(t *testing.T) { t.Run("queued events", func(t *testing.T) {
...@@ -105,7 +111,8 @@ func TestDriver(t *testing.T) { ...@@ -105,7 +111,8 @@ func TestDriver(t *testing.T) {
} }
count += 1 count += 1
}) })
require.NoError(t, d.RunComplete()) _, err := d.RunComplete()
require.NoError(t, err)
// add 1 for initial event that RunComplete fires // add 1 for initial event that RunComplete fires
require.Equal(t, 1+3*2, count, "must have queued up 2 events 3 times") require.Equal(t, 1+3*2, count, "must have queued up 2 events 3 times")
}) })
......
...@@ -3,6 +3,7 @@ package driver ...@@ -3,6 +3,7 @@ package driver
import ( import (
"fmt" "fmt"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
...@@ -21,7 +22,8 @@ type ProgramDeriver struct { ...@@ -21,7 +22,8 @@ type ProgramDeriver struct {
Emitter event.Emitter Emitter event.Emitter
closing bool closing bool
result error result eth.L2BlockRef
resultError error
targetBlockNum uint64 targetBlockNum uint64
} }
...@@ -29,8 +31,8 @@ func (d *ProgramDeriver) Closing() bool { ...@@ -29,8 +31,8 @@ func (d *ProgramDeriver) Closing() bool {
return d.closing return d.closing
} }
func (d *ProgramDeriver) Result() error { func (d *ProgramDeriver) Result() (eth.L2BlockRef, error) {
return d.result return d.result, d.resultError
} }
func (d *ProgramDeriver) OnEvent(ev event.Event) bool { func (d *ProgramDeriver) OnEvent(ev event.Event) bool {
...@@ -58,20 +60,35 @@ func (d *ProgramDeriver) OnEvent(ev event.Event) bool { ...@@ -58,20 +60,35 @@ func (d *ProgramDeriver) OnEvent(ev event.Event) bool {
// and continue with the next. // and continue with the next.
d.Emitter.Emit(engine.PendingSafeRequestEvent{}) d.Emitter.Emit(engine.PendingSafeRequestEvent{})
case engine.ForkchoiceUpdateEvent: case engine.ForkchoiceUpdateEvent:
// Track latest head.
if x.SafeL2Head.Number >= d.result.Number {
d.result = x.SafeL2Head
}
// Stop if we have reached the target block
if x.SafeL2Head.Number >= d.targetBlockNum { if x.SafeL2Head.Number >= d.targetBlockNum {
d.logger.Info("Derivation complete: reached L2 block", "head", x.SafeL2Head) d.logger.Info("Derivation complete: reached L2 block as safe", "head", x.SafeL2Head)
d.closing = true
}
case engine.LocalSafeUpdateEvent:
// Track latest head.
if x.Ref.Number >= d.result.Number {
d.result = x.Ref
}
// Stop if we have reached the target block
if x.Ref.Number >= d.targetBlockNum {
d.logger.Info("Derivation complete: reached L2 block as local safe", "head", x.Ref)
d.closing = true d.closing = true
} }
case derive.DeriverIdleEvent: case derive.DeriverIdleEvent:
// We dont't close the deriver yet, as the engine may still be processing events to reach // We don't close the deriver yet, as the engine may still be processing events to reach
// the target. A ForkchoiceUpdateEvent will close the deriver when the target is reached. // the target. A ForkchoiceUpdateEvent will close the deriver when the target is reached.
d.logger.Info("Derivation complete: no further L1 data to process") d.logger.Info("Derivation complete: no further L1 data to process")
case rollup.ResetEvent: case rollup.ResetEvent:
d.closing = true d.closing = true
d.result = fmt.Errorf("unexpected reset error: %w", x.Err) d.resultError = fmt.Errorf("unexpected reset error: %w", x.Err)
case rollup.L1TemporaryErrorEvent: case rollup.L1TemporaryErrorEvent:
d.closing = true d.closing = true
d.result = fmt.Errorf("unexpected L1 error: %w", x.Err) d.resultError = fmt.Errorf("unexpected L1 error: %w", x.Err)
case rollup.EngineTemporaryErrorEvent: case rollup.EngineTemporaryErrorEvent:
// (Legacy case): While most temporary errors are due to requests for external data failing which can't happen, // (Legacy case): While most temporary errors are due to requests for external data failing which can't happen,
// they may also be returned due to other events like channels timing out so need to be handled // they may also be returned due to other events like channels timing out so need to be handled
...@@ -79,7 +96,7 @@ func (d *ProgramDeriver) OnEvent(ev event.Event) bool { ...@@ -79,7 +96,7 @@ func (d *ProgramDeriver) OnEvent(ev event.Event) bool {
d.Emitter.Emit(engine.PendingSafeRequestEvent{}) d.Emitter.Emit(engine.PendingSafeRequestEvent{})
case rollup.CriticalErrorEvent: case rollup.CriticalErrorEvent:
d.closing = true d.closing = true
d.result = x.Err d.resultError = x.Err
default: default:
// Other events can be ignored safely. // Other events can be ignored safely.
// They are broadcast, but only consumed by the other derivers, // They are broadcast, but only consumed by the other derivers,
......
...@@ -36,9 +36,9 @@ func TestProgramDeriver(t *testing.T) { ...@@ -36,9 +36,9 @@ func TestProgramDeriver(t *testing.T) {
p.OnEvent(engine.EngineResetConfirmedEvent{}) p.OnEvent(engine.EngineResetConfirmedEvent{})
m.AssertExpectations(t) m.AssertExpectations(t)
require.False(t, p.closing) require.False(t, p.closing)
require.NoError(t, p.result) require.NoError(t, p.resultError)
require.False(t, p.closing) require.False(t, p.closing)
require.NoError(t, p.result) require.NoError(t, p.resultError)
}) })
// step 2: more derivation work, triggered when pending safe data is published // step 2: more derivation work, triggered when pending safe data is published
t.Run("pending safe update", func(t *testing.T) { t.Run("pending safe update", func(t *testing.T) {
...@@ -48,7 +48,7 @@ func TestProgramDeriver(t *testing.T) { ...@@ -48,7 +48,7 @@ func TestProgramDeriver(t *testing.T) {
p.OnEvent(engine.PendingSafeUpdateEvent{PendingSafe: ref}) p.OnEvent(engine.PendingSafeUpdateEvent{PendingSafe: ref})
m.AssertExpectations(t) m.AssertExpectations(t)
require.False(t, p.closing) require.False(t, p.closing)
require.NoError(t, p.result) require.NoError(t, p.resultError)
}) })
// step 3: if no attributes are generated, loop back to derive more. // step 3: if no attributes are generated, loop back to derive more.
t.Run("deriver more", func(t *testing.T) { t.Run("deriver more", func(t *testing.T) {
...@@ -57,7 +57,7 @@ func TestProgramDeriver(t *testing.T) { ...@@ -57,7 +57,7 @@ func TestProgramDeriver(t *testing.T) {
p.OnEvent(derive.DeriverMoreEvent{}) p.OnEvent(derive.DeriverMoreEvent{})
m.AssertExpectations(t) m.AssertExpectations(t)
require.False(t, p.closing) require.False(t, p.closing)
require.NoError(t, p.result) require.NoError(t, p.resultError)
}) })
// step 4: if attributes are derived, pass them to the engine. // step 4: if attributes are derived, pass them to the engine.
t.Run("derived attributes", func(t *testing.T) { t.Run("derived attributes", func(t *testing.T) {
...@@ -68,7 +68,7 @@ func TestProgramDeriver(t *testing.T) { ...@@ -68,7 +68,7 @@ func TestProgramDeriver(t *testing.T) {
p.OnEvent(derive.DerivedAttributesEvent{Attributes: attrib}) p.OnEvent(derive.DerivedAttributesEvent{Attributes: attrib})
m.AssertExpectations(t) m.AssertExpectations(t)
require.False(t, p.closing) require.False(t, p.closing)
require.NoError(t, p.result) require.NoError(t, p.resultError)
}) })
// step 5: if attributes were invalid, continue with derivation for new attributes. // step 5: if attributes were invalid, continue with derivation for new attributes.
t.Run("invalid payload", func(t *testing.T) { t.Run("invalid payload", func(t *testing.T) {
...@@ -77,7 +77,7 @@ func TestProgramDeriver(t *testing.T) { ...@@ -77,7 +77,7 @@ func TestProgramDeriver(t *testing.T) {
p.OnEvent(engine.InvalidPayloadAttributesEvent{Attributes: &derive.AttributesWithParent{}}) p.OnEvent(engine.InvalidPayloadAttributesEvent{Attributes: &derive.AttributesWithParent{}})
m.AssertExpectations(t) m.AssertExpectations(t)
require.False(t, p.closing) require.False(t, p.closing)
require.NoError(t, p.result) require.NoError(t, p.resultError)
}) })
// step 6: if attributes were valid, we may have reached the target. // step 6: if attributes were valid, we may have reached the target.
// Or back to step 2 (PendingSafeUpdateEvent) // Or back to step 2 (PendingSafeUpdateEvent)
...@@ -87,21 +87,21 @@ func TestProgramDeriver(t *testing.T) { ...@@ -87,21 +87,21 @@ func TestProgramDeriver(t *testing.T) {
p.OnEvent(engine.ForkchoiceUpdateEvent{SafeL2Head: eth.L2BlockRef{Number: 42 + 1}}) p.OnEvent(engine.ForkchoiceUpdateEvent{SafeL2Head: eth.L2BlockRef{Number: 42 + 1}})
m.AssertExpectations(t) m.AssertExpectations(t)
require.True(t, p.closing) require.True(t, p.closing)
require.NoError(t, p.result) require.NoError(t, p.resultError)
}) })
t.Run("completed", func(t *testing.T) { t.Run("completed", func(t *testing.T) {
p, m := newProgram(t, 42) p, m := newProgram(t, 42)
p.OnEvent(engine.ForkchoiceUpdateEvent{SafeL2Head: eth.L2BlockRef{Number: 42}}) p.OnEvent(engine.ForkchoiceUpdateEvent{SafeL2Head: eth.L2BlockRef{Number: 42}})
m.AssertExpectations(t) m.AssertExpectations(t)
require.True(t, p.closing) require.True(t, p.closing)
require.NoError(t, p.result) require.NoError(t, p.resultError)
}) })
t.Run("incomplete", func(t *testing.T) { t.Run("incomplete", func(t *testing.T) {
p, m := newProgram(t, 42) p, m := newProgram(t, 42)
p.OnEvent(engine.ForkchoiceUpdateEvent{SafeL2Head: eth.L2BlockRef{Number: 42 - 1}}) p.OnEvent(engine.ForkchoiceUpdateEvent{SafeL2Head: eth.L2BlockRef{Number: 42 - 1}})
m.AssertExpectations(t) m.AssertExpectations(t)
require.False(t, p.closing) require.False(t, p.closing)
require.NoError(t, p.result) require.NoError(t, p.resultError)
}) })
}) })
// Do not stop processing when the deriver is idle, the engine may still be busy and create further events. // Do not stop processing when the deriver is idle, the engine may still be busy and create further events.
...@@ -110,7 +110,7 @@ func TestProgramDeriver(t *testing.T) { ...@@ -110,7 +110,7 @@ func TestProgramDeriver(t *testing.T) {
p.OnEvent(derive.DeriverIdleEvent{}) p.OnEvent(derive.DeriverIdleEvent{})
m.AssertExpectations(t) m.AssertExpectations(t)
require.False(t, p.closing) require.False(t, p.closing)
require.Nil(t, p.result) require.NoError(t, p.resultError)
}) })
// on inconsistent chain data: stop with error // on inconsistent chain data: stop with error
t.Run("reset event", func(t *testing.T) { t.Run("reset event", func(t *testing.T) {
...@@ -118,7 +118,7 @@ func TestProgramDeriver(t *testing.T) { ...@@ -118,7 +118,7 @@ func TestProgramDeriver(t *testing.T) {
p.OnEvent(rollup.ResetEvent{Err: errors.New("reset test err")}) p.OnEvent(rollup.ResetEvent{Err: errors.New("reset test err")})
m.AssertExpectations(t) m.AssertExpectations(t)
require.True(t, p.closing) require.True(t, p.closing)
require.NotNil(t, p.result) require.Error(t, p.resultError)
}) })
// on L1 temporary error: stop with error // on L1 temporary error: stop with error
t.Run("L1 temporary error event", func(t *testing.T) { t.Run("L1 temporary error event", func(t *testing.T) {
...@@ -126,7 +126,7 @@ func TestProgramDeriver(t *testing.T) { ...@@ -126,7 +126,7 @@ func TestProgramDeriver(t *testing.T) {
p.OnEvent(rollup.L1TemporaryErrorEvent{Err: errors.New("temp test err")}) p.OnEvent(rollup.L1TemporaryErrorEvent{Err: errors.New("temp test err")})
m.AssertExpectations(t) m.AssertExpectations(t)
require.True(t, p.closing) require.True(t, p.closing)
require.NotNil(t, p.result) require.Error(t, p.resultError)
}) })
// on engine temporary error: continue derivation (because legacy, not all connection related) // on engine temporary error: continue derivation (because legacy, not all connection related)
t.Run("engine temp error event", func(t *testing.T) { t.Run("engine temp error event", func(t *testing.T) {
...@@ -135,7 +135,7 @@ func TestProgramDeriver(t *testing.T) { ...@@ -135,7 +135,7 @@ func TestProgramDeriver(t *testing.T) {
p.OnEvent(rollup.EngineTemporaryErrorEvent{Err: errors.New("temp test err")}) p.OnEvent(rollup.EngineTemporaryErrorEvent{Err: errors.New("temp test err")})
m.AssertExpectations(t) m.AssertExpectations(t)
require.False(t, p.closing) require.False(t, p.closing)
require.NoError(t, p.result) require.NoError(t, p.resultError)
}) })
// on critical error: stop // on critical error: stop
t.Run("critical error event", func(t *testing.T) { t.Run("critical error event", func(t *testing.T) {
...@@ -143,14 +143,14 @@ func TestProgramDeriver(t *testing.T) { ...@@ -143,14 +143,14 @@ func TestProgramDeriver(t *testing.T) {
p.OnEvent(rollup.ResetEvent{Err: errors.New("crit test err")}) p.OnEvent(rollup.ResetEvent{Err: errors.New("crit test err")})
m.AssertExpectations(t) m.AssertExpectations(t)
require.True(t, p.closing) require.True(t, p.closing)
require.NotNil(t, p.result) require.Error(t, p.resultError)
}) })
t.Run("unknown event", func(t *testing.T) { t.Run("unknown event", func(t *testing.T) {
p, m := newProgram(t, 1000) p, m := newProgram(t, 1000)
p.OnEvent(TestEvent{}) p.OnEvent(TestEvent{})
m.AssertExpectations(t) m.AssertExpectations(t)
require.False(t, p.closing) require.False(t, p.closing)
require.NoError(t, p.result) require.NoError(t, p.resultError)
}) })
} }
......
...@@ -91,7 +91,7 @@ func runInteropProgram(logger log.Logger, bootInfo *boot.BootInfo, l1PreimageOra ...@@ -91,7 +91,7 @@ func runInteropProgram(logger log.Logger, bootInfo *boot.BootInfo, l1PreimageOra
if !validateClaim { if !validateClaim {
return nil return nil
} }
return claim.ValidateClaim(logger, derivationResult.SafeHead, eth.Bytes32(bootInfo.L2Claim), eth.Bytes32(expected)) return claim.ValidateClaim(logger, derivationResult.Head, eth.Bytes32(bootInfo.L2Claim), eth.Bytes32(expected))
} }
type interopTaskExecutor struct { type interopTaskExecutor struct {
......
...@@ -75,7 +75,7 @@ func (t *stubTasks) RunDerivation( ...@@ -75,7 +75,7 @@ func (t *stubTasks) RunDerivation(
_ l1.Oracle, _ l1.Oracle,
_ l2.Oracle) (tasks.DerivationResult, error) { _ l2.Oracle) (tasks.DerivationResult, error) {
return tasks.DerivationResult{ return tasks.DerivationResult{
SafeHead: t.l2SafeHead, Head: t.l2SafeHead,
BlockHash: t.blockHash, BlockHash: t.blockHash,
OutputRoot: t.outputRoot, OutputRoot: t.outputRoot,
}, t.err }, t.err
......
...@@ -48,7 +48,7 @@ func (i *TransitionState) Hash() (common.Hash, error) { ...@@ -48,7 +48,7 @@ func (i *TransitionState) Hash() (common.Hash, error) {
return crypto.Keccak256Hash(data), nil return crypto.Keccak256Hash(data), nil
} }
func UnmarshalProofsState(data []byte) (*TransitionState, error) { func UnmarshalTransitionState(data []byte) (*TransitionState, error) {
if len(data) == 0 { if len(data) == 0 {
return nil, eth.ErrInvalidSuperRoot return nil, eth.ErrInvalidSuperRoot
} }
......
...@@ -27,7 +27,7 @@ func TestTransitionStateCodec(t *testing.T) { ...@@ -27,7 +27,7 @@ func TestTransitionStateCodec(t *testing.T) {
} }
data, err := state.Marshal() data, err := state.Marshal()
require.NoError(t, err) require.NoError(t, err)
actual, err := UnmarshalProofsState(data) actual, err := UnmarshalTransitionState(data)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, state, actual) require.Equal(t, state, actual)
}) })
...@@ -44,7 +44,7 @@ func TestTransitionStateCodec(t *testing.T) { ...@@ -44,7 +44,7 @@ func TestTransitionStateCodec(t *testing.T) {
SuperRoot: superRoot.Marshal(), SuperRoot: superRoot.Marshal(),
} }
data := superRoot.Marshal() data := superRoot.Marshal()
actual, err := UnmarshalProofsState(data) actual, err := UnmarshalTransitionState(data)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, expected, actual) require.Equal(t, expected, actual)
}) })
......
...@@ -124,7 +124,7 @@ func (p *PreimageOracle) BlockDataByHash(agreedBlockHash, blockHash common.Hash, ...@@ -124,7 +124,7 @@ func (p *PreimageOracle) BlockDataByHash(agreedBlockHash, blockHash common.Hash,
func (p *PreimageOracle) TransitionStateByRoot(root common.Hash) *interopTypes.TransitionState { func (p *PreimageOracle) TransitionStateByRoot(root common.Hash) *interopTypes.TransitionState {
p.hint.Hint(AgreedPrestateHint(root)) p.hint.Hint(AgreedPrestateHint(root))
data := p.oracle.Get(preimage.Keccak256Key(root)) data := p.oracle.Get(preimage.Keccak256Key(root))
output, err := interopTypes.UnmarshalProofsState(data) output, err := interopTypes.UnmarshalTransitionState(data)
if err != nil { if err != nil {
panic(fmt.Errorf("invalid agreed prestate data for root %s: %w", root, err)) panic(fmt.Errorf("invalid agreed prestate data for root %s: %w", root, err))
} }
......
...@@ -25,5 +25,5 @@ func RunPreInteropProgram(logger log.Logger, bootInfo *boot.BootInfo, l1Preimage ...@@ -25,5 +25,5 @@ func RunPreInteropProgram(logger log.Logger, bootInfo *boot.BootInfo, l1Preimage
if err != nil { if err != nil {
return err return err
} }
return claim.ValidateClaim(logger, result.SafeHead, eth.Bytes32(bootInfo.L2Claim), result.OutputRoot) return claim.ValidateClaim(logger, result.Head, eth.Bytes32(bootInfo.L2Claim), result.OutputRoot)
} }
package tasks package tasks
import ( import (
"context"
"fmt" "fmt"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
...@@ -15,12 +14,11 @@ import ( ...@@ -15,12 +14,11 @@ import (
) )
type L2Source interface { type L2Source interface {
L2BlockRefByLabel(ctx context.Context, label eth.BlockLabel) (eth.L2BlockRef, error)
L2OutputRoot(uint64) (common.Hash, eth.Bytes32, error) L2OutputRoot(uint64) (common.Hash, eth.Bytes32, error)
} }
type DerivationResult struct { type DerivationResult struct {
SafeHead eth.L2BlockRef Head eth.L2BlockRef
BlockHash common.Hash BlockHash common.Hash
OutputRoot eth.Bytes32 OutputRoot eth.Bytes32
} }
...@@ -49,23 +47,20 @@ func RunDerivation( ...@@ -49,23 +47,20 @@ func RunDerivation(
logger.Info("Starting derivation") logger.Info("Starting derivation")
d := cldr.NewDriver(logger, cfg, l1Source, l1BlobsSource, l2Source, l2ClaimBlockNum) d := cldr.NewDriver(logger, cfg, l1Source, l1BlobsSource, l2Source, l2ClaimBlockNum)
if err := d.RunComplete(); err != nil { result, err := d.RunComplete()
if err != nil {
return DerivationResult{}, fmt.Errorf("failed to run program to completion: %w", err) return DerivationResult{}, fmt.Errorf("failed to run program to completion: %w", err)
} }
return loadOutputRoot(l2ClaimBlockNum, l2Source) return loadOutputRoot(l2ClaimBlockNum, result, l2Source)
} }
func loadOutputRoot(l2ClaimBlockNum uint64, src L2Source) (DerivationResult, error) { func loadOutputRoot(l2ClaimBlockNum uint64, head eth.L2BlockRef, src L2Source) (DerivationResult, error) {
l2Head, err := src.L2BlockRefByLabel(context.Background(), eth.Safe) blockHash, outputRoot, err := src.L2OutputRoot(min(l2ClaimBlockNum, head.Number))
if err != nil {
return DerivationResult{}, fmt.Errorf("cannot retrieve safe head: %w", err)
}
blockHash, outputRoot, err := src.L2OutputRoot(min(l2ClaimBlockNum, l2Head.Number))
if err != nil { if err != nil {
return DerivationResult{}, fmt.Errorf("calculate L2 output root: %w", err) return DerivationResult{}, fmt.Errorf("calculate L2 output root: %w", err)
} }
return DerivationResult{ return DerivationResult{
SafeHead: l2Head, Head: head,
BlockHash: blockHash, BlockHash: blockHash,
OutputRoot: outputRoot, OutputRoot: outputRoot,
}, nil }, nil
......
package tasks package tasks
import ( import (
"context"
"errors" "errors"
"testing" "testing"
...@@ -12,66 +11,51 @@ import ( ...@@ -12,66 +11,51 @@ import (
func TestLoadOutputRoot(t *testing.T) { func TestLoadOutputRoot(t *testing.T) {
t.Run("Success", func(t *testing.T) { t.Run("Success", func(t *testing.T) {
safeHead := eth.L2BlockRef{Number: 65}
l2 := &mockL2{ l2 := &mockL2{
blockHash: common.Hash{0x24}, blockHash: common.Hash{0x24},
outputRoot: eth.Bytes32{0x11}, outputRoot: eth.Bytes32{0x11},
safeL2: eth.L2BlockRef{Number: 65},
} }
result, err := loadOutputRoot(uint64(0), l2) result, err := loadOutputRoot(uint64(0), safeHead, l2)
require.NoError(t, err) require.NoError(t, err)
assertDerivationResult(t, result, l2.safeL2, l2.blockHash, l2.outputRoot) assertDerivationResult(t, result, safeHead, l2.blockHash, l2.outputRoot)
}) })
t.Run("Success-PriorToSafeHead", func(t *testing.T) { t.Run("Success-PriorToSafeHead", func(t *testing.T) {
expected := eth.Bytes32{0x11} expected := eth.Bytes32{0x11}
safeHead := eth.L2BlockRef{
Number: 10,
}
l2 := &mockL2{ l2 := &mockL2{
blockHash: common.Hash{0x24}, blockHash: common.Hash{0x24},
outputRoot: expected, outputRoot: expected,
safeL2: eth.L2BlockRef{
Number: 10,
},
} }
result, err := loadOutputRoot(uint64(20), l2) result, err := loadOutputRoot(uint64(20), safeHead, l2)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, uint64(10), l2.requestedOutputRoot) require.Equal(t, uint64(10), l2.requestedOutputRoot)
assertDerivationResult(t, result, l2.safeL2, l2.blockHash, l2.outputRoot) assertDerivationResult(t, result, safeHead, l2.blockHash, l2.outputRoot)
})
t.Run("Error-SafeHead", func(t *testing.T) {
expectedErr := errors.New("boom")
l2 := &mockL2{
blockHash: common.Hash{0x24},
outputRoot: eth.Bytes32{0x11},
safeL2: eth.L2BlockRef{Number: 10},
safeL2Err: expectedErr,
}
_, err := loadOutputRoot(uint64(0), l2)
require.ErrorIs(t, err, expectedErr)
}) })
t.Run("Error-OutputRoot", func(t *testing.T) { t.Run("Error-OutputRoot", func(t *testing.T) {
expectedErr := errors.New("boom") expectedErr := errors.New("boom")
safeHead := eth.L2BlockRef{Number: 10}
l2 := &mockL2{ l2 := &mockL2{
blockHash: common.Hash{0x24}, blockHash: common.Hash{0x24},
outputRoot: eth.Bytes32{0x11}, outputRoot: eth.Bytes32{0x11},
outputRootErr: expectedErr, outputRootErr: expectedErr,
safeL2: eth.L2BlockRef{Number: 10},
} }
_, err := loadOutputRoot(uint64(0), l2) _, err := loadOutputRoot(uint64(0), safeHead, l2)
require.ErrorIs(t, err, expectedErr) require.ErrorIs(t, err, expectedErr)
}) })
} }
func assertDerivationResult(t *testing.T, actual DerivationResult, safeHead eth.L2BlockRef, blockHash common.Hash, outputRoot eth.Bytes32) { func assertDerivationResult(t *testing.T, actual DerivationResult, safeHead eth.L2BlockRef, blockHash common.Hash, outputRoot eth.Bytes32) {
require.Equal(t, safeHead, actual.SafeHead) require.Equal(t, safeHead, actual.Head)
require.Equal(t, blockHash, actual.BlockHash) require.Equal(t, blockHash, actual.BlockHash)
require.Equal(t, outputRoot, actual.OutputRoot) require.Equal(t, outputRoot, actual.OutputRoot)
} }
type mockL2 struct { type mockL2 struct {
safeL2 eth.L2BlockRef
safeL2Err error
blockHash common.Hash blockHash common.Hash
outputRoot eth.Bytes32 outputRoot eth.Bytes32
outputRootErr error outputRootErr error
...@@ -79,16 +63,6 @@ type mockL2 struct { ...@@ -79,16 +63,6 @@ type mockL2 struct {
requestedOutputRoot uint64 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) (common.Hash, eth.Bytes32, error) { func (m *mockL2) L2OutputRoot(u uint64) (common.Hash, eth.Bytes32, error) {
m.requestedOutputRoot = u m.requestedOutputRoot = u
if m.outputRootErr != nil { if m.outputRootErr != nil {
......
...@@ -14,6 +14,7 @@ import ( ...@@ -14,6 +14,7 @@ import (
"github.com/ethereum-optimism/optimism/op-program/host/types" "github.com/ethereum-optimism/optimism/op-program/host/types"
oplog "github.com/ethereum-optimism/optimism/op-service/log" oplog "github.com/ethereum-optimism/optimism/op-service/log"
"github.com/ethereum-optimism/optimism/op-service/sources" "github.com/ethereum-optimism/optimism/op-service/sources"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
...@@ -202,7 +203,11 @@ func TestL2Head(t *testing.T) { ...@@ -202,7 +203,11 @@ func TestL2Head(t *testing.T) {
func TestL2OutputRoot(t *testing.T) { func TestL2OutputRoot(t *testing.T) {
t.Run("Required", func(t *testing.T) { t.Run("Required", func(t *testing.T) {
verifyArgsInvalid(t, "flag l2.outputroot is required", addRequiredArgsExcept("--l2.outputroot")) verifyArgsInvalid(t, "flag l2.outputroot or l2.agreed-prestate is required", addRequiredArgsExcept("--l2.outputroot"))
})
t.Run("NotRequiredWhenAgreedPrestateProvided", func(t *testing.T) {
configForArgs(t, addRequiredArgsExcept("--l2.outputroot", "--l2.agreed-prestate", "0x1234"))
}) })
t.Run("Valid", func(t *testing.T) { t.Run("Valid", func(t *testing.T) {
...@@ -215,6 +220,33 @@ func TestL2OutputRoot(t *testing.T) { ...@@ -215,6 +220,33 @@ func TestL2OutputRoot(t *testing.T) {
}) })
} }
func TestL2AgreedPrestate(t *testing.T) {
t.Run("NotRequiredWhenL2OutputRootProvided", func(t *testing.T) {
configForArgs(t, addRequiredArgsExcept("--l2.outputroot", "--l2.outputroot", "0x1234"))
})
t.Run("Valid", func(t *testing.T) {
prestate := "0x1234"
prestateBytes := common.FromHex(prestate)
expectedOutputRoot := crypto.Keccak256Hash(prestateBytes)
cfg := configForArgs(t, addRequiredArgsExcept("--l2.outputroot", "--l2.agreed-prestate", prestate))
require.Equal(t, expectedOutputRoot, cfg.L2OutputRoot)
require.Equal(t, prestateBytes, cfg.AgreedPrestate)
})
t.Run("MustNotSpecifyWithL2OutputRoot", func(t *testing.T) {
verifyArgsInvalid(t, "flag l2.outputroot and l2.agreed-prestate must not be specified together", addRequiredArgs("--l2.agreed-prestate", "0x1234"))
})
t.Run("Invalid", func(t *testing.T) {
verifyArgsInvalid(t, config.ErrInvalidAgreedPrestate.Error(), addRequiredArgsExcept("--l2.outputroot", "--l2.agreed-prestate", "something"))
})
t.Run("ZeroLength", func(t *testing.T) {
verifyArgsInvalid(t, config.ErrInvalidAgreedPrestate.Error(), addRequiredArgsExcept("--l2.outputroot", "--l2.agreed-prestate", "0x"))
})
}
func TestL1Head(t *testing.T) { func TestL1Head(t *testing.T) {
t.Run("Required", func(t *testing.T) { t.Run("Required", func(t *testing.T) {
verifyArgsInvalid(t, "flag l1.head is required", addRequiredArgsExcept("--l1.head")) verifyArgsInvalid(t, "flag l1.head is required", addRequiredArgsExcept("--l1.head"))
......
...@@ -12,6 +12,7 @@ import ( ...@@ -12,6 +12,7 @@ import (
"github.com/ethereum-optimism/optimism/op-program/chainconfig" "github.com/ethereum-optimism/optimism/op-program/chainconfig"
"github.com/ethereum-optimism/optimism/op-program/client/boot" "github.com/ethereum-optimism/optimism/op-program/client/boot"
"github.com/ethereum-optimism/optimism/op-program/host/types" "github.com/ethereum-optimism/optimism/op-program/host/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-program/host/flags" "github.com/ethereum-optimism/optimism/op-program/host/flags"
...@@ -24,17 +25,19 @@ import ( ...@@ -24,17 +25,19 @@ import (
) )
var ( var (
ErrMissingRollupConfig = errors.New("missing rollup config") ErrMissingRollupConfig = errors.New("missing rollup config")
ErrMissingL2Genesis = errors.New("missing l2 genesis") ErrMissingL2Genesis = errors.New("missing l2 genesis")
ErrInvalidL1Head = errors.New("invalid l1 head") ErrInvalidL1Head = errors.New("invalid l1 head")
ErrInvalidL2Head = errors.New("invalid l2 head") ErrInvalidL2Head = errors.New("invalid l2 head")
ErrInvalidL2OutputRoot = errors.New("invalid l2 output root") ErrInvalidL2OutputRoot = errors.New("invalid l2 output root")
ErrL1AndL2Inconsistent = errors.New("l1 and l2 options must be specified together or both omitted") ErrInvalidAgreedPrestate = errors.New("invalid l2 agreed prestate")
ErrInvalidL2Claim = errors.New("invalid l2 claim") ErrL1AndL2Inconsistent = errors.New("l1 and l2 options must be specified together or both omitted")
ErrInvalidL2ClaimBlock = errors.New("invalid l2 claim block number") ErrInvalidL2Claim = errors.New("invalid l2 claim")
ErrDataDirRequired = errors.New("datadir must be specified when in non-fetching mode") ErrInvalidL2ClaimBlock = errors.New("invalid l2 claim block number")
ErrNoExecInServerMode = errors.New("exec command must not be set when in server mode") ErrDataDirRequired = errors.New("datadir must be specified when in non-fetching mode")
ErrInvalidDataFormat = errors.New("invalid data format") ErrNoExecInServerMode = errors.New("exec command must not be set when in server mode")
ErrInvalidDataFormat = errors.New("invalid data format")
ErrMissingAgreedPrestate = errors.New("missing agreed prestate")
) )
type Config struct { type Config struct {
...@@ -80,6 +83,8 @@ type Config struct { ...@@ -80,6 +83,8 @@ type Config struct {
// InteropEnabled enables interop fault proof rules when running the client in-process // InteropEnabled enables interop fault proof rules when running the client in-process
InteropEnabled bool InteropEnabled bool
// AgreedPrestate is the preimage of the agreed prestate claim. Required for interop.
AgreedPrestate []byte
} }
func (c *Config) Check() error { func (c *Config) Check() error {
...@@ -116,6 +121,14 @@ func (c *Config) Check() error { ...@@ -116,6 +121,14 @@ func (c *Config) Check() error {
if c.DataDir != "" && !slices.Contains(types.SupportedDataFormats, c.DataFormat) { if c.DataDir != "" && !slices.Contains(types.SupportedDataFormats, c.DataFormat) {
return ErrInvalidDataFormat return ErrInvalidDataFormat
} }
if c.InteropEnabled {
if len(c.AgreedPrestate) == 0 {
return ErrMissingAgreedPrestate
}
if crypto.Keccak256Hash(c.AgreedPrestate) != c.L2OutputRoot {
return fmt.Errorf("%w: must be preimage of L2 output root", ErrInvalidAgreedPrestate)
}
}
return nil return nil
} }
...@@ -162,7 +175,18 @@ func NewConfigFromCLI(log log.Logger, ctx *cli.Context) (*Config, error) { ...@@ -162,7 +175,18 @@ func NewConfigFromCLI(log log.Logger, ctx *cli.Context) (*Config, error) {
if l2Head == (common.Hash{}) { if l2Head == (common.Hash{}) {
return nil, ErrInvalidL2Head return nil, ErrInvalidL2Head
} }
l2OutputRoot := common.HexToHash(ctx.String(flags.L2OutputRoot.Name)) var l2OutputRoot common.Hash
var agreedPrestate []byte
if ctx.IsSet(flags.L2OutputRoot.Name) {
l2OutputRoot = common.HexToHash(ctx.String(flags.L2OutputRoot.Name))
} else if ctx.IsSet(flags.L2AgreedPrestate.Name) {
prestateStr := ctx.String(flags.L2AgreedPrestate.Name)
agreedPrestate = common.FromHex(prestateStr)
if len(agreedPrestate) == 0 {
return nil, ErrInvalidAgreedPrestate
}
l2OutputRoot = crypto.Keccak256Hash(agreedPrestate)
}
if l2OutputRoot == (common.Hash{}) { if l2OutputRoot == (common.Hash{}) {
return nil, ErrInvalidL2OutputRoot return nil, ErrInvalidL2OutputRoot
} }
...@@ -238,6 +262,7 @@ func NewConfigFromCLI(log log.Logger, ctx *cli.Context) (*Config, error) { ...@@ -238,6 +262,7 @@ func NewConfigFromCLI(log log.Logger, ctx *cli.Context) (*Config, error) {
L2ChainConfig: l2ChainConfig, L2ChainConfig: l2ChainConfig,
L2Head: l2Head, L2Head: l2Head,
L2OutputRoot: l2OutputRoot, L2OutputRoot: l2OutputRoot,
AgreedPrestate: agreedPrestate,
L2Claim: l2Claim, L2Claim: l2Claim,
L2ClaimBlockNumber: l2ClaimBlockNum, L2ClaimBlockNumber: l2ClaimBlockNum,
L1Head: l1Head, L1Head: l1Head,
......
...@@ -11,6 +11,7 @@ import ( ...@@ -11,6 +11,7 @@ import (
"github.com/ethereum-optimism/optimism/op-program/client/boot" "github.com/ethereum-optimism/optimism/op-program/client/boot"
"github.com/ethereum-optimism/optimism/op-program/host/types" "github.com/ethereum-optimism/optimism/op-program/host/types"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -174,7 +175,45 @@ func TestCustomL2ChainID(t *testing.T) { ...@@ -174,7 +175,45 @@ func TestCustomL2ChainID(t *testing.T) {
cfg := NewConfig(validRollupConfig, customChainConfig, validL1Head, validL2Head, validL2OutputRoot, validL2Claim, validL2ClaimBlockNum) cfg := NewConfig(validRollupConfig, customChainConfig, validL1Head, validL2Head, validL2OutputRoot, validL2Claim, validL2ClaimBlockNum)
require.Equal(t, cfg.L2ChainID, boot.CustomChainIDIndicator) require.Equal(t, cfg.L2ChainID, boot.CustomChainIDIndicator)
}) })
}
func TestAgreedPrestate(t *testing.T) {
t.Run("requiredWithInterop-nil", func(t *testing.T) {
cfg := validConfig()
cfg.InteropEnabled = true
cfg.AgreedPrestate = nil
err := cfg.Check()
require.ErrorIs(t, err, ErrMissingAgreedPrestate)
})
t.Run("requiredWithInterop-empty", func(t *testing.T) {
cfg := validConfig()
cfg.InteropEnabled = true
cfg.AgreedPrestate = []byte{}
err := cfg.Check()
require.ErrorIs(t, err, ErrMissingAgreedPrestate)
})
t.Run("notRequiredWithoutInterop", func(t *testing.T) {
cfg := validConfig()
cfg.AgreedPrestate = nil
require.NoError(t, cfg.Check())
})
t.Run("valid", func(t *testing.T) {
cfg := validConfig()
cfg.InteropEnabled = true
cfg.AgreedPrestate = []byte{1}
cfg.L2OutputRoot = crypto.Keccak256Hash(cfg.AgreedPrestate)
require.NoError(t, cfg.Check())
})
t.Run("mustMatchL2OutputRoot", func(t *testing.T) {
cfg := validConfig()
cfg.InteropEnabled = true
cfg.AgreedPrestate = []byte{1}
cfg.L2OutputRoot = common.Hash{0xaa}
require.ErrorIs(t, cfg.Check(), ErrInvalidAgreedPrestate)
})
} }
func TestDBFormat(t *testing.T) { func TestDBFormat(t *testing.T) {
......
...@@ -72,9 +72,15 @@ var ( ...@@ -72,9 +72,15 @@ var (
} }
L2OutputRoot = &cli.StringFlag{ L2OutputRoot = &cli.StringFlag{
Name: "l2.outputroot", Name: "l2.outputroot",
Usage: "Agreed L2 Output Root to start derivation from", Usage: "Agreed L2 Output Root to start derivation from. Used for non-interop games.",
EnvVars: prefixEnvVars("L2_OUTPUT_ROOT"), EnvVars: prefixEnvVars("L2_OUTPUT_ROOT"),
} }
L2AgreedPrestate = &cli.StringFlag{
Name: "l2.agreed-prestate",
Usage: "Agreed L2 pre state pre-image to start derivation from. " +
"l2.outputroot will be automatically set to the hash of the prestate. Used for interop-enabled games.",
EnvVars: prefixEnvVars("L2_AGREED_PRESTATE"),
}
L2Claim = &cli.StringFlag{ L2Claim = &cli.StringFlag{
Name: "l2.claim", Name: "l2.claim",
Usage: "Claimed L2 output root to validate", Usage: "Claimed L2 output root to validate",
...@@ -133,12 +139,13 @@ var Flags []cli.Flag ...@@ -133,12 +139,13 @@ var Flags []cli.Flag
var requiredFlags = []cli.Flag{ var requiredFlags = []cli.Flag{
L1Head, L1Head,
L2Head, L2Head,
L2OutputRoot,
L2Claim, L2Claim,
L2BlockNumber, L2BlockNumber,
} }
var programFlags = []cli.Flag{ var programFlags = []cli.Flag{
L2OutputRoot,
L2AgreedPrestate,
L2Custom, L2Custom,
RollupConfig, RollupConfig,
Network, Network,
...@@ -184,5 +191,11 @@ func CheckRequired(ctx *cli.Context) error { ...@@ -184,5 +191,11 @@ func CheckRequired(ctx *cli.Context) error {
return fmt.Errorf("flag %s is required", flag.Names()[0]) return fmt.Errorf("flag %s is required", flag.Names()[0])
} }
} }
if !ctx.IsSet(L2OutputRoot.Name) && !ctx.IsSet(L2AgreedPrestate.Name) {
return fmt.Errorf("flag %s or %s is required", L2OutputRoot.Name, L2AgreedPrestate.Name)
}
if ctx.IsSet(L2OutputRoot.Name) && ctx.IsSet(L2AgreedPrestate.Name) {
return fmt.Errorf("flag %s and %s must not be specified together", L2OutputRoot.Name, L2AgreedPrestate.Name)
}
return nil return nil
} }
...@@ -95,7 +95,7 @@ func makeDefaultPrefetcher(ctx context.Context, logger log.Logger, kv kvstore.KV ...@@ -95,7 +95,7 @@ func makeDefaultPrefetcher(ctx context.Context, logger log.Logger, kv kvstore.KV
} }
executor := MakeProgramExecutor(logger, cfg) executor := MakeProgramExecutor(logger, cfg)
return prefetcher.NewPrefetcher(logger, l1Cl, l1BlobFetcher, l2Client, kv, cfg.L2ChainConfig, executor), nil return prefetcher.NewPrefetcher(logger, l1Cl, l1BlobFetcher, l2Client, kv, executor, cfg.AgreedPrestate), nil
} }
type programExecutor struct { type programExecutor struct {
......
...@@ -27,6 +27,8 @@ import ( ...@@ -27,6 +27,8 @@ import (
var ( var (
precompileSuccess = [1]byte{1} precompileSuccess = [1]byte{1}
precompileFailure = [1]byte{0} precompileFailure = [1]byte{0}
ErrAgreedPrestateUnavailable = errors.New("agreed prestate unavailable")
) )
var acceleratedPrecompiles = []common.Address{ var acceleratedPrecompiles = []common.Address{
...@@ -55,7 +57,8 @@ type Prefetcher struct { ...@@ -55,7 +57,8 @@ type Prefetcher struct {
kvStore kvstore.KV kvStore kvstore.KV
// Used to run the program for native block execution // Used to run the program for native block execution
executor ProgramExecutor executor ProgramExecutor
agreedPrestate []byte
} }
func NewPrefetcher( func NewPrefetcher(
...@@ -64,16 +67,17 @@ func NewPrefetcher( ...@@ -64,16 +67,17 @@ func NewPrefetcher(
l1BlobFetcher L1BlobSource, l1BlobFetcher L1BlobSource,
l2Fetcher hosttypes.L2Source, l2Fetcher hosttypes.L2Source,
kvStore kvstore.KV, kvStore kvstore.KV,
l2ChainConfig *params.ChainConfig,
executor ProgramExecutor, executor ProgramExecutor,
agreedPrestate []byte,
) *Prefetcher { ) *Prefetcher {
return &Prefetcher{ return &Prefetcher{
logger: logger, logger: logger,
l1Fetcher: NewRetryingL1Source(logger, l1Fetcher), l1Fetcher: NewRetryingL1Source(logger, l1Fetcher),
l1BlobFetcher: NewRetryingL1BlobSource(logger, l1BlobFetcher), l1BlobFetcher: NewRetryingL1BlobSource(logger, l1BlobFetcher),
l2Fetcher: NewRetryingL2Source(logger, l2Fetcher), l2Fetcher: NewRetryingL2Source(logger, l2Fetcher),
kvStore: kvStore, kvStore: kvStore,
executor: executor, executor: executor,
agreedPrestate: agreedPrestate,
} }
} }
...@@ -312,6 +316,12 @@ func (p *Prefetcher) prefetch(ctx context.Context, hint string) error { ...@@ -312,6 +316,12 @@ func (p *Prefetcher) prefetch(ctx context.Context, hint string) error {
return fmt.Errorf("failed to re-execute block: %w", err) return fmt.Errorf("failed to re-execute block: %w", err)
} }
return p.kvStore.Put(BlockDataKey(blockHash).Key(), []byte{1}) return p.kvStore.Put(BlockDataKey(blockHash).Key(), []byte{1})
case l2.HintAgreedPrestate:
if len(p.agreedPrestate) == 0 {
return ErrAgreedPrestateUnavailable
}
hash := crypto.Keccak256Hash(p.agreedPrestate)
return p.kvStore.Put(preimage.Keccak256Key(hash).PreimageKey(), p.agreedPrestate)
} }
return fmt.Errorf("unknown hint type: %v", hintType) return fmt.Errorf("unknown hint type: %v", hintType)
} }
......
...@@ -567,6 +567,28 @@ func TestFetchL2BlockData(t *testing.T) { ...@@ -567,6 +567,28 @@ func TestFetchL2BlockData(t *testing.T) {
}) })
} }
func TestFetchAgreedPrestate(t *testing.T) {
t.Run("unavailable", func(t *testing.T) {
prefetcher, _, _, _, _ := createPrefetcher(t)
hash := common.Hash{0xaa}
hint := l2.AgreedPrestateHint(hash).Hint()
require.NoError(t, prefetcher.Hint(hint))
_, err := prefetcher.GetPreimage(context.Background(), hash)
require.ErrorIs(t, err, ErrAgreedPrestateUnavailable)
})
t.Run("available", func(t *testing.T) {
prestate := []byte{1, 2, 3, 6}
prefetcher, _, _, _, _ := createPrefetcherWithAgreedPrestate(t, prestate)
hash := crypto.Keccak256Hash(prestate)
hint := l2.AgreedPrestateHint(hash).Hint()
require.NoError(t, prefetcher.Hint(hint))
actual, err := prefetcher.GetPreimage(context.Background(), preimage.Keccak256Key(hash).PreimageKey())
require.NoError(t, err)
require.Equal(t, prestate, actual)
})
}
func TestBadHints(t *testing.T) { func TestBadHints(t *testing.T) {
prefetcher, _, _, _, kv := createPrefetcher(t) prefetcher, _, _, _, kv := createPrefetcher(t)
hash := common.Hash{0xad} hash := common.Hash{0xad}
...@@ -666,6 +688,9 @@ func (m *l2Client) ExpectOutputByRoot(root common.Hash, output eth.Output, err e ...@@ -666,6 +688,9 @@ func (m *l2Client) ExpectOutputByRoot(root common.Hash, output eth.Output, err e
} }
func createPrefetcher(t *testing.T) (*Prefetcher, *testutils.MockL1Source, *testutils.MockBlobsFetcher, *l2Client, kvstore.KV) { func createPrefetcher(t *testing.T) (*Prefetcher, *testutils.MockL1Source, *testutils.MockBlobsFetcher, *l2Client, kvstore.KV) {
return createPrefetcherWithAgreedPrestate(t, nil)
}
func createPrefetcherWithAgreedPrestate(t *testing.T, agreedPrestate []byte) (*Prefetcher, *testutils.MockL1Source, *testutils.MockBlobsFetcher, *l2Client, kvstore.KV) {
logger := testlog.Logger(t, log.LevelDebug) logger := testlog.Logger(t, log.LevelDebug)
kv := kvstore.NewMemKV() kv := kvstore.NewMemKV()
...@@ -676,7 +701,7 @@ func createPrefetcher(t *testing.T) (*Prefetcher, *testutils.MockL1Source, *test ...@@ -676,7 +701,7 @@ func createPrefetcher(t *testing.T) (*Prefetcher, *testutils.MockL1Source, *test
MockDebugClient: new(testutils.MockDebugClient), MockDebugClient: new(testutils.MockDebugClient),
} }
prefetcher := NewPrefetcher(logger, l1Source, l1BlobSource, l2Source, kv, nil, nil) prefetcher := NewPrefetcher(logger, l1Source, l1BlobSource, l2Source, kv, nil, agreedPrestate)
return prefetcher, l1Source, l1BlobSource, l2Source, kv return prefetcher, l1Source, l1BlobSource, l2Source, kv
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment