Commit f0f0643d authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

op-program: Pass required block hash to OutputByRoot rather than to L2Source constructor (#13716)

* op-program: Pass required block hash to OutputByRoot rather than to L2Source constructor

* Move check that output root matches requested hash to prefetcher instead of source.
parent 7dc282d4
...@@ -78,12 +78,12 @@ func RunFaultProofProgram(t helpers.Testing, logger log.Logger, l1 *helpers.L1Mi ...@@ -78,12 +78,12 @@ func RunFaultProofProgram(t helpers.Testing, logger log.Logger, l1 *helpers.L1Mi
// Set up in-process L2 source // Set up in-process L2 source
l2ClCfg := sources.L2ClientDefaultConfig(l2.RollupCfg, true) l2ClCfg := sources.L2ClientDefaultConfig(l2.RollupCfg, true)
l2RPC := l2Eng.RPCClient() l2RPC := l2Eng.RPCClient()
l2Client, err := hostcommon.NewL2Client(l2RPC, logger, nil, &hostcommon.L2ClientConfig{L2ClientConfig: l2ClCfg, L2Head: cfg.L2Head}) l2Client, err := hostcommon.NewL2Client(l2RPC, logger, nil, &hostcommon.L2ClientConfig{L2ClientConfig: l2ClCfg})
require.NoError(t, err, "failed to create L2 client") require.NoError(t, err, "failed to create L2 client")
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, executor, cfg.AgreedPrestate), nil return prefetcher.NewPrefetcher(logger, l1Cl, l1BlobFetcher, l2DebugCl, kv, executor, cfg.L2Head, 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)
......
...@@ -2,7 +2,6 @@ package common ...@@ -2,7 +2,6 @@ package common
import ( import (
"context" "context"
"fmt"
"github.com/ethereum-optimism/optimism/op-service/client" "github.com/ethereum-optimism/optimism/op-service/client"
"github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/eth"
...@@ -14,14 +13,10 @@ import ( ...@@ -14,14 +13,10 @@ import (
type L2Client struct { type L2Client struct {
*sources.L2Client *sources.L2Client
// l2Head is the L2 block hash that we use to fetch L2 output
l2Head common.Hash
} }
type L2ClientConfig struct { type L2ClientConfig struct {
*sources.L2ClientConfig *sources.L2ClientConfig
L2Head common.Hash
} }
func NewL2Client(client client.RPC, log log.Logger, metrics caching.Metrics, config *L2ClientConfig) (*L2Client, error) { func NewL2Client(client client.RPC, log log.Logger, metrics caching.Metrics, config *L2ClientConfig) (*L2Client, error) {
...@@ -31,21 +26,9 @@ func NewL2Client(client client.RPC, log log.Logger, metrics caching.Metrics, con ...@@ -31,21 +26,9 @@ func NewL2Client(client client.RPC, log log.Logger, metrics caching.Metrics, con
} }
return &L2Client{ return &L2Client{
L2Client: l2Client, L2Client: l2Client,
l2Head: config.L2Head,
}, nil }, nil
} }
func (s *L2Client) OutputByRoot(ctx context.Context, l2OutputRoot common.Hash) (eth.Output, error) { func (s *L2Client) OutputByRoot(ctx context.Context, blockRoot common.Hash) (eth.Output, error) {
output, err := s.OutputV0AtBlock(ctx, s.l2Head) return s.OutputV0AtBlock(ctx, blockRoot)
if err != nil {
return nil, err
}
actualOutputRoot := eth.OutputRoot(output)
if actualOutputRoot != eth.Bytes32(l2OutputRoot) {
// For fault proofs, we only reference outputs at the l2 head at boot time
// The caller shouldn't be requesting outputs at any other block
// If they are, there is no chance of recovery and we should panic to avoid retrying forever
panic(fmt.Errorf("output root %v from specified L2 block %v does not match requested output root %v", actualOutputRoot, s.l2Head, l2OutputRoot))
}
return output, nil
} }
...@@ -59,7 +59,7 @@ func NewL2Source(ctx context.Context, logger log.Logger, config *config.Config) ...@@ -59,7 +59,7 @@ func NewL2Source(ctx context.Context, logger log.Logger, config *config.Config)
canonicalDebugClient := sources.NewDebugClient(canonicalL2RPC.CallContext) canonicalDebugClient := sources.NewDebugClient(canonicalL2RPC.CallContext)
canonicalL2ClientCfg := sources.L2ClientDefaultConfig(config.Rollup, true) canonicalL2ClientCfg := sources.L2ClientDefaultConfig(config.Rollup, true)
canonicalL2Client, err := NewL2Client(canonicalL2RPC, logger, nil, &L2ClientConfig{L2ClientConfig: canonicalL2ClientCfg, L2Head: config.L2Head}) canonicalL2Client, err := NewL2Client(canonicalL2RPC, logger, nil, &L2ClientConfig{L2ClientConfig: canonicalL2ClientCfg})
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -77,7 +77,7 @@ func NewL2Source(ctx context.Context, logger log.Logger, config *config.Config) ...@@ -77,7 +77,7 @@ func NewL2Source(ctx context.Context, logger log.Logger, config *config.Config)
return nil, err return nil, err
} }
experimentalL2ClientCfg := sources.L2ClientDefaultConfig(config.Rollup, true) experimentalL2ClientCfg := sources.L2ClientDefaultConfig(config.Rollup, true)
experimentalL2Client, err := NewL2Client(experimentalRPC, logger, nil, &L2ClientConfig{L2ClientConfig: experimentalL2ClientCfg, L2Head: config.L2Head}) experimentalL2Client, err := NewL2Client(experimentalRPC, logger, nil, &L2ClientConfig{L2ClientConfig: experimentalL2ClientCfg})
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -120,11 +120,11 @@ func (l *L2Source) InfoAndTxsByHash(ctx context.Context, blockHash common.Hash) ...@@ -120,11 +120,11 @@ func (l *L2Source) InfoAndTxsByHash(ctx context.Context, blockHash common.Hash)
} }
// OutputByRoot implements prefetcher.L2Source. // OutputByRoot implements prefetcher.L2Source.
func (l *L2Source) OutputByRoot(ctx context.Context, root common.Hash) (eth.Output, error) { func (l *L2Source) OutputByRoot(ctx context.Context, blockRoot common.Hash) (eth.Output, error) {
if l.ExperimentalEnabled() { if l.ExperimentalEnabled() {
return l.experimentalClient.OutputByRoot(ctx, root) return l.experimentalClient.OutputByRoot(ctx, blockRoot)
} }
return l.canonicalEthClient.OutputByRoot(ctx, root) return l.canonicalEthClient.OutputByRoot(ctx, blockRoot)
} }
// ExecutionWitness implements prefetcher.L2Source. // ExecutionWitness implements prefetcher.L2Source.
......
...@@ -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, executor, cfg.AgreedPrestate), nil return prefetcher.NewPrefetcher(logger, l1Cl, l1BlobFetcher, l2Client, kv, executor, cfg.L2Head, cfg.AgreedPrestate), nil
} }
type programExecutor struct { type programExecutor struct {
......
...@@ -55,6 +55,8 @@ type Prefetcher struct { ...@@ -55,6 +55,8 @@ type Prefetcher struct {
l2Fetcher *RetryingL2Source l2Fetcher *RetryingL2Source
lastHint string lastHint string
kvStore kvstore.KV kvStore kvstore.KV
// l2Head is the L2 block hash to retrieve output root from if interop is disabled
l2Head common.Hash
// Used to run the program for native block execution // Used to run the program for native block execution
executor ProgramExecutor executor ProgramExecutor
...@@ -68,6 +70,7 @@ func NewPrefetcher( ...@@ -68,6 +70,7 @@ func NewPrefetcher(
l2Fetcher hosttypes.L2Source, l2Fetcher hosttypes.L2Source,
kvStore kvstore.KV, kvStore kvstore.KV,
executor ProgramExecutor, executor ProgramExecutor,
l2Head common.Hash,
agreedPrestate []byte, agreedPrestate []byte,
) *Prefetcher { ) *Prefetcher {
return &Prefetcher{ return &Prefetcher{
...@@ -77,6 +80,7 @@ func NewPrefetcher( ...@@ -77,6 +80,7 @@ func NewPrefetcher(
l2Fetcher: NewRetryingL2Source(logger, l2Fetcher), l2Fetcher: NewRetryingL2Source(logger, l2Fetcher),
kvStore: kvStore, kvStore: kvStore,
executor: executor, executor: executor,
l2Head: l2Head,
agreedPrestate: agreedPrestate, agreedPrestate: agreedPrestate,
} }
} }
...@@ -292,10 +296,14 @@ func (p *Prefetcher) prefetch(ctx context.Context, hint string) error { ...@@ -292,10 +296,14 @@ func (p *Prefetcher) prefetch(ctx context.Context, hint string) error {
if len(hintBytes) != 32 { if len(hintBytes) != 32 {
return fmt.Errorf("invalid L2 output hint: %x", hint) return fmt.Errorf("invalid L2 output hint: %x", hint)
} }
hash := common.Hash(hintBytes) requestedHash := common.Hash(hintBytes)
output, err := p.l2Fetcher.OutputByRoot(ctx, hash) output, err := p.l2Fetcher.OutputByRoot(ctx, p.l2Head)
if err != nil { if err != nil {
return fmt.Errorf("failed to fetch L2 output root %s: %w", hash, err) return fmt.Errorf("failed to fetch L2 output root for block %s: %w", p.l2Head, err)
}
hash := eth.OutputRoot(output)
if requestedHash != common.Hash(hash) {
return fmt.Errorf("output root %x from block %v does not match requested root: %x", hash, p.l2Head, requestedHash)
} }
return p.kvStore.Put(preimage.Keccak256Key(hash).PreimageKey(), output.Marshal()) return p.kvStore.Put(preimage.Keccak256Key(hash).PreimageKey(), output.Marshal())
case l2.HintL2BlockData: case l2.HintL2BlockData:
......
...@@ -646,7 +646,7 @@ func TestRetryWhenNotAvailableAfterPrefetching(t *testing.T) { ...@@ -646,7 +646,7 @@ func TestRetryWhenNotAvailableAfterPrefetching(t *testing.T) {
_, l1Source, l1BlobSource, l2Cl, kv := createPrefetcher(t) _, l1Source, l1BlobSource, l2Cl, kv := createPrefetcher(t)
putsToIgnore := 2 putsToIgnore := 2
kv = &unreliableKvStore{KV: kv, putsToIgnore: putsToIgnore} kv = &unreliableKvStore{KV: kv, putsToIgnore: putsToIgnore}
prefetcher := NewPrefetcher(testlog.Logger(t, log.LevelInfo), l1Source, l1BlobSource, l2Cl, kv, nil, nil) prefetcher := NewPrefetcher(testlog.Logger(t, log.LevelInfo), l1Source, l1BlobSource, l2Cl, kv, nil, common.Hash{}, nil)
// Expect one call for each ignored put, plus one more request for when the put succeeds // Expect one call for each ignored put, plus one more request for when the put succeeds
for i := 0; i < putsToIgnore+1; i++ { for i := 0; i < putsToIgnore+1; i++ {
...@@ -678,13 +678,13 @@ type l2Client struct { ...@@ -678,13 +678,13 @@ type l2Client struct {
*testutils.MockDebugClient *testutils.MockDebugClient
} }
func (m *l2Client) OutputByRoot(ctx context.Context, root common.Hash) (eth.Output, error) { func (m *l2Client) OutputByRoot(ctx context.Context, blockHash common.Hash) (eth.Output, error) {
out := m.Mock.MethodCalled("OutputByRoot", root) out := m.Mock.MethodCalled("OutputByRoot", blockHash)
return out[0].(eth.Output), *out[1].(*error) return out[0].(eth.Output), *out[1].(*error)
} }
func (m *l2Client) ExpectOutputByRoot(root common.Hash, output eth.Output, err error) { func (m *l2Client) ExpectOutputByRoot(blockRoot common.Hash, output eth.Output, err error) {
m.Mock.On("OutputByRoot", root).Once().Return(output, &err) m.Mock.On("OutputByRoot", blockRoot).Once().Return(output, &err)
} }
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) {
...@@ -701,7 +701,7 @@ func createPrefetcherWithAgreedPrestate(t *testing.T, agreedPrestate []byte) (*P ...@@ -701,7 +701,7 @@ func createPrefetcherWithAgreedPrestate(t *testing.T, agreedPrestate []byte) (*P
MockDebugClient: new(testutils.MockDebugClient), MockDebugClient: new(testutils.MockDebugClient),
} }
prefetcher := NewPrefetcher(logger, l1Source, l1BlobSource, l2Source, kv, nil, agreedPrestate) prefetcher := NewPrefetcher(logger, l1Source, l1BlobSource, l2Source, kv, nil, common.Hash{0xdd}, agreedPrestate)
return prefetcher, l1Source, l1BlobSource, l2Source, kv return prefetcher, l1Source, l1BlobSource, l2Source, kv
} }
......
...@@ -132,11 +132,11 @@ func (s *RetryingL2Source) CodeByHash(ctx context.Context, hash common.Hash) ([] ...@@ -132,11 +132,11 @@ func (s *RetryingL2Source) CodeByHash(ctx context.Context, hash common.Hash) ([]
}) })
} }
func (s *RetryingL2Source) OutputByRoot(ctx context.Context, root common.Hash) (eth.Output, error) { func (s *RetryingL2Source) OutputByRoot(ctx context.Context, blockRoot common.Hash) (eth.Output, error) {
return retry.Do(ctx, maxAttempts, s.strategy, func() (eth.Output, error) { return retry.Do(ctx, maxAttempts, s.strategy, func() (eth.Output, error) {
o, err := s.source.OutputByRoot(ctx, root) o, err := s.source.OutputByRoot(ctx, blockRoot)
if err != nil { if err != nil {
s.logger.Warn("Failed to fetch l2 output", "root", root, "err", err) s.logger.Warn("Failed to fetch l2 output", "block", blockRoot, "err", err)
return o, err return o, err
} }
return o, nil return o, nil
......
...@@ -354,8 +354,8 @@ func (m *MockL2Source) CodeByHash(ctx context.Context, hash common.Hash) ([]byte ...@@ -354,8 +354,8 @@ func (m *MockL2Source) CodeByHash(ctx context.Context, hash common.Hash) ([]byte
return out[0].([]byte), *out[1].(*error) return out[0].([]byte), *out[1].(*error)
} }
func (m *MockL2Source) OutputByRoot(ctx context.Context, root common.Hash) (eth.Output, error) { func (m *MockL2Source) OutputByRoot(ctx context.Context, blockRoot common.Hash) (eth.Output, error) {
out := m.Mock.MethodCalled("OutputByRoot", root) out := m.Mock.MethodCalled("OutputByRoot", blockRoot)
return out[0].(eth.Output), *out[1].(*error) return out[0].(eth.Output), *out[1].(*error)
} }
...@@ -371,8 +371,8 @@ func (m *MockL2Source) ExpectCodeByHash(hash common.Hash, code []byte, err error ...@@ -371,8 +371,8 @@ func (m *MockL2Source) ExpectCodeByHash(hash common.Hash, code []byte, err error
m.Mock.On("CodeByHash", hash).Once().Return(code, &err) m.Mock.On("CodeByHash", hash).Once().Return(code, &err)
} }
func (m *MockL2Source) ExpectOutputByRoot(root common.Hash, output eth.Output, err error) { func (m *MockL2Source) ExpectOutputByRoot(blockHash common.Hash, output eth.Output, err error) {
m.Mock.On("OutputByRoot", root).Once().Return(output, &err) m.Mock.On("OutputByRoot", blockHash).Once().Return(output, &err)
} }
var _ hosttypes.L2Source = (*MockL2Source)(nil) var _ hosttypes.L2Source = (*MockL2Source)(nil)
...@@ -22,5 +22,5 @@ type L2Source interface { ...@@ -22,5 +22,5 @@ type L2Source interface {
InfoAndTxsByHash(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Transactions, error) InfoAndTxsByHash(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Transactions, error)
NodeByHash(ctx context.Context, hash common.Hash) ([]byte, error) NodeByHash(ctx context.Context, hash common.Hash) ([]byte, error)
CodeByHash(ctx context.Context, hash common.Hash) ([]byte, error) CodeByHash(ctx context.Context, hash common.Hash) ([]byte, error)
OutputByRoot(ctx context.Context, root common.Hash) (eth.Output, error) OutputByRoot(ctx context.Context, blockRoot common.Hash) (eth.Output, error)
} }
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