Commit 4ce2ad0f authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into clabby/ctb/local-preimage-key-support

parents a23ef477 f1359221
...@@ -9,6 +9,7 @@ example/bin ...@@ -9,6 +9,7 @@ example/bin
contracts/out contracts/out
state.json state.json
*.json *.json
*.json.gz
*.pprof *.pprof
*.out *.out
bin bin
package cmd package cmd
import ( import (
"compress/gzip"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"os" "os"
"strings"
) )
func loadJSON[X any](inputPath string) (*X, error) { func loadJSON[X any](inputPath string) (*X, error) {
if inputPath == "" { if inputPath == "" {
return nil, errors.New("no path specified") return nil, errors.New("no path specified")
} }
var f io.ReadCloser
f, err := os.OpenFile(inputPath, os.O_RDONLY, 0) f, err := os.OpenFile(inputPath, os.O_RDONLY, 0)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to open file %q: %w", inputPath, err) return nil, fmt.Errorf("failed to open file %q: %w", inputPath, err)
} }
defer f.Close() defer f.Close()
if isGzip(inputPath) {
f, err = gzip.NewReader(f)
if err != nil {
return nil, fmt.Errorf("create gzip reader: %w", err)
}
defer f.Close()
}
var state X var state X
if err := json.NewDecoder(f).Decode(&state); err != nil { if err := json.NewDecoder(f).Decode(&state); err != nil {
return nil, fmt.Errorf("failed to decode file %q: %w", inputPath, err) return nil, fmt.Errorf("failed to decode file %q: %w", inputPath, err)
...@@ -33,6 +43,11 @@ func writeJSON[X any](outputPath string, value X, outIfEmpty bool) error { ...@@ -33,6 +43,11 @@ func writeJSON[X any](outputPath string, value X, outIfEmpty bool) error {
} }
defer f.Close() defer f.Close()
out = f out = f
if isGzip(outputPath) {
g := gzip.NewWriter(f)
defer g.Close()
out = g
}
} else if outIfEmpty { } else if outIfEmpty {
out = os.Stdout out = os.Stdout
} else { } else {
...@@ -48,3 +63,7 @@ func writeJSON[X any](outputPath string, value X, outIfEmpty bool) error { ...@@ -48,3 +63,7 @@ func writeJSON[X any](outputPath string, value X, outIfEmpty bool) error {
} }
return nil return nil
} }
func isGzip(path string) bool {
return strings.HasSuffix(path, ".gz")
}
package cmd
import (
"encoding/json"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
)
func TestRoundTripJSON(t *testing.T) {
dir := t.TempDir()
file := filepath.Join(dir, "test.json")
data := &jsonTestData{A: "yay", B: 3}
err := writeJSON(file, data, false)
require.NoError(t, err)
// Confirm the file is uncompressed
fileContent, err := os.ReadFile(file)
require.NoError(t, err)
err = json.Unmarshal(fileContent, &jsonTestData{})
require.NoError(t, err)
var result *jsonTestData
result, err = loadJSON[jsonTestData](file)
require.NoError(t, err)
require.EqualValues(t, data, result)
}
func TestRoundTripJSONWithGzip(t *testing.T) {
dir := t.TempDir()
file := filepath.Join(dir, "test.json.gz")
data := &jsonTestData{A: "yay", B: 3}
err := writeJSON(file, data, false)
require.NoError(t, err)
// Confirm the file isn't raw JSON
fileContent, err := os.ReadFile(file)
require.NoError(t, err)
err = json.Unmarshal(fileContent, &jsonTestData{})
require.Error(t, err, "should not be able to decode without decompressing")
var result *jsonTestData
result, err = loadJSON[jsonTestData](file)
require.NoError(t, err)
require.EqualValues(t, data, result)
}
type jsonTestData struct {
A string `json:"a"`
B int `json:"b"`
}
...@@ -16,6 +16,7 @@ import ( ...@@ -16,6 +16,7 @@ import (
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum-optimism/optimism/op-node/rollup/sync"
"github.com/ethereum-optimism/optimism/op-node/testlog" "github.com/ethereum-optimism/optimism/op-node/testlog"
) )
...@@ -30,7 +31,7 @@ func TestBatcher(gt *testing.T) { ...@@ -30,7 +31,7 @@ func TestBatcher(gt *testing.T) {
sd := e2eutils.Setup(t, dp, defaultAlloc) sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlDebug) log := testlog.Logger(t, log.LvlDebug)
miner, seqEngine, sequencer := setupSequencerTest(t, sd, log) miner, seqEngine, sequencer := setupSequencerTest(t, sd, log)
verifEngine, verifier := setupVerifier(t, sd, log, miner.L1Client(t, sd.RollupCfg)) verifEngine, verifier := setupVerifier(t, sd, log, miner.L1Client(t, sd.RollupCfg), &sync.Config{})
rollupSeqCl := sequencer.RollupClient() rollupSeqCl := sequencer.RollupClient()
batcher := NewL2Batcher(log, sd.RollupCfg, &BatcherCfg{ batcher := NewL2Batcher(log, sd.RollupCfg, &BatcherCfg{
...@@ -268,7 +269,7 @@ func TestGarbageBatch(gt *testing.T) { ...@@ -268,7 +269,7 @@ func TestGarbageBatch(gt *testing.T) {
log := testlog.Logger(t, log.LvlError) log := testlog.Logger(t, log.LvlError)
miner, engine, sequencer := setupSequencerTest(t, sd, log) miner, engine, sequencer := setupSequencerTest(t, sd, log)
_, verifier := setupVerifier(t, sd, log, miner.L1Client(t, sd.RollupCfg)) _, verifier := setupVerifier(t, sd, log, miner.L1Client(t, sd.RollupCfg), &sync.Config{})
batcherCfg := &BatcherCfg{ batcherCfg := &BatcherCfg{
MinL1TxSize: 0, MinL1TxSize: 0,
...@@ -350,7 +351,7 @@ func TestExtendedTimeWithoutL1Batches(gt *testing.T) { ...@@ -350,7 +351,7 @@ func TestExtendedTimeWithoutL1Batches(gt *testing.T) {
log := testlog.Logger(t, log.LvlError) log := testlog.Logger(t, log.LvlError)
miner, engine, sequencer := setupSequencerTest(t, sd, log) miner, engine, sequencer := setupSequencerTest(t, sd, log)
_, verifier := setupVerifier(t, sd, log, miner.L1Client(t, sd.RollupCfg)) _, verifier := setupVerifier(t, sd, log, miner.L1Client(t, sd.RollupCfg), &sync.Config{})
batcher := NewL2Batcher(log, sd.RollupCfg, &BatcherCfg{ batcher := NewL2Batcher(log, sd.RollupCfg, &BatcherCfg{
MinL1TxSize: 0, MinL1TxSize: 0,
...@@ -407,7 +408,7 @@ func TestBigL2Txs(gt *testing.T) { ...@@ -407,7 +408,7 @@ func TestBigL2Txs(gt *testing.T) {
log := testlog.Logger(t, log.LvlInfo) log := testlog.Logger(t, log.LvlInfo)
miner, engine, sequencer := setupSequencerTest(t, sd, log) miner, engine, sequencer := setupSequencerTest(t, sd, log)
_, verifier := setupVerifier(t, sd, log, miner.L1Client(t, sd.RollupCfg)) _, verifier := setupVerifier(t, sd, log, miner.L1Client(t, sd.RollupCfg), &sync.Config{})
batcher := NewL2Batcher(log, sd.RollupCfg, &BatcherCfg{ batcher := NewL2Batcher(log, sd.RollupCfg, &BatcherCfg{
MinL1TxSize: 0, MinL1TxSize: 0,
......
...@@ -12,6 +12,7 @@ import ( ...@@ -12,6 +12,7 @@ import (
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum-optimism/optimism/op-node/rollup/driver" "github.com/ethereum-optimism/optimism/op-node/rollup/driver"
"github.com/ethereum-optimism/optimism/op-node/rollup/sync"
) )
// MockL1OriginSelector is a shim to override the origin as sequencer, so we can force it to stay on an older origin. // MockL1OriginSelector is a shim to override the origin as sequencer, so we can force it to stay on an older origin.
...@@ -40,7 +41,7 @@ type L2Sequencer struct { ...@@ -40,7 +41,7 @@ type L2Sequencer struct {
} }
func NewL2Sequencer(t Testing, log log.Logger, l1 derive.L1Fetcher, eng L2API, cfg *rollup.Config, seqConfDepth uint64) *L2Sequencer { func NewL2Sequencer(t Testing, log log.Logger, l1 derive.L1Fetcher, eng L2API, cfg *rollup.Config, seqConfDepth uint64) *L2Sequencer {
ver := NewL2Verifier(t, log, l1, eng, cfg) ver := NewL2Verifier(t, log, l1, eng, cfg, &sync.Config{})
attrBuilder := derive.NewFetchingAttributesBuilder(cfg, l1, eng) attrBuilder := derive.NewFetchingAttributesBuilder(cfg, l1, eng)
seqConfDepthL1 := driver.NewConfDepth(seqConfDepth, ver.l1State.L1Head, l1) seqConfDepthL1 := driver.NewConfDepth(seqConfDepth, ver.l1State.L1Head, l1)
l1OriginSelector := &MockL1OriginSelector{ l1OriginSelector := &MockL1OriginSelector{
......
...@@ -56,9 +56,9 @@ type L2API interface { ...@@ -56,9 +56,9 @@ type L2API interface {
GetProof(ctx context.Context, address common.Address, storage []common.Hash, blockTag string) (*eth.AccountResult, error) GetProof(ctx context.Context, address common.Address, storage []common.Hash, blockTag string) (*eth.AccountResult, error)
} }
func NewL2Verifier(t Testing, log log.Logger, l1 derive.L1Fetcher, eng L2API, cfg *rollup.Config) *L2Verifier { func NewL2Verifier(t Testing, log log.Logger, l1 derive.L1Fetcher, eng L2API, cfg *rollup.Config, syncCfg *sync.Config) *L2Verifier {
metrics := &testutils.TestDerivationMetrics{} metrics := &testutils.TestDerivationMetrics{}
pipeline := derive.NewDerivationPipeline(log, cfg, l1, eng, metrics) pipeline := derive.NewDerivationPipeline(log, cfg, l1, eng, metrics, syncCfg)
pipeline.Reset() pipeline.Reset()
rollupNode := &L2Verifier{ rollupNode := &L2Verifier{
...@@ -138,6 +138,10 @@ func (s *L2Verifier) L2Unsafe() eth.L2BlockRef { ...@@ -138,6 +138,10 @@ func (s *L2Verifier) L2Unsafe() eth.L2BlockRef {
return s.derivation.UnsafeL2Head() return s.derivation.UnsafeL2Head()
} }
func (s *L2Verifier) EngineSyncTarget() eth.L2BlockRef {
return s.derivation.EngineSyncTarget()
}
func (s *L2Verifier) SyncStatus() *eth.SyncStatus { func (s *L2Verifier) SyncStatus() *eth.SyncStatus {
return &eth.SyncStatus{ return &eth.SyncStatus{
CurrentL1: s.derivation.Origin(), CurrentL1: s.derivation.Origin(),
...@@ -149,6 +153,7 @@ func (s *L2Verifier) SyncStatus() *eth.SyncStatus { ...@@ -149,6 +153,7 @@ func (s *L2Verifier) SyncStatus() *eth.SyncStatus {
SafeL2: s.L2Safe(), SafeL2: s.L2Safe(),
FinalizedL2: s.L2Finalized(), FinalizedL2: s.L2Finalized(),
UnsafeL2SyncTarget: s.derivation.UnsafeL2SyncTarget(), UnsafeL2SyncTarget: s.derivation.UnsafeL2SyncTarget(),
EngineSyncTarget: s.EngineSyncTarget(),
} }
} }
...@@ -205,7 +210,7 @@ func (s *L2Verifier) ActL2PipelineStep(t Testing) { ...@@ -205,7 +210,7 @@ func (s *L2Verifier) ActL2PipelineStep(t Testing) {
s.l2PipelineIdle = false s.l2PipelineIdle = false
err := s.derivation.Step(t.Ctx()) err := s.derivation.Step(t.Ctx())
if err == io.EOF { if err == io.EOF || (err != nil && errors.Is(err, derive.EngineP2PSyncing)) {
s.l2PipelineIdle = true s.l2PipelineIdle = true
return return
} else if err != nil && errors.Is(err, derive.NotEnoughData) { } else if err != nil && errors.Is(err, derive.NotEnoughData) {
......
...@@ -9,21 +9,22 @@ import ( ...@@ -9,21 +9,22 @@ import (
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum-optimism/optimism/op-node/rollup/sync"
"github.com/ethereum-optimism/optimism/op-node/testlog" "github.com/ethereum-optimism/optimism/op-node/testlog"
) )
func setupVerifier(t Testing, sd *e2eutils.SetupData, log log.Logger, l1F derive.L1Fetcher) (*L2Engine, *L2Verifier) { func setupVerifier(t Testing, sd *e2eutils.SetupData, log log.Logger, l1F derive.L1Fetcher, syncCfg *sync.Config) (*L2Engine, *L2Verifier) {
jwtPath := e2eutils.WriteDefaultJWT(t) jwtPath := e2eutils.WriteDefaultJWT(t)
engine := NewL2Engine(t, log, sd.L2Cfg, sd.RollupCfg.Genesis.L1, jwtPath) engine := NewL2Engine(t, log, sd.L2Cfg, sd.RollupCfg.Genesis.L1, jwtPath)
engCl := engine.EngineClient(t, sd.RollupCfg) engCl := engine.EngineClient(t, sd.RollupCfg)
verifier := NewL2Verifier(t, log, l1F, engCl, sd.RollupCfg) verifier := NewL2Verifier(t, log, l1F, engCl, sd.RollupCfg, syncCfg)
return engine, verifier return engine, verifier
} }
func setupVerifierOnlyTest(t Testing, sd *e2eutils.SetupData, log log.Logger) (*L1Miner, *L2Engine, *L2Verifier) { func setupVerifierOnlyTest(t Testing, sd *e2eutils.SetupData, log log.Logger) (*L1Miner, *L2Engine, *L2Verifier) {
miner := NewL1Miner(t, log, sd.L1Cfg) miner := NewL1Miner(t, log, sd.L1Cfg)
l1Cl := miner.L1Client(t, sd.RollupCfg) l1Cl := miner.L1Client(t, sd.RollupCfg)
engine, verifier := setupVerifier(t, sd, log, l1Cl) engine, verifier := setupVerifier(t, sd, log, l1Cl, &sync.Config{})
return miner, engine, verifier return miner, engine, verifier
} }
......
...@@ -16,6 +16,7 @@ import ( ...@@ -16,6 +16,7 @@ import (
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-node/client" "github.com/ethereum-optimism/optimism/op-node/client"
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup/sync"
"github.com/ethereum-optimism/optimism/op-node/sources" "github.com/ethereum-optimism/optimism/op-node/sources"
"github.com/ethereum-optimism/optimism/op-node/testlog" "github.com/ethereum-optimism/optimism/op-node/testlog"
) )
...@@ -33,7 +34,7 @@ func setupReorgTestActors(t Testing, dp *e2eutils.DeployParams, sd *e2eutils.Set ...@@ -33,7 +34,7 @@ func setupReorgTestActors(t Testing, dp *e2eutils.DeployParams, sd *e2eutils.Set
miner, seqEngine, sequencer := setupSequencerTest(t, sd, log) miner, seqEngine, sequencer := setupSequencerTest(t, sd, log)
miner.ActL1SetFeeRecipient(common.Address{'A'}) miner.ActL1SetFeeRecipient(common.Address{'A'})
sequencer.ActL2PipelineFull(t) sequencer.ActL2PipelineFull(t)
verifEngine, verifier := setupVerifier(t, sd, log, miner.L1Client(t, sd.RollupCfg)) verifEngine, verifier := setupVerifier(t, sd, log, miner.L1Client(t, sd.RollupCfg), &sync.Config{})
rollupSeqCl := sequencer.RollupClient() rollupSeqCl := sequencer.RollupClient()
batcher := NewL2Batcher(log, sd.RollupCfg, &BatcherCfg{ batcher := NewL2Batcher(log, sd.RollupCfg, &BatcherCfg{
MinL1TxSize: 0, MinL1TxSize: 0,
......
...@@ -6,6 +6,9 @@ import ( ...@@ -6,6 +6,9 @@ import (
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup/sync"
"github.com/ethereum-optimism/optimism/op-node/sources"
"github.com/ethereum-optimism/optimism/op-node/testlog" "github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
...@@ -92,3 +95,74 @@ func TestFinalizeWhileSyncing(gt *testing.T) { ...@@ -92,3 +95,74 @@ func TestFinalizeWhileSyncing(gt *testing.T) {
// Verify the verifier finalized something new // Verify the verifier finalized something new
require.Less(t, verifierStartStatus.FinalizedL2.Number, verifier.SyncStatus().FinalizedL2.Number, "verifier finalized L2 blocks during sync") require.Less(t, verifierStartStatus.FinalizedL2.Number, verifier.SyncStatus().FinalizedL2.Number, "verifier finalized L2 blocks during sync")
} }
func TestUnsafeSync(gt *testing.T) {
t := NewDefaultTesting(gt)
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlInfo)
sd, _, _, sequencer, seqEng, verifier, _, _ := setupReorgTestActors(t, dp, sd, log)
seqEngCl, err := sources.NewEngineClient(seqEng.RPCClient(), log, nil, sources.EngineClientDefaultConfig(sd.RollupCfg))
require.NoError(t, err)
sequencer.ActL2PipelineFull(t)
verifier.ActL2PipelineFull(t)
for i := 0; i < 10; i++ {
// Build a L2 block
sequencer.ActL2StartBlock(t)
sequencer.ActL2EndBlock(t)
// Notify new L2 block to verifier by unsafe gossip
seqHead, err := seqEngCl.PayloadByLabel(t.Ctx(), eth.Unsafe)
require.NoError(t, err)
verifier.ActL2UnsafeGossipReceive(seqHead)(t)
// Handle unsafe payload
verifier.ActL2PipelineFull(t)
// Verifier must advance its unsafe head and engine sync target.
require.Equal(t, sequencer.L2Unsafe().Hash, verifier.L2Unsafe().Hash)
// Check engine sync target updated.
require.Equal(t, sequencer.L2Unsafe().Hash, sequencer.EngineSyncTarget().Hash)
require.Equal(t, verifier.L2Unsafe().Hash, verifier.EngineSyncTarget().Hash)
}
}
func TestEngineP2PSync(gt *testing.T) {
t := NewDefaultTesting(gt)
dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams)
sd := e2eutils.Setup(t, dp, defaultAlloc)
log := testlog.Logger(t, log.LvlInfo)
miner, seqEng, sequencer := setupSequencerTest(t, sd, log)
// Enable engine P2P sync
_, verifier := setupVerifier(t, sd, log, miner.L1Client(t, sd.RollupCfg), &sync.Config{EngineSync: true})
seqEngCl, err := sources.NewEngineClient(seqEng.RPCClient(), log, nil, sources.EngineClientDefaultConfig(sd.RollupCfg))
require.NoError(t, err)
sequencer.ActL2PipelineFull(t)
verifier.ActL2PipelineFull(t)
verifierUnsafeHead := verifier.L2Unsafe()
// Build a L2 block. This block will not be gossiped to verifier, so verifier can not advance chain by itself.
sequencer.ActL2StartBlock(t)
sequencer.ActL2EndBlock(t)
for i := 0; i < 10; i++ {
// Build a L2 block
sequencer.ActL2StartBlock(t)
sequencer.ActL2EndBlock(t)
// Notify new L2 block to verifier by unsafe gossip
seqHead, err := seqEngCl.PayloadByLabel(t.Ctx(), eth.Unsafe)
require.NoError(t, err)
verifier.ActL2UnsafeGossipReceive(seqHead)(t)
// Handle unsafe payload
verifier.ActL2PipelineFull(t)
// Verifier must advance only engine sync target.
require.NotEqual(t, sequencer.L2Unsafe().Hash, verifier.L2Unsafe().Hash)
require.NotEqual(t, verifier.L2Unsafe().Hash, verifier.EngineSyncTarget().Hash)
require.Equal(t, verifier.L2Unsafe().Hash, verifierUnsafeHead.Hash)
require.Equal(t, sequencer.L2Unsafe().Hash, verifier.EngineSyncTarget().Hash)
}
}
...@@ -15,6 +15,7 @@ import ( ...@@ -15,6 +15,7 @@ import (
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum-optimism/optimism/op-node/rollup/sync"
"github.com/ethereum-optimism/optimism/op-node/testlog" "github.com/ethereum-optimism/optimism/op-node/testlog"
) )
...@@ -29,7 +30,7 @@ func TestBatcherKeyRotation(gt *testing.T) { ...@@ -29,7 +30,7 @@ func TestBatcherKeyRotation(gt *testing.T) {
miner, seqEngine, sequencer := setupSequencerTest(t, sd, log) miner, seqEngine, sequencer := setupSequencerTest(t, sd, log)
miner.ActL1SetFeeRecipient(common.Address{'A'}) miner.ActL1SetFeeRecipient(common.Address{'A'})
sequencer.ActL2PipelineFull(t) sequencer.ActL2PipelineFull(t)
_, verifier := setupVerifier(t, sd, log, miner.L1Client(t, sd.RollupCfg)) _, verifier := setupVerifier(t, sd, log, miner.L1Client(t, sd.RollupCfg), &sync.Config{})
rollupSeqCl := sequencer.RollupClient() rollupSeqCl := sequencer.RollupClient()
// the default batcher // the default batcher
...@@ -358,7 +359,7 @@ func TestGasLimitChange(gt *testing.T) { ...@@ -358,7 +359,7 @@ func TestGasLimitChange(gt *testing.T) {
miner.ActL1IncludeTx(dp.Addresses.Batcher)(t) miner.ActL1IncludeTx(dp.Addresses.Batcher)(t)
miner.ActL1EndBlock(t) miner.ActL1EndBlock(t)
_, verifier := setupVerifier(t, sd, log, miner.L1Client(t, sd.RollupCfg)) _, verifier := setupVerifier(t, sd, log, miner.L1Client(t, sd.RollupCfg), &sync.Config{})
verifier.ActL2PipelineFull(t) verifier.ActL2PipelineFull(t)
require.Equal(t, sequencer.L2Unsafe(), verifier.L2Safe(), "verifier stays in sync, even with gaslimit changes") require.Equal(t, sequencer.L2Unsafe(), verifier.L2Safe(), "verifier stays in sync, even with gaslimit changes")
......
...@@ -35,4 +35,7 @@ type SyncStatus struct { ...@@ -35,4 +35,7 @@ type SyncStatus struct {
// UnsafeL2SyncTarget points to the first unprocessed unsafe L2 block. // UnsafeL2SyncTarget points to the first unprocessed unsafe L2 block.
// It may be zeroed if there is no targeted block. // It may be zeroed if there is no targeted block.
UnsafeL2SyncTarget L2BlockRef `json:"queued_unsafe_l2"` UnsafeL2SyncTarget L2BlockRef `json:"queued_unsafe_l2"`
// EngineSyncTarget points to the L2 block that the execution engine is syncing to.
// If it is ahead from UnsafeL2, the engine is in progress of P2P sync.
EngineSyncTarget L2BlockRef `json:"engine_sync_target"`
} }
...@@ -214,6 +214,21 @@ var ( ...@@ -214,6 +214,21 @@ var (
EnvVars: prefixEnvVars("L2_BACKUP_UNSAFE_SYNC_RPC_TRUST_RPC"), EnvVars: prefixEnvVars("L2_BACKUP_UNSAFE_SYNC_RPC_TRUST_RPC"),
Required: false, Required: false,
} }
L2EngineSyncEnabled = &cli.BoolFlag{
Name: "l2.engine-sync",
Usage: "Enables or disables execution engine P2P sync",
EnvVars: prefixEnvVars("L2_ENGINE_SYNC_ENABLED"),
Required: false,
Value: false,
}
SkipSyncStartCheck = &cli.BoolFlag{
Name: "l2.skip-sync-start-check",
Usage: "Skip sanity check of consistency of L1 origins of the unsafe L2 blocks when determining the sync-starting point. " +
"This defers the L1-origin verification, and is recommended to use in when utilizing l2.engine-sync",
EnvVars: prefixEnvVars("L2_SKIP_SYNC_START_CHECK"),
Required: false,
Value: false,
}
) )
var requiredFlags = []cli.Flag{ var requiredFlags = []cli.Flag{
...@@ -252,6 +267,8 @@ var optionalFlags = []cli.Flag{ ...@@ -252,6 +267,8 @@ var optionalFlags = []cli.Flag{
HeartbeatURLFlag, HeartbeatURLFlag,
BackupL2UnsafeSyncRPC, BackupL2UnsafeSyncRPC,
BackupL2UnsafeSyncRPCTrustRPC, BackupL2UnsafeSyncRPCTrustRPC,
L2EngineSyncEnabled,
SkipSyncStartCheck,
} }
// Flags contains the list of configuration options available to the binary. // Flags contains the list of configuration options available to the binary.
......
...@@ -30,6 +30,7 @@ var ( ...@@ -30,6 +30,7 @@ var (
Name: "p2p.scoring", Name: "p2p.scoring",
Usage: "Sets the peer scoring strategy for the P2P stack. Can be one of: none or light.", Usage: "Sets the peer scoring strategy for the P2P stack. Can be one of: none or light.",
Required: false, Required: false,
Value: "light",
EnvVars: p2pEnv("PEER_SCORING"), EnvVars: p2pEnv("PEER_SCORING"),
} }
PeerScoring = &cli.StringFlag{ PeerScoring = &cli.StringFlag{
......
...@@ -10,6 +10,7 @@ import ( ...@@ -10,6 +10,7 @@ import (
"github.com/ethereum-optimism/optimism/op-node/p2p" "github.com/ethereum-optimism/optimism/op-node/p2p"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/rollup/driver" "github.com/ethereum-optimism/optimism/op-node/rollup/driver"
"github.com/ethereum-optimism/optimism/op-node/rollup/sync"
oppprof "github.com/ethereum-optimism/optimism/op-service/pprof" oppprof "github.com/ethereum-optimism/optimism/op-service/pprof"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
...@@ -43,6 +44,8 @@ type Config struct { ...@@ -43,6 +44,8 @@ type Config struct {
// Optional // Optional
Tracer Tracer Tracer Tracer
Heartbeat HeartbeatConfig Heartbeat HeartbeatConfig
Sync sync.Config
} }
type RPCConfig struct { type RPCConfig struct {
......
...@@ -199,7 +199,7 @@ func (n *OpNode) initL2(ctx context.Context, cfg *Config, snapshotLog log.Logger ...@@ -199,7 +199,7 @@ func (n *OpNode) initL2(ctx context.Context, cfg *Config, snapshotLog log.Logger
return err return err
} }
n.l2Driver = driver.NewDriver(&cfg.Driver, &cfg.Rollup, n.l2Source, n.l1Source, n, n, n.log, snapshotLog, n.metrics, cfg.ConfigPersistence) n.l2Driver = driver.NewDriver(&cfg.Driver, &cfg.Rollup, n.l2Source, n.l1Source, n, n, n.log, snapshotLog, n.metrics, cfg.ConfigPersistence, &cfg.Sync)
return nil return nil
} }
......
...@@ -167,6 +167,7 @@ func randomSyncStatus(rng *rand.Rand) *eth.SyncStatus { ...@@ -167,6 +167,7 @@ func randomSyncStatus(rng *rand.Rand) *eth.SyncStatus {
SafeL2: testutils.RandomL2BlockRef(rng), SafeL2: testutils.RandomL2BlockRef(rng),
FinalizedL2: testutils.RandomL2BlockRef(rng), FinalizedL2: testutils.RandomL2BlockRef(rng),
UnsafeL2SyncTarget: testutils.RandomL2BlockRef(rng), UnsafeL2SyncTarget: testutils.RandomL2BlockRef(rng),
EngineSyncTarget: testutils.RandomL2BlockRef(rng),
} }
} }
......
This diff is collapsed.
...@@ -17,6 +17,7 @@ import ( ...@@ -17,6 +17,7 @@ import (
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/metrics" "github.com/ethereum-optimism/optimism/op-node/metrics"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/rollup/sync"
"github.com/ethereum-optimism/optimism/op-node/testlog" "github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum-optimism/optimism/op-node/testutils" "github.com/ethereum-optimism/optimism/op-node/testutils"
) )
...@@ -246,7 +247,7 @@ func TestEngineQueue_Finalize(t *testing.T) { ...@@ -246,7 +247,7 @@ func TestEngineQueue_Finalize(t *testing.T) {
prev := &fakeAttributesQueue{} prev := &fakeAttributesQueue{}
eq := NewEngineQueue(logger, cfg, eng, metrics, prev, l1F) eq := NewEngineQueue(logger, cfg, eng, metrics, prev, l1F, &sync.Config{})
require.ErrorIs(t, eq.Reset(context.Background(), eth.L1BlockRef{}, eth.SystemConfig{}), io.EOF) require.ErrorIs(t, eq.Reset(context.Background(), eth.L1BlockRef{}, eth.SystemConfig{}), io.EOF)
require.Equal(t, refB1, eq.SafeL2Head(), "L2 reset should go back to sequence window ago: blocks with origin E and D are not safe until we reconcile, C is extra, and B1 is the end we look for") require.Equal(t, refB1, eq.SafeL2Head(), "L2 reset should go back to sequence window ago: blocks with origin E and D are not safe until we reconcile, C is extra, and B1 is the end we look for")
...@@ -480,7 +481,7 @@ func TestEngineQueue_ResetWhenUnsafeOriginNotCanonical(t *testing.T) { ...@@ -480,7 +481,7 @@ func TestEngineQueue_ResetWhenUnsafeOriginNotCanonical(t *testing.T) {
prev := &fakeAttributesQueue{origin: refE} prev := &fakeAttributesQueue{origin: refE}
eq := NewEngineQueue(logger, cfg, eng, metrics, prev, l1F) eq := NewEngineQueue(logger, cfg, eng, metrics, prev, l1F, &sync.Config{})
require.ErrorIs(t, eq.Reset(context.Background(), eth.L1BlockRef{}, eth.SystemConfig{}), io.EOF) require.ErrorIs(t, eq.Reset(context.Background(), eth.L1BlockRef{}, eth.SystemConfig{}), io.EOF)
require.Equal(t, refB1, eq.SafeL2Head(), "L2 reset should go back to sequence window ago: blocks with origin E and D are not safe until we reconcile, C is extra, and B1 is the end we look for") require.Equal(t, refB1, eq.SafeL2Head(), "L2 reset should go back to sequence window ago: blocks with origin E and D are not safe until we reconcile, C is extra, and B1 is the end we look for")
...@@ -811,7 +812,7 @@ func TestVerifyNewL1Origin(t *testing.T) { ...@@ -811,7 +812,7 @@ func TestVerifyNewL1Origin(t *testing.T) {
}, nil) }, nil)
prev := &fakeAttributesQueue{origin: refE} prev := &fakeAttributesQueue{origin: refE}
eq := NewEngineQueue(logger, cfg, eng, metrics, prev, l1F) eq := NewEngineQueue(logger, cfg, eng, metrics, prev, l1F, &sync.Config{})
require.ErrorIs(t, eq.Reset(context.Background(), eth.L1BlockRef{}, eth.SystemConfig{}), io.EOF) require.ErrorIs(t, eq.Reset(context.Background(), eth.L1BlockRef{}, eth.SystemConfig{}), io.EOF)
require.Equal(t, refB1, eq.SafeL2Head(), "L2 reset should go back to sequence window ago: blocks with origin E and D are not safe until we reconcile, C is extra, and B1 is the end we look for") require.Equal(t, refB1, eq.SafeL2Head(), "L2 reset should go back to sequence window ago: blocks with origin E and D are not safe until we reconcile, C is extra, and B1 is the end we look for")
...@@ -909,7 +910,7 @@ func TestBlockBuildingRace(t *testing.T) { ...@@ -909,7 +910,7 @@ func TestBlockBuildingRace(t *testing.T) {
} }
prev := &fakeAttributesQueue{origin: refA, attrs: attrs} prev := &fakeAttributesQueue{origin: refA, attrs: attrs}
eq := NewEngineQueue(logger, cfg, eng, metrics, prev, l1F) eq := NewEngineQueue(logger, cfg, eng, metrics, prev, l1F, &sync.Config{})
require.ErrorIs(t, eq.Reset(context.Background(), eth.L1BlockRef{}, eth.SystemConfig{}), io.EOF) require.ErrorIs(t, eq.Reset(context.Background(), eth.L1BlockRef{}, eth.SystemConfig{}), io.EOF)
id := eth.PayloadID{0xff} id := eth.PayloadID{0xff}
...@@ -1079,8 +1080,9 @@ func TestResetLoop(t *testing.T) { ...@@ -1079,8 +1080,9 @@ func TestResetLoop(t *testing.T) {
prev := &fakeAttributesQueue{origin: refA, attrs: attrs} prev := &fakeAttributesQueue{origin: refA, attrs: attrs}
eq := NewEngineQueue(logger, cfg, eng, metrics.NoopMetrics, prev, l1F) eq := NewEngineQueue(logger, cfg, eng, metrics.NoopMetrics, prev, l1F, &sync.Config{})
eq.unsafeHead = refA2 eq.unsafeHead = refA2
eq.engineSyncTarget = refA2
eq.safeHead = refA1 eq.safeHead = refA1
eq.finalized = refA0 eq.finalized = refA0
...@@ -1176,7 +1178,7 @@ func TestEngineQueue_StepPopOlderUnsafe(t *testing.T) { ...@@ -1176,7 +1178,7 @@ func TestEngineQueue_StepPopOlderUnsafe(t *testing.T) {
prev := &fakeAttributesQueue{origin: refA} prev := &fakeAttributesQueue{origin: refA}
eq := NewEngineQueue(logger, cfg, eng, metrics.NoopMetrics, prev, l1F) eq := NewEngineQueue(logger, cfg, eng, metrics.NoopMetrics, prev, l1F, &sync.Config{})
eq.unsafeHead = refA2 eq.unsafeHead = refA2
eq.safeHead = refA0 eq.safeHead = refA0
eq.finalized = refA0 eq.finalized = refA0
......
...@@ -96,3 +96,6 @@ var ErrCritical = NewCriticalError(nil) ...@@ -96,3 +96,6 @@ var ErrCritical = NewCriticalError(nil)
// NotEnoughData implies that the function currently does not have enough data to progress // NotEnoughData implies that the function currently does not have enough data to progress
// but if it is retried enough times, it will eventually return a real value or io.EOF // but if it is retried enough times, it will eventually return a real value or io.EOF
var NotEnoughData = errors.New("not enough data") var NotEnoughData = errors.New("not enough data")
// EngineP2PSyncing implies that the execution engine is currently in progress of P2P sync.
var EngineP2PSyncing = errors.New("engine is P2P syncing")
...@@ -2,6 +2,7 @@ package derive ...@@ -2,6 +2,7 @@ package derive
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"io" "io"
...@@ -9,6 +10,7 @@ import ( ...@@ -9,6 +10,7 @@ import (
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/rollup/sync"
) )
type Metrics interface { type Metrics interface {
...@@ -46,6 +48,7 @@ type EngineQueueStage interface { ...@@ -46,6 +48,7 @@ type EngineQueueStage interface {
Finalized() eth.L2BlockRef Finalized() eth.L2BlockRef
UnsafeL2Head() eth.L2BlockRef UnsafeL2Head() eth.L2BlockRef
SafeL2Head() eth.L2BlockRef SafeL2Head() eth.L2BlockRef
EngineSyncTarget() eth.L2BlockRef
Origin() eth.L1BlockRef Origin() eth.L1BlockRef
SystemConfig() eth.SystemConfig SystemConfig() eth.SystemConfig
SetUnsafeHead(head eth.L2BlockRef) SetUnsafeHead(head eth.L2BlockRef)
...@@ -75,7 +78,7 @@ type DerivationPipeline struct { ...@@ -75,7 +78,7 @@ type DerivationPipeline struct {
} }
// NewDerivationPipeline creates a derivation pipeline, which should be reset before use. // NewDerivationPipeline creates a derivation pipeline, which should be reset before use.
func NewDerivationPipeline(log log.Logger, cfg *rollup.Config, l1Fetcher L1Fetcher, engine Engine, metrics Metrics) *DerivationPipeline { func NewDerivationPipeline(log log.Logger, cfg *rollup.Config, l1Fetcher L1Fetcher, engine Engine, metrics Metrics, syncCfg *sync.Config) *DerivationPipeline {
// Pull stages // Pull stages
l1Traversal := NewL1Traversal(log, cfg, l1Fetcher) l1Traversal := NewL1Traversal(log, cfg, l1Fetcher)
...@@ -89,7 +92,7 @@ func NewDerivationPipeline(log log.Logger, cfg *rollup.Config, l1Fetcher L1Fetch ...@@ -89,7 +92,7 @@ func NewDerivationPipeline(log log.Logger, cfg *rollup.Config, l1Fetcher L1Fetch
attributesQueue := NewAttributesQueue(log, cfg, attrBuilder, batchQueue) attributesQueue := NewAttributesQueue(log, cfg, attrBuilder, batchQueue)
// Step stages // Step stages
eng := NewEngineQueue(log, cfg, engine, metrics, attributesQueue, l1Fetcher) eng := NewEngineQueue(log, cfg, engine, metrics, attributesQueue, l1Fetcher, syncCfg)
// Reset from engine queue then up from L1 Traversal. The stages do not talk to each other during // Reset from engine queue then up from L1 Traversal. The stages do not talk to each other during
// the reset, but after the engine queue, this is the order in which the stages could talk to each other. // the reset, but after the engine queue, this is the order in which the stages could talk to each other.
...@@ -147,6 +150,10 @@ func (dp *DerivationPipeline) UnsafeL2Head() eth.L2BlockRef { ...@@ -147,6 +150,10 @@ func (dp *DerivationPipeline) UnsafeL2Head() eth.L2BlockRef {
return dp.eng.UnsafeL2Head() return dp.eng.UnsafeL2Head()
} }
func (dp *DerivationPipeline) EngineSyncTarget() eth.L2BlockRef {
return dp.eng.EngineSyncTarget()
}
func (dp *DerivationPipeline) StartPayload(ctx context.Context, parent eth.L2BlockRef, attrs *eth.PayloadAttributes, updateSafe bool) (errType BlockInsertionErrType, err error) { func (dp *DerivationPipeline) StartPayload(ctx context.Context, parent eth.L2BlockRef, attrs *eth.PayloadAttributes, updateSafe bool) (errType BlockInsertionErrType, err error) {
return dp.eng.StartPayload(ctx, parent, attrs, updateSafe) return dp.eng.StartPayload(ctx, parent, attrs, updateSafe)
} }
...@@ -199,6 +206,8 @@ func (dp *DerivationPipeline) Step(ctx context.Context) error { ...@@ -199,6 +206,8 @@ func (dp *DerivationPipeline) Step(ctx context.Context) error {
if err := dp.eng.Step(ctx); err == io.EOF { if err := dp.eng.Step(ctx); err == io.EOF {
// If every stage has returned io.EOF, try to advance the L1 Origin // If every stage has returned io.EOF, try to advance the L1 Origin
return dp.traversal.AdvanceL1Block(ctx) return dp.traversal.AdvanceL1Block(ctx)
} else if errors.Is(err, EngineP2PSyncing) {
return err
} else if err != nil { } else if err != nil {
return fmt.Errorf("engine stage failed: %w", err) return fmt.Errorf("engine stage failed: %w", err)
} else { } else {
......
...@@ -10,6 +10,7 @@ import ( ...@@ -10,6 +10,7 @@ import (
"github.com/ethereum-optimism/optimism/op-node/eth" "github.com/ethereum-optimism/optimism/op-node/eth"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum-optimism/optimism/op-node/rollup/sync"
) )
type Metrics interface { type Metrics interface {
...@@ -58,6 +59,7 @@ type DerivationPipeline interface { ...@@ -58,6 +59,7 @@ type DerivationPipeline interface {
UnsafeL2Head() eth.L2BlockRef UnsafeL2Head() eth.L2BlockRef
Origin() eth.L1BlockRef Origin() eth.L1BlockRef
EngineReady() bool EngineReady() bool
EngineSyncTarget() eth.L2BlockRef
} }
type L1StateIface interface { type L1StateIface interface {
...@@ -108,13 +110,13 @@ type SequencerStateListener interface { ...@@ -108,13 +110,13 @@ type SequencerStateListener interface {
} }
// NewDriver composes an events handler that tracks L1 state, triggers L2 derivation, and optionally sequences new L2 blocks. // NewDriver composes an events handler that tracks L1 state, triggers L2 derivation, and optionally sequences new L2 blocks.
func NewDriver(driverCfg *Config, cfg *rollup.Config, l2 L2Chain, l1 L1Chain, altSync AltSync, network Network, log log.Logger, snapshotLog log.Logger, metrics Metrics, sequencerStateListener SequencerStateListener) *Driver { func NewDriver(driverCfg *Config, cfg *rollup.Config, l2 L2Chain, l1 L1Chain, altSync AltSync, network Network, log log.Logger, snapshotLog log.Logger, metrics Metrics, sequencerStateListener SequencerStateListener, syncCfg *sync.Config) *Driver {
l1 = NewMeteredL1Fetcher(l1, metrics) l1 = NewMeteredL1Fetcher(l1, metrics)
l1State := NewL1State(log, metrics) l1State := NewL1State(log, metrics)
sequencerConfDepth := NewConfDepth(driverCfg.SequencerConfDepth, l1State.L1Head, l1) sequencerConfDepth := NewConfDepth(driverCfg.SequencerConfDepth, l1State.L1Head, l1)
findL1Origin := NewL1OriginSelector(log, cfg, sequencerConfDepth) findL1Origin := NewL1OriginSelector(log, cfg, sequencerConfDepth)
verifConfDepth := NewConfDepth(driverCfg.VerifierConfDepth, l1State.L1Head, l1) verifConfDepth := NewConfDepth(driverCfg.VerifierConfDepth, l1State.L1Head, l1)
derivationPipeline := derive.NewDerivationPipeline(log, cfg, verifConfDepth, l2, metrics) derivationPipeline := derive.NewDerivationPipeline(log, cfg, verifConfDepth, l2, metrics, syncCfg)
attrBuilder := derive.NewFetchingAttributesBuilder(cfg, l1, l2) attrBuilder := derive.NewFetchingAttributesBuilder(cfg, l1, l2)
engine := derivationPipeline engine := derivationPipeline
meteredEngine := NewMeteredEngine(cfg, engine, metrics, log) meteredEngine := NewMeteredEngine(cfg, engine, metrics, log)
......
...@@ -314,7 +314,12 @@ func (s *Driver) eventLoop() { ...@@ -314,7 +314,12 @@ func (s *Driver) eventLoop() {
err := s.derivation.Step(context.Background()) err := s.derivation.Step(context.Background())
stepAttempts += 1 // count as attempt by default. We reset to 0 if we are making healthy progress. stepAttempts += 1 // count as attempt by default. We reset to 0 if we are making healthy progress.
if err == io.EOF { if err == io.EOF {
s.log.Debug("Derivation process went idle", "progress", s.derivation.Origin()) s.log.Debug("Derivation process went idle", "progress", s.derivation.Origin(), "err", err)
stepAttempts = 0
s.metrics.SetDerivationIdle(true)
continue
} else if err != nil && errors.Is(err, derive.EngineP2PSyncing) {
s.log.Debug("Derivation process went idle because the engine is syncing", "progress", s.derivation.Origin(), "sync_target", s.derivation.EngineSyncTarget(), "err", err)
stepAttempts = 0 stepAttempts = 0
s.metrics.SetDerivationIdle(true) s.metrics.SetDerivationIdle(true)
continue continue
...@@ -474,6 +479,7 @@ func (s *Driver) syncStatus() *eth.SyncStatus { ...@@ -474,6 +479,7 @@ func (s *Driver) syncStatus() *eth.SyncStatus {
SafeL2: s.derivation.SafeL2Head(), SafeL2: s.derivation.SafeL2Head(),
FinalizedL2: s.derivation.Finalized(), FinalizedL2: s.derivation.Finalized(),
UnsafeL2SyncTarget: s.derivation.UnsafeL2SyncTarget(), UnsafeL2SyncTarget: s.derivation.UnsafeL2SyncTarget(),
EngineSyncTarget: s.derivation.EngineSyncTarget(),
} }
} }
......
package sync
type Config struct {
// EngineSync is true when the EngineQueue can trigger execution engine P2P sync.
EngineSync bool `json:"engine_sync"`
// SkipSyncStartCheck skip the sanity check of consistency of L1 origins of the unsafe L2 blocks when determining the sync-starting point. This defers the L1-origin verification, and is recommended to use in when utilizing l2.engine-sync
SkipSyncStartCheck bool `json:"skip_sync_start_check"`
}
...@@ -102,7 +102,7 @@ func currentHeads(ctx context.Context, cfg *rollup.Config, l2 L2Chain) (*FindHea ...@@ -102,7 +102,7 @@ func currentHeads(ctx context.Context, cfg *rollup.Config, l2 L2Chain) (*FindHea
// Plausible: meaning that the blockhash of the L2 block's L1 origin // Plausible: meaning that the blockhash of the L2 block's L1 origin
// (as reported in the L1 Attributes deposit within the L2 block) is not canonical at another height in the L1 chain, // (as reported in the L1 Attributes deposit within the L2 block) is not canonical at another height in the L1 chain,
// and the same holds for all its ancestors. // and the same holds for all its ancestors.
func FindL2Heads(ctx context.Context, cfg *rollup.Config, l1 L1Chain, l2 L2Chain, lgr log.Logger) (result *FindHeadsResult, err error) { func FindL2Heads(ctx context.Context, cfg *rollup.Config, l1 L1Chain, l2 L2Chain, lgr log.Logger, syncCfg *Config) (result *FindHeadsResult, err error) {
// Fetch current L2 forkchoice state // Fetch current L2 forkchoice state
result, err = currentHeads(ctx, cfg, l2) result, err = currentHeads(ctx, cfg, l2)
if err != nil { if err != nil {
...@@ -170,18 +170,18 @@ func FindL2Heads(ctx context.Context, cfg *rollup.Config, l1 L1Chain, l2 L2Chain ...@@ -170,18 +170,18 @@ func FindL2Heads(ctx context.Context, cfg *rollup.Config, l1 L1Chain, l2 L2Chain
if (n.Number == result.Finalized.Number) && (n.Hash != result.Finalized.Hash) { if (n.Number == result.Finalized.Number) && (n.Hash != result.Finalized.Hash) {
return nil, fmt.Errorf("%w: finalized %s, got: %s", ReorgFinalizedErr, result.Finalized, n) return nil, fmt.Errorf("%w: finalized %s, got: %s", ReorgFinalizedErr, result.Finalized, n)
} }
// Check we are not reorging L2 incredibly deep
if n.L1Origin.Number+(MaxReorgSeqWindows*cfg.SeqWindowSize) < prevUnsafe.L1Origin.Number {
// If the reorg depth is too large, something is fishy.
// This can legitimately happen if L1 goes down for a while. But in that case,
// restarting the L2 node with a bigger configured MaxReorgDepth is an acceptable
// stopgap solution.
return nil, fmt.Errorf("%w: traversed back to L2 block %s, but too deep compared to previous unsafe block %s", TooDeepReorgErr, n, prevUnsafe)
}
// If we don't have a usable unsafe head, then set it // If we don't have a usable unsafe head, then set it
if result.Unsafe == (eth.L2BlockRef{}) { if result.Unsafe == (eth.L2BlockRef{}) {
result.Unsafe = n result.Unsafe = n
// Check we are not reorging L2 incredibly deep
if n.L1Origin.Number+(MaxReorgSeqWindows*cfg.SeqWindowSize) < prevUnsafe.L1Origin.Number {
// If the reorg depth is too large, something is fishy.
// This can legitimately happen if L1 goes down for a while. But in that case,
// restarting the L2 node with a bigger configured MaxReorgDepth is an acceptable
// stopgap solution.
return nil, fmt.Errorf("%w: traversed back to L2 block %s, but too deep compared to previous unsafe block %s", TooDeepReorgErr, n, prevUnsafe)
}
} }
if ahead { if ahead {
...@@ -212,6 +212,11 @@ func FindL2Heads(ctx context.Context, cfg *rollup.Config, l1 L1Chain, l2 L2Chain ...@@ -212,6 +212,11 @@ func FindL2Heads(ctx context.Context, cfg *rollup.Config, l1 L1Chain, l2 L2Chain
return result, nil return result, nil
} }
if syncCfg.SkipSyncStartCheck && highestL2WithCanonicalL1Origin.Hash == n.Hash {
lgr.Info("Found highest L2 block with canonical L1 origin. Skip further sanity check and jump to the safe head")
n = result.Safe
continue
}
// Pull L2 parent for next iteration // Pull L2 parent for next iteration
parent, err := l2.L2BlockRefByHash(ctx, n.ParentHash) parent, err := l2.L2BlockRefByHash(ctx, n.ParentHash)
if err != nil { if err != nil {
......
...@@ -76,7 +76,7 @@ func (c *syncStartTestCase) Run(t *testing.T) { ...@@ -76,7 +76,7 @@ func (c *syncStartTestCase) Run(t *testing.T) {
} }
lgr := log.New() lgr := log.New()
lgr.SetHandler(log.DiscardHandler()) lgr.SetHandler(log.DiscardHandler())
result, err := FindL2Heads(context.Background(), cfg, chain, chain, lgr) result, err := FindL2Heads(context.Background(), cfg, chain, chain, lgr, &Config{})
if c.ExpectedErr != nil { if c.ExpectedErr != nil {
require.ErrorIs(t, err, c.ExpectedErr, "expected error") require.ErrorIs(t, err, c.ExpectedErr, "expected error")
return return
...@@ -286,6 +286,37 @@ func TestFindSyncStart(t *testing.T) { ...@@ -286,6 +286,37 @@ func TestFindSyncStart(t *testing.T) {
SafeL2Head: 'D', SafeL2Head: 'D',
ExpectedErr: WrongChainErr, ExpectedErr: WrongChainErr,
}, },
{
// FindL2Heads() keeps walking back to safe head after finding canonical unsafe head
// TooDeepReorgErr must not be raised
Name: "long traverse to safe head",
GenesisL1Num: 0,
L1: "abcdefgh",
L2: "ABCDEFGH",
NewL1: "abcdefgx",
PreFinalizedL2: 'B',
PreSafeL2: 'B',
GenesisL1: 'a',
GenesisL2: 'A',
UnsafeL2Head: 'G',
SeqWindowSize: 1,
SafeL2Head: 'B',
ExpectedErr: nil,
},
{
// L2 reorg is too deep
Name: "reorg too deep",
GenesisL1Num: 0,
L1: "abcdefgh",
L2: "ABCDEFGH",
NewL1: "abijklmn",
PreFinalizedL2: 'B',
PreSafeL2: 'B',
GenesisL1: 'a',
GenesisL2: 'A',
SeqWindowSize: 1,
ExpectedErr: TooDeepReorgErr,
},
} }
for _, testCase := range testCases { for _, testCase := range testCases {
......
...@@ -22,6 +22,7 @@ import ( ...@@ -22,6 +22,7 @@ import (
p2pcli "github.com/ethereum-optimism/optimism/op-node/p2p/cli" p2pcli "github.com/ethereum-optimism/optimism/op-node/p2p/cli"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/rollup/driver" "github.com/ethereum-optimism/optimism/op-node/rollup/driver"
"github.com/ethereum-optimism/optimism/op-node/rollup/sync"
) )
// NewConfig creates a Config from the provided flags or environment variables. // NewConfig creates a Config from the provided flags or environment variables.
...@@ -58,6 +59,8 @@ func NewConfig(ctx *cli.Context, log log.Logger) (*node.Config, error) { ...@@ -58,6 +59,8 @@ func NewConfig(ctx *cli.Context, log log.Logger) (*node.Config, error) {
l2SyncEndpoint := NewL2SyncEndpointConfig(ctx) l2SyncEndpoint := NewL2SyncEndpointConfig(ctx)
syncConfig := NewSyncConfig(ctx)
cfg := &node.Config{ cfg := &node.Config{
L1: l1Endpoint, L1: l1Endpoint,
L2: l2Endpoint, L2: l2Endpoint,
...@@ -88,6 +91,7 @@ func NewConfig(ctx *cli.Context, log log.Logger) (*node.Config, error) { ...@@ -88,6 +91,7 @@ func NewConfig(ctx *cli.Context, log log.Logger) (*node.Config, error) {
URL: ctx.String(flags.HeartbeatURLFlag.Name), URL: ctx.String(flags.HeartbeatURLFlag.Name),
}, },
ConfigPersistence: configPersistence, ConfigPersistence: configPersistence,
Sync: *syncConfig,
} }
if err := cfg.LoadPersisted(log); err != nil { if err := cfg.LoadPersisted(log); err != nil {
...@@ -214,3 +218,10 @@ func NewSnapshotLogger(ctx *cli.Context) (log.Logger, error) { ...@@ -214,3 +218,10 @@ func NewSnapshotLogger(ctx *cli.Context) (log.Logger, error) {
logger.SetHandler(handler) logger.SetHandler(handler)
return logger, nil return logger, nil
} }
func NewSyncConfig(ctx *cli.Context) *sync.Config {
return &sync.Config{
EngineSync: ctx.Bool(flags.L2EngineSyncEnabled.Name),
SkipSyncStartCheck: ctx.Bool(flags.SkipSyncStartCheck.Name),
}
}
...@@ -266,6 +266,7 @@ func RandomOutputResponse(rng *rand.Rand) *eth.OutputResponse { ...@@ -266,6 +266,7 @@ func RandomOutputResponse(rng *rand.Rand) *eth.OutputResponse {
UnsafeL2: RandomL2BlockRef(rng), UnsafeL2: RandomL2BlockRef(rng),
SafeL2: RandomL2BlockRef(rng), SafeL2: RandomL2BlockRef(rng),
FinalizedL2: RandomL2BlockRef(rng), FinalizedL2: RandomL2BlockRef(rng),
EngineSyncTarget: RandomL2BlockRef(rng),
}, },
} }
} }
...@@ -10,6 +10,7 @@ import ( ...@@ -10,6 +10,7 @@ import (
"github.com/ethereum-optimism/optimism/op-node/metrics" "github.com/ethereum-optimism/optimism/op-node/metrics"
"github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum-optimism/optimism/op-node/rollup/sync"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
...@@ -35,7 +36,7 @@ type Driver struct { ...@@ -35,7 +36,7 @@ type Driver struct {
} }
func NewDriver(logger log.Logger, cfg *rollup.Config, l1Source derive.L1Fetcher, l2Source L2Source, targetBlockNum uint64) *Driver { func NewDriver(logger log.Logger, cfg *rollup.Config, l1Source derive.L1Fetcher, l2Source L2Source, targetBlockNum uint64) *Driver {
pipeline := derive.NewDerivationPipeline(logger, cfg, l1Source, l2Source, metrics.NoopMetrics) pipeline := derive.NewDerivationPipeline(logger, cfg, l1Source, l2Source, metrics.NoopMetrics, &sync.Config{})
pipeline.Reset() pipeline.Reset()
return &Driver{ return &Driver{
logger: logger, logger: logger,
......
...@@ -94,7 +94,8 @@ func RecordError(provider string, errorLabel string) { ...@@ -94,7 +94,8 @@ func RecordError(provider string, errorLabel string) {
func RecordErrorDetails(provider string, label string, err error) { func RecordErrorDetails(provider string, label string, err error) {
errClean := nonAlphanumericRegex.ReplaceAllString(err.Error(), "") errClean := nonAlphanumericRegex.ReplaceAllString(err.Error(), "")
errClean = strings.ReplaceAll(errClean, " ", "_") errClean = strings.ReplaceAll(errClean, " ", "_")
label = fmt.Sprintf("%s.%s", label) errClean = strings.ReplaceAll(errClean, "__", "_")
label = fmt.Sprintf("%s.%s", label, errClean)
RecordError(provider, label) RecordError(provider, label)
} }
......
...@@ -44,10 +44,11 @@ func resolveAddr(ctx context.Context, client *kms.KeyManagementClient, keyName s ...@@ -44,10 +44,11 @@ func resolveAddr(ctx context.Context, client *kms.KeyManagementClient, keyName s
if err != nil { if err != nil {
return common.Address{}, fmt.Errorf("google kms public key %q lookup: %w", keyName, err) return common.Address{}, fmt.Errorf("google kms public key %q lookup: %w", keyName, err)
} }
keyPem := resp.Pem
block, _ := pem.Decode([]byte(resp.Pem)) block, _ := pem.Decode([]byte(keyPem))
if block == nil { if block == nil {
return common.Address{}, fmt.Errorf("google kms public key %q pem empty: %.130q", keyName, resp.Pem) return common.Address{}, fmt.Errorf("google kms public key %q pem empty: %.130q", keyName, keyPem)
} }
var info struct { var info struct {
...@@ -59,11 +60,6 @@ func resolveAddr(ctx context.Context, client *kms.KeyManagementClient, keyName s ...@@ -59,11 +60,6 @@ func resolveAddr(ctx context.Context, client *kms.KeyManagementClient, keyName s
return common.Address{}, fmt.Errorf("google kms public key %q pem block %q: %v", keyName, block.Type, err) return common.Address{}, fmt.Errorf("google kms public key %q pem block %q: %v", keyName, block.Type, err)
} }
wantAlg := asn1.ObjectIdentifier{1, 2, 840, 10045, 2, 1}
if gotAlg := info.AlgID.Algorithm; !gotAlg.Equal(wantAlg) {
return common.Address{}, fmt.Errorf("google kms public key %q asn.1 algorithm %s intead of %s", keyName, gotAlg, wantAlg)
}
return pubKeyAddr(info.Key.Bytes), nil return pubKeyAddr(info.Key.Bytes), nil
} }
......
# @eth-optimism/drippie-mon # @eth-optimism/drippie-mon
## 0.4.2
### Patch Changes
- [#6469](https://github.com/ethereum-optimism/optimism/pull/6469) [`0c769680e`](https://github.com/ethereum-optimism/optimism/commit/0c769680e44208c086deef2f9c03c37da2b536fe) Thanks [@maurelian](https://github.com/maurelian)! - Update language in fault-mon from batches to outputs
## 0.4.1 ## 0.4.1
### Patch Changes ### Patch Changes
......
{ {
"private": true, "private": true,
"name": "@eth-optimism/chain-mon", "name": "@eth-optimism/chain-mon",
"version": "0.4.1", "version": "0.4.2",
"description": "[Optimism] Chain monitoring services", "description": "[Optimism] Chain monitoring services",
"main": "dist/index", "main": "dist/index",
"types": "dist/index", "types": "dist/index",
......
...@@ -31,22 +31,22 @@ export const findOutputForIndex = async ( ...@@ -31,22 +31,22 @@ export const findOutputForIndex = async (
} }
/** /**
* Finds the first state batch index that has not yet passed the fault proof window. * Finds the first L2 output index that has not yet passed the fault proof window.
* *
* @param oracle Output oracle contract. * @param oracle Output oracle contract.
* @returns Starting state root batch index. * @returns Starting L2 output index.
*/ */
export const findFirstUnfinalizedStateBatchIndex = async ( export const findFirstUnfinalizedOutputIndex = async (
oracle: Contract, oracle: Contract,
fpw: number, fpw: number,
logger?: Logger logger?: Logger
): Promise<number> => { ): Promise<number> => {
const latestBlock = await oracle.provider.getBlock('latest') const latestBlock = await oracle.provider.getBlock('latest')
const totalBatches = (await oracle.nextOutputIndex()).toNumber() const totalOutputs = (await oracle.nextOutputIndex()).toNumber()
// Perform a binary search to find the next batch that will pass the challenge period. // Perform a binary search to find the next batch that will pass the challenge period.
let lo = 0 let lo = 0
let hi = totalBatches let hi = totalOutputs
while (lo !== hi) { while (lo !== hi) {
const mid = Math.floor((lo + hi) / 2) const mid = Math.floor((lo + hi) / 2)
const outputData = await findOutputForIndex(oracle, mid, logger) const outputData = await findOutputForIndex(oracle, mid, logger)
...@@ -60,7 +60,7 @@ export const findFirstUnfinalizedStateBatchIndex = async ( ...@@ -60,7 +60,7 @@ export const findFirstUnfinalizedStateBatchIndex = async (
// Result will be zero if the chain is less than FPW seconds old. Only returns undefined in the // Result will be zero if the chain is less than FPW seconds old. Only returns undefined in the
// case that no batches have been submitted for an entire challenge period. // case that no batches have been submitted for an entire challenge period.
if (lo === totalBatches) { if (lo === totalOutputs) {
return undefined return undefined
} else { } else {
return lo return lo
......
...@@ -25,20 +25,17 @@ import { Contract, ethers } from 'ethers' ...@@ -25,20 +25,17 @@ import { Contract, ethers } from 'ethers'
import dateformat from 'dateformat' import dateformat from 'dateformat'
import { version } from '../../package.json' import { version } from '../../package.json'
import { import { findFirstUnfinalizedOutputIndex, findOutputForIndex } from './helpers'
findFirstUnfinalizedStateBatchIndex,
findOutputForIndex,
} from './helpers'
type Options = { type Options = {
l1RpcProvider: Provider l1RpcProvider: Provider
l2RpcProvider: Provider l2RpcProvider: Provider
startBatchIndex: number startOutputIndex: number
optimismPortalAddress?: string optimismPortalAddress?: string
} }
type Metrics = { type Metrics = {
highestBatchIndex: Gauge highestOutputIndex: Gauge
isCurrentlyMismatched: Gauge isCurrentlyMismatched: Gauge
nodeConnectionFailures: Gauge nodeConnectionFailures: Gauge
} }
...@@ -47,7 +44,7 @@ type State = { ...@@ -47,7 +44,7 @@ type State = {
faultProofWindow: number faultProofWindow: number
outputOracle: Contract outputOracle: Contract
messenger: CrossChainMessenger messenger: CrossChainMessenger
currentBatchIndex: number currentOutputIndex: number
diverged: boolean diverged: boolean
} }
...@@ -70,7 +67,7 @@ export class FaultDetector extends BaseServiceV2<Options, Metrics, State> { ...@@ -70,7 +67,7 @@ export class FaultDetector extends BaseServiceV2<Options, Metrics, State> {
validator: validators.provider, validator: validators.provider,
desc: 'Provider for interacting with L2', desc: 'Provider for interacting with L2',
}, },
startBatchIndex: { startOutputIndex: {
validator: validators.num, validator: validators.num,
default: -1, default: -1,
desc: 'The L2 height to start from', desc: 'The L2 height to start from',
...@@ -84,9 +81,9 @@ export class FaultDetector extends BaseServiceV2<Options, Metrics, State> { ...@@ -84,9 +81,9 @@ export class FaultDetector extends BaseServiceV2<Options, Metrics, State> {
}, },
}, },
metricsSpec: { metricsSpec: {
highestBatchIndex: { highestOutputIndex: {
type: Gauge, type: Gauge,
desc: 'Highest batch indices (checked and known)', desc: 'Highest output indices (checked and known)',
labels: ['type'], labels: ['type'],
}, },
isCurrentlyMismatched: { isCurrentlyMismatched: {
...@@ -200,30 +197,32 @@ export class FaultDetector extends BaseServiceV2<Options, Metrics, State> { ...@@ -200,30 +197,32 @@ export class FaultDetector extends BaseServiceV2<Options, Metrics, State> {
this.state.outputOracle = this.state.messenger.contracts.l1.L2OutputOracle this.state.outputOracle = this.state.messenger.contracts.l1.L2OutputOracle
// Figure out where to start syncing from. // Figure out where to start syncing from.
if (this.options.startBatchIndex === -1) { if (this.options.startOutputIndex === -1) {
this.logger.info('finding appropriate starting unfinalized batch') this.logger.info('finding appropriate starting unfinalized output')
const firstUnfinalized = await findFirstUnfinalizedStateBatchIndex( const firstUnfinalized = await findFirstUnfinalizedOutputIndex(
this.state.outputOracle, this.state.outputOracle,
this.state.faultProofWindow, this.state.faultProofWindow,
this.logger this.logger
) )
// We may not have an unfinalized batches in the case where no batches have been submitted // We may not have an unfinalized outputs in the case where no outputs have been submitted
// for the entire duration of the FAULTPROOFWINDOW. We generally do not expect this to happen on mainnet, // for the entire duration of the FAULTPROOFWINDOW. We generally do not expect this to happen on mainnet,
// but it happens often on testnets because the FAULTPROOFWINDOW is very short. // but it happens often on testnets because the FAULTPROOFWINDOW is very short.
if (firstUnfinalized === undefined) { if (firstUnfinalized === undefined) {
this.logger.info('no unfinalized batches found. skipping all batches.') this.logger.info(
const totalBatches = await this.state.outputOracle.nextOutputIndex() 'no unfinalized outputes found. skipping all outputes.'
this.state.currentBatchIndex = totalBatches.toNumber() - 1 )
const totalOutputes = await this.state.outputOracle.nextOutputIndex()
this.state.currentOutputIndex = totalOutputes.toNumber() - 1
} else { } else {
this.state.currentBatchIndex = firstUnfinalized this.state.currentOutputIndex = firstUnfinalized
} }
} else { } else {
this.state.currentBatchIndex = this.options.startBatchIndex this.state.currentOutputIndex = this.options.startOutputIndex
} }
this.logger.info('starting batch', { this.logger.info('starting output', {
batchIndex: this.state.currentBatchIndex, outputIndex: this.state.currentOutputIndex,
}) })
// Set the initial metrics. // Set the initial metrics.
...@@ -241,12 +240,12 @@ export class FaultDetector extends BaseServiceV2<Options, Metrics, State> { ...@@ -241,12 +240,12 @@ export class FaultDetector extends BaseServiceV2<Options, Metrics, State> {
async main(): Promise<void> { async main(): Promise<void> {
const startMs = Date.now() const startMs = Date.now()
let latestBatchIndex: number let latestOutputIndex: number
try { try {
const totalBatches = await this.state.outputOracle.nextOutputIndex() const totalOutputes = await this.state.outputOracle.nextOutputIndex()
latestBatchIndex = totalBatches.toNumber() - 1 latestOutputIndex = totalOutputes.toNumber() - 1
} catch (err) { } catch (err) {
this.logger.error('failed to query total # of batches', { this.logger.error('failed to query total # of outputes', {
error: err, error: err,
node: 'l1', node: 'l1',
section: 'nextOutputIndex', section: 'nextOutputIndex',
...@@ -259,34 +258,34 @@ export class FaultDetector extends BaseServiceV2<Options, Metrics, State> { ...@@ -259,34 +258,34 @@ export class FaultDetector extends BaseServiceV2<Options, Metrics, State> {
return return
} }
if (this.state.currentBatchIndex > latestBatchIndex) { if (this.state.currentOutputIndex > latestOutputIndex) {
this.logger.info('batch index is ahead of the oracle. waiting...', { this.logger.info('output index is ahead of the oracle. waiting...', {
batchIndex: this.state.currentBatchIndex, outputIndex: this.state.currentOutputIndex,
latestBatchIndex, latestOutputIndex,
}) })
await sleep(15000) await sleep(15000)
return return
} }
this.metrics.highestBatchIndex.set({ type: 'known' }, latestBatchIndex) this.metrics.highestOutputIndex.set({ type: 'known' }, latestOutputIndex)
this.logger.info('checking batch', { this.logger.info('checking output', {
batchIndex: this.state.currentBatchIndex, outputIndex: this.state.currentOutputIndex,
latestBatchIndex, latestOutputIndex,
}) })
let outputData: BedrockOutputData let outputData: BedrockOutputData
try { try {
outputData = await findOutputForIndex( outputData = await findOutputForIndex(
this.state.outputOracle, this.state.outputOracle,
this.state.currentBatchIndex, this.state.currentOutputIndex,
this.logger this.logger
) )
} catch (err) { } catch (err) {
this.logger.error('failed to fetch output associated with batch', { this.logger.error('failed to fetch output associated with output', {
error: err, error: err,
node: 'l1', node: 'l1',
section: 'findOutputForIndex', section: 'findOutputForIndex',
batchIndex: this.state.currentBatchIndex, outputIndex: this.state.currentOutputIndex,
}) })
this.metrics.nodeConnectionFailures.inc({ this.metrics.nodeConnectionFailures.inc({
layer: 'l1', layer: 'l1',
...@@ -397,20 +396,20 @@ export class FaultDetector extends BaseServiceV2<Options, Metrics, State> { ...@@ -397,20 +396,20 @@ export class FaultDetector extends BaseServiceV2<Options, Metrics, State> {
const elapsedMs = Date.now() - startMs const elapsedMs = Date.now() - startMs
// Mark the current batch index as checked // Mark the current output index as checked
this.logger.info('checked batch ok', { this.logger.info('checked output ok', {
batchIndex: this.state.currentBatchIndex, outputIndex: this.state.currentOutputIndex,
timeMs: elapsedMs, timeMs: elapsedMs,
}) })
this.metrics.highestBatchIndex.set( this.metrics.highestOutputIndex.set(
{ type: 'checked' }, { type: 'checked' },
this.state.currentBatchIndex this.state.currentOutputIndex
) )
// If we got through the above without throwing an error, we should be // If we got through the above without throwing an error, we should be
// fine to reset and move onto the next batch // fine to reset and move onto the next output
this.state.diverged = false this.state.diverged = false
this.state.currentBatchIndex++ this.state.currentOutputIndex++
this.metrics.isCurrentlyMismatched.set(0) this.metrics.isCurrentlyMismatched.set(0)
} }
} }
......
import { Provider } from '@ethersproject/abstract-provider'
import { Logger } from '@eth-optimism/common-ts'
/**
* Finds
*
* @param
* @param
* @param
* @returns
*/
export const getLastFinalizedBlock = async (
l1RpcProvider: Provider,
faultProofWindow: number,
logger: Logger
): Promise<number> => {
let guessWindowStartBlock
try {
const l1Block = await l1RpcProvider.getBlock('latest')
// The time corresponding to the start of the FPW, based on the current block.
const windowStartTime = l1Block.timestamp - faultProofWindow
// Use the FPW to find the block number that is the start of the FPW.
guessWindowStartBlock = l1Block.number - faultProofWindow / 12
let block = await l1RpcProvider.getBlock(guessWindowStartBlock)
while (block.timestamp > windowStartTime) {
guessWindowStartBlock--
block = await l1RpcProvider.getBlock(guessWindowStartBlock)
}
return block.number
} catch (err) {
logger.fatal('error when calling querying for block', {
errors: err,
})
throw new Error(
`unable to find block number ${guessWindowStartBlock || 'latest'}`
)
}
}
...@@ -13,6 +13,7 @@ import { Event } from 'ethers' ...@@ -13,6 +13,7 @@ import { Event } from 'ethers'
import dateformat from 'dateformat' import dateformat from 'dateformat'
import { version } from '../../package.json' import { version } from '../../package.json'
import { getLastFinalizedBlock as getLastFinalizedBlock } from './helpers'
type Options = { type Options = {
l1RpcProvider: Provider l1RpcProvider: Provider
...@@ -30,7 +31,7 @@ type Metrics = { ...@@ -30,7 +31,7 @@ type Metrics = {
type State = { type State = {
messenger: CrossChainMessenger messenger: CrossChainMessenger
highestUncheckedBlockNumber: number highestUncheckedBlockNumber: number
finalizationWindow: number faultProofWindow: number
forgeryDetected: boolean forgeryDetected: boolean
} }
...@@ -109,10 +110,20 @@ export class WithdrawalMonitor extends BaseServiceV2<Options, Metrics, State> { ...@@ -109,10 +110,20 @@ export class WithdrawalMonitor extends BaseServiceV2<Options, Metrics, State> {
// Not detected by default. // Not detected by default.
this.state.forgeryDetected = false this.state.forgeryDetected = false
// For now we'll just start take it from the env or the tip of the chain this.state.faultProofWindow =
await this.state.messenger.getChallengePeriodSeconds()
this.logger.info(
`fault proof window is ${this.state.faultProofWindow} seconds`
)
// Set the start block number.
if (this.options.startBlockNumber === -1) { if (this.options.startBlockNumber === -1) {
this.state.highestUncheckedBlockNumber = // We default to starting from the last finalized block.
await this.options.l1RpcProvider.getBlockNumber() this.state.highestUncheckedBlockNumber = await getLastFinalizedBlock(
this.options.l1RpcProvider,
this.state.faultProofWindow,
this.logger
)
} else { } else {
this.state.highestUncheckedBlockNumber = this.options.startBlockNumber this.state.highestUncheckedBlockNumber = this.options.startBlockNumber
} }
......
...@@ -8,7 +8,7 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' ...@@ -8,7 +8,7 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
import { expect } from './setup' import { expect } from './setup'
import { import {
findOutputForIndex, findOutputForIndex,
findFirstUnfinalizedStateBatchIndex, findFirstUnfinalizedOutputIndex,
} from '../../src/fault-mon' } from '../../src/fault-mon'
describe('helpers', () => { describe('helpers', () => {
...@@ -122,7 +122,7 @@ describe('helpers', () => { ...@@ -122,7 +122,7 @@ describe('helpers', () => {
}) })
it('should find the first batch older than the FPW', async () => { it('should find the first batch older than the FPW', async () => {
const first = await findFirstUnfinalizedStateBatchIndex( const first = await findFirstUnfinalizedOutputIndex(
L2OutputOracle, L2OutputOracle,
deployConfig.finalizationPeriodSeconds deployConfig.finalizationPeriodSeconds
) )
...@@ -164,7 +164,7 @@ describe('helpers', () => { ...@@ -164,7 +164,7 @@ describe('helpers', () => {
}) })
it('should return zero', async () => { it('should return zero', async () => {
const first = await findFirstUnfinalizedStateBatchIndex( const first = await findFirstUnfinalizedOutputIndex(
L2OutputOracle, L2OutputOracle,
deployConfig.finalizationPeriodSeconds deployConfig.finalizationPeriodSeconds
) )
...@@ -214,7 +214,7 @@ describe('helpers', () => { ...@@ -214,7 +214,7 @@ describe('helpers', () => {
}) })
it('should return undefined', async () => { it('should return undefined', async () => {
const first = await findFirstUnfinalizedStateBatchIndex( const first = await findFirstUnfinalizedOutputIndex(
L2OutputOracle, L2OutputOracle,
deployConfig.finalizationPeriodSeconds deployConfig.finalizationPeriodSeconds
) )
......
...@@ -242,7 +242,7 @@ as the engine implementation can sync state faster through methods like [snap-sy ...@@ -242,7 +242,7 @@ as the engine implementation can sync state faster through methods like [snap-sy
### Happy-path sync ### Happy-path sync
1. The rollup node informs the engine of the L2 chain head, unconditionally (part of regular node operation): 1. The rollup node informs the engine of the L2 chain head, unconditionally (part of regular node operation):
- [`engine_newPayloadV1`][engine_newPayloadV1] is called with latest L2 block derived from L1. - [`engine_newPayloadV1`][engine_newPayloadV1] is called with latest L2 block received from P2P.
- [`engine_forkchoiceUpdatedV1`][engine_forkchoiceUpdatedV1] is called with the current - [`engine_forkchoiceUpdatedV1`][engine_forkchoiceUpdatedV1] is called with the current
`unsafe`/`safe`/`finalized` L2 block hashes. `unsafe`/`safe`/`finalized` L2 block hashes.
2. The engine requests headers from peers, in reverse till the parent hash matches the local chain 2. The engine requests headers from peers, in reverse till the parent hash matches the local chain
......
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