From 82bbac041b3a64dec4615f17830b831a5bb47ab5 Mon Sep 17 00:00:00 2001 From: "allen.wu" Date: Fri, 12 Jun 2026 18:38:04 +0800 Subject: [PATCH] fix(node): refresh L1 state before recovery Refresh the sequencer verifier before reopening the L1 tracker health gate, pass the tracker into Tendermint, and retry HA advertised-address DNS resolution during startup. Update morph Go modules to the latest Tendermint branch pin. Co-authored-by: Cursor --- bindings/go.mod | 2 +- common/go.mod | 2 +- contracts/go.mod | 2 +- node/cmd/node/main.go | 18 ++--- node/go.mod | 2 +- node/go.sum | 4 +- node/hakeeper/ha_service.go | 32 +++++++- node/hakeeper/rpc/types.go | 20 +++-- node/l1sequencer/tracker.go | 107 +++++++++++++++++++----- node/l1sequencer/tracker_test.go | 135 +++++++++++++++++++++++++++++++ node/l1sequencer/verifier.go | 7 ++ node/sequencer/tm_node.go | 8 ++ ops/l2-genesis/go.mod | 2 +- ops/tools/go.mod | 2 +- ops/tools/go.sum | 4 +- tx-submitter/go.mod | 2 +- 16 files changed, 300 insertions(+), 49 deletions(-) create mode 100644 node/l1sequencer/tracker_test.go diff --git a/bindings/go.mod b/bindings/go.mod index b96cde242..63aaf94f3 100644 --- a/bindings/go.mod +++ b/bindings/go.mod @@ -2,7 +2,7 @@ module morph-l2/bindings go 1.24.0 -replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.0.0-20260602085346-ee68e1bcf49a +replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.3.8-0.20260612101929-7222401d6577 replace github.com/morph-l2/go-ethereum => github.com/morph-l2/go-ethereum v0.0.0-20260608072528-fe02cc1f10bc diff --git a/common/go.mod b/common/go.mod index 59111228c..00656ed42 100644 --- a/common/go.mod +++ b/common/go.mod @@ -2,7 +2,7 @@ module morph-l2/common go 1.24.0 -replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.0.0-20260602085346-ee68e1bcf49a +replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.3.8-0.20260612101929-7222401d6577 replace github.com/morph-l2/go-ethereum => github.com/morph-l2/go-ethereum v0.0.0-20260608072528-fe02cc1f10bc diff --git a/contracts/go.mod b/contracts/go.mod index 11676035e..d19dfca19 100644 --- a/contracts/go.mod +++ b/contracts/go.mod @@ -2,7 +2,7 @@ module morph-l2/contract go 1.24.0 -replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.0.0-20260602085346-ee68e1bcf49a +replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.3.8-0.20260612101929-7222401d6577 replace github.com/morph-l2/go-ethereum => github.com/morph-l2/go-ethereum v0.0.0-20260608072528-fe02cc1f10bc diff --git a/node/cmd/node/main.go b/node/cmd/node/main.go index 00aecc2b8..80cc11702 100644 --- a/node/cmd/node/main.go +++ b/node/cmd/node/main.go @@ -170,7 +170,7 @@ func L2NodeMain(ctx *cli.Context) error { if haService != nil { ha = haService } - tmNode, err = sequencer.SetupNode(tmCfg, tmVal, executor, nodeConfig.Logger, verifier, signer, ha) + tmNode, err = sequencer.SetupNode(tmCfg, tmVal, executor, nodeConfig.Logger, verifier, tracker, signer, ha) if err != nil { return fmt.Errorf("failed to setup consensus node: %v", err) } @@ -314,14 +314,7 @@ func initL1SequencerComponents( seqPrivKeyHex := ctx.GlobalString(flags.SequencerPrivateKey.Name) enclaveSignerAddr := ctx.GlobalString(flags.SequencerEnclaveSignerAddr.Name) - // Initialize L1 Tracker - tracker := l1sequencer.NewL1Tracker(context.Background(), l1Client, lagThreshold, logger) - if err := tracker.Start(); err != nil { - return nil, nil, nil, fmt.Errorf("failed to start L1 tracker: %w", err) - } - logger.Info("L1 Tracker started", "lagThreshold", lagThreshold) - - // Initialize Sequencer Verifier + // Initialize Sequencer Verifier (built before the tracker, which refreshes it on L1 recovery). var verifier *l1sequencer.SequencerVerifier if contractAddr != (common.Address{}) { caller, err := bindings.NewL1SequencerCaller(contractAddr, l1Client) @@ -337,6 +330,13 @@ func initL1SequencerComponents( return nil, nil, nil, fmt.Errorf("L1 Sequencer contract address is required, check l1.sequencerContract configuration") } + // Initialize L1 Tracker (health gate: halts production/sync when L1 is stale). + tracker := l1sequencer.NewL1Tracker(context.Background(), l1Client, verifier, lagThreshold, logger) + if err := tracker.Start(); err != nil { + return nil, nil, nil, fmt.Errorf("failed to start L1 tracker: %w", err) + } + logger.Info("L1 Tracker started", "warnLag", lagThreshold) + // Initialize Signer (optional). Three mutually exclusive modes: // 1) sequencer.privateKey set → LocalSigner (plaintext key in node memory) // 2) sequencer.enclaveSignerAddr → EnclaveSigner (vsock to Nitro Enclave; key never in node) diff --git a/node/go.mod b/node/go.mod index c3383135b..a16557b82 100644 --- a/node/go.mod +++ b/node/go.mod @@ -2,7 +2,7 @@ module morph-l2/node go 1.24.0 -replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.0.0-20260602085346-ee68e1bcf49a +replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.3.8-0.20260612101929-7222401d6577 replace github.com/morph-l2/go-ethereum => github.com/morph-l2/go-ethereum v0.0.0-20260608072528-fe02cc1f10bc diff --git a/node/go.sum b/node/go.sum index aa16d7a41..315995fb6 100644 --- a/node/go.sum +++ b/node/go.sum @@ -416,8 +416,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/morph-l2/go-ethereum v0.0.0-20260608072528-fe02cc1f10bc h1:2Umr8WRDBKwCgGrQQ8yCdhn71bCuMJuecId2ClK80DU= github.com/morph-l2/go-ethereum v0.0.0-20260608072528-fe02cc1f10bc/go.mod h1:nkVzHjQWCOjvukQW8ittlwX+Xz9gmVHrP7mUi7zoHTs= -github.com/morph-l2/tendermint v0.0.0-20260602085346-ee68e1bcf49a h1:TeuQHBpIpH2/Z8jX9sZLtB0+4mwLBfKfII7BD/J5XME= -github.com/morph-l2/tendermint v0.0.0-20260602085346-ee68e1bcf49a/go.mod h1:qpiwqfcCB89dBYfqVJOc/HjGxDp3OdDlthgttJJYyRs= +github.com/morph-l2/tendermint v0.3.8-0.20260612101929-7222401d6577 h1:u1F8xG9X23TIE/zYeVMOY1BiHPGT9pcfpIoMz2kyrJY= +github.com/morph-l2/tendermint v0.3.8-0.20260612101929-7222401d6577/go.mod h1:qpiwqfcCB89dBYfqVJOc/HjGxDp3OdDlthgttJJYyRs= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= diff --git a/node/hakeeper/ha_service.go b/node/hakeeper/ha_service.go index fd3953a52..cf2e2901a 100644 --- a/node/hakeeper/ha_service.go +++ b/node/hakeeper/ha_service.go @@ -25,6 +25,12 @@ const ( raftInfiniteTimeout = 0 // wait forever raftMaxConnPool = 10 raftSnapshots = 1 // snapshot data is trivial (8 bytes); keep 1 for log compaction + + // advertisedAddr may be a hostname whose DNS record is not yet propagated the + // instant the pod starts (it resolves a few seconds later). Retry the initial + // resolve instead of failing fast on that startup race. + advAddrResolveRetries = 5 // retries after the first attempt (6 resolves total) + advAddrResolveInterval = 3 * time.Second // 5 retries × 3s ≈ 15s max wait for DNS to propagate ) // HAService implements the SequencerHA interface from tendermint/sequencer. @@ -259,7 +265,12 @@ func (h *HAService) ClusterMembership() (*hakeeperrpc.ClusterMembership, error) Suffrage: hakeeperrpc.ServerSuffrage(srv.Suffrage), }) } - return &hakeeperrpc.ClusterMembership{Servers: servers, Version: future.Index()}, nil + _, leaderID := h.r.LeaderWithID() + return &hakeeperrpc.ClusterMembership{ + Servers: servers, + LeaderID: string(leaderID), + Version: future.Index(), + }, nil } func (h *HAService) ServerID() string { return h.cfg.ServerID } @@ -336,9 +347,22 @@ func (h *HAService) initRaft() (retErr error) { // Note: the resolved IP is only used by the transport's LocalAddr(). The ServerAddress // stored in Raft cluster config (BootstrapCluster/AddServerAsVoter) uses the raw // h.advertisedAddr which may be a hostname — Raft's Dial() re-resolves DNS each time. - tcpAdvAddr, err := net.ResolveTCPAddr("tcp", h.advertisedAddr) - if err != nil { - return fmt.Errorf("resolve advertised addr %q: %w", h.advertisedAddr, err) + var tcpAdvAddr *net.TCPAddr + for attempt := 0; ; attempt++ { + tcpAdvAddr, err = net.ResolveTCPAddr("tcp", h.advertisedAddr) + if err == nil { + break + } + if attempt >= advAddrResolveRetries { + // DNS still hasn't propagated after the full retry budget. Some consensus-switch + // paths only log StartSequencerRoutines errors and keep running, which would leave + // this node alive but with Raft never started. Panic instead so the process exits + // non-zero and the orchestrator (k8s) restarts the pod for another DNS attempt. + panic(fmt.Errorf("hakeeper: resolve advertised addr %q after %d retries: %w", h.advertisedAddr, advAddrResolveRetries, err)) + } + h.logger.Info("hakeeper: advertised addr not resolvable yet, retrying", + "addr", h.advertisedAddr, "retry", attempt+1, "max", advAddrResolveRetries, "err", err) + time.Sleep(advAddrResolveInterval) } bindAddr := fmt.Sprintf("%s:%d", h.cfg.Consensus.ListenAddr, h.cfg.Consensus.ListenPort) diff --git a/node/hakeeper/rpc/types.go b/node/hakeeper/rpc/types.go index c62dfdb66..fbc0bfeaf 100644 --- a/node/hakeeper/rpc/types.go +++ b/node/hakeeper/rpc/types.go @@ -3,12 +3,17 @@ package rpc // ServerSuffrage determines whether a Server in a Configuration gets a vote. type ServerSuffrage int +// These values must match hashicorp/raft's ServerSuffrage exactly: cluster +// membership is read from raft and cast by raw integer value (see +// HAService.ClusterMembership), so any divergence silently mislabels roles. const ( - // Nonvoter receives log entries but is not considered for elections. - // Zero value — safer default (no voting rights). - Nonvoter ServerSuffrage = iota // Voter is a server whose vote is counted in elections. - Voter + Voter ServerSuffrage = iota + // Nonvoter receives log entries but is not considered for elections. + Nonvoter + // Staging is a server that acts like a Nonvoter while it catches up, then is + // promoted to Voter. Present for parity with raft's enum. + Staging ) func (s ServerSuffrage) String() string { @@ -17,6 +22,8 @@ func (s ServerSuffrage) String() string { return "Voter" case Nonvoter: return "Nonvoter" + case Staging: + return "Staging" } return "ServerSuffrage" } @@ -24,7 +31,10 @@ func (s ServerSuffrage) String() string { // ClusterMembership is a versioned list of servers in the Raft cluster. type ClusterMembership struct { Servers []ServerInfo `json:"servers"` - Version uint64 `json:"version"` + // LeaderID is the ID of the current leader, matching one of Servers[i].ID. + // Empty when no leader is currently known (e.g. during an election). + LeaderID string `json:"leaderId"` + Version uint64 `json:"version"` } // ServerInfo describes a single Raft cluster member. diff --git a/node/l1sequencer/tracker.go b/node/l1sequencer/tracker.go index d8ea3b8c0..dd810cb53 100644 --- a/node/l1sequencer/tracker.go +++ b/node/l1sequencer/tracker.go @@ -2,49 +2,83 @@ package l1sequencer import ( "context" + "sync/atomic" "time" "github.com/morph-l2/go-ethereum/ethclient" tmlog "github.com/tendermint/tendermint/libs/log" ) -// L1Tracker monitors L1 RPC sync status and logs warnings if behind. -// It runs as an independent service. +// checkInterval is how often the tracker polls L1. Smaller than the halt +// threshold so the gate trips/recovers promptly without burdening the RPC. +const checkInterval = 15 * time.Second + +// defaultHaltLag is the threshold at which the node halts: once our reference +// timestamp (the L1 head time on success, or the first-failure time during an +// RPC outage) is more than this behind wall-clock, the sequencer stops producing +// and fullnodes stop syncing. Hardcoded for now; promote to a flag if needed. +const defaultHaltLag = 30 * time.Minute + +// verifierRefresher re-syncs the L1 sequencer set on demand. The tracker forces +// one refresh when L1 recovers, before reopening the gate, so block acceptance +// resumes against the freshest sequencer set. Implemented by *SequencerVerifier. +type verifierRefresher interface { + refresh() error +} + +// L1Tracker polls the L1 RPC and reports a single halt signal via IsHalt: when +// our most recent reference timestamp is older than haltLag, the sequencer must +// stop producing and fullnodes must stop syncing, to avoid acting on a stale +// view of L1 sequencer changes. It implements sequencer.L1Tracker. type L1Tracker struct { ctx context.Context cancel context.CancelFunc l1Client *ethclient.Client - lagThreshold time.Duration + verifier verifierRefresher + lagThreshold time.Duration // warn threshold (log only) + haltLag time.Duration // halt threshold logger tmlog.Logger stop chan struct{} + + // State below is only mutated from the single loop goroutine (and tests). + healthy atomic.Bool // read concurrently by gate consumers + lastSeen time.Time // L1 head time on success, or first-failure time during an outage + inErr bool // in an RPC-failure run; keeps lastSeen anchored at the first failure } -// NewL1Tracker creates a new L1Tracker +// NewL1Tracker creates a new L1Tracker. verifier must not be nil. func NewL1Tracker( ctx context.Context, l1Client *ethclient.Client, - lagThreshold time.Duration, + verifier verifierRefresher, + warnLag time.Duration, logger tmlog.Logger, ) *L1Tracker { ctx, cancel := context.WithCancel(ctx) - return &L1Tracker{ + t := &L1Tracker{ ctx: ctx, cancel: cancel, l1Client: l1Client, - lagThreshold: lagThreshold, + verifier: verifier, + lagThreshold: warnLag, + haltLag: defaultHaltLag, logger: logger.With("module", "l1tracker"), stop: make(chan struct{}), } + t.healthy.Store(true) // start allowed + t.lastSeen = time.Now() // grace: tolerate initial RPC failures for haltLag + return t } -// Start starts the L1Tracker +// IsHalt implements sequencer.L1Tracker. +func (t *L1Tracker) IsHalt() bool { return !t.healthy.Load() } + func (t *L1Tracker) Start() error { - t.logger.Info("Starting L1Tracker", "lagThreshold", t.lagThreshold) + t.logger.Info("Starting L1Tracker", "warnLag", t.lagThreshold, "haltLag", t.haltLag, "tick", checkInterval) go t.loop() return nil } -// Stop stops the L1Tracker func (t *L1Tracker) Stop() { t.logger.Info("Stopping L1Tracker") t.cancel() @@ -53,34 +87,67 @@ func (t *L1Tracker) Stop() { func (t *L1Tracker) loop() { defer close(t.stop) - - ticker := time.NewTicker(1 * time.Minute) + ticker := time.NewTicker(checkInterval) defer ticker.Stop() - for { select { case <-t.ctx.Done(): return case <-ticker.C: - t.checkL1SyncLag() + t.check() } } } -func (t *L1Tracker) checkL1SyncLag() { +// check polls the L1 head, emits the warn-level log, and folds the result into +// the health state. +func (t *L1Tracker) check() { header, err := t.l1Client.HeaderByNumber(t.ctx, nil) if err != nil { t.logger.Error("Failed to get L1 header", "error", err) + t.update(time.Time{}, false, time.Now()) return } - - blockTime := time.Unix(int64(header.Time), 0) - lag := time.Since(blockTime) - if lag > t.lagThreshold { + headTime := time.Unix(int64(header.Time), 0) + if lag := time.Since(headTime); lag > t.lagThreshold { t.logger.Error("L1 RPC is behind", "latestBlock", header.Number.Uint64(), - "blockTime", blockTime.Format(time.RFC3339), + "blockTime", headTime.Format(time.RFC3339), "lag", lag.Round(time.Second), ) } + t.update(headTime, true, time.Now()) +} + +// update folds one poll into the health state. On success it anchors lastSeen at +// the L1 head time; on the first failure of an outage it anchors at now (the +// inErr flag stops later failures from re-anchoring). It then halts when lastSeen +// is older than haltLag, and on recovery refreshes the verifier before reopening. +// now is injected for testability. +func (t *L1Tracker) update(headTime time.Time, ok bool, now time.Time) { + if ok { + t.inErr = false + t.lastSeen = headTime + } else if !t.inErr { + t.inErr = true + t.lastSeen = now + } + + if now.Sub(t.lastSeen) > t.haltLag { + if t.healthy.CompareAndSwap(true, false) { + t.logger.Error("L1 health gate TRIPPED: L1 too stale, halting block production and sync", "haltLag", t.haltLag) + } + return + } + + // L1 is fresh again. On recovery, force a verifier resync before reopening; + // if it fails, stay halted and retry next tick. + if !t.healthy.Load() { + if err := t.verifier.refresh(); err != nil { + t.logger.Error("verifier refresh on L1 recovery failed; staying halted", "err", err) + return + } + t.healthy.Store(true) + t.logger.Info("L1 health gate RECOVERED: resuming block production and sync") + } } diff --git a/node/l1sequencer/tracker_test.go b/node/l1sequencer/tracker_test.go new file mode 100644 index 000000000..fd787debe --- /dev/null +++ b/node/l1sequencer/tracker_test.go @@ -0,0 +1,135 @@ +package l1sequencer + +import ( + "errors" + "testing" + "time" + + tmlog "github.com/tendermint/tendermint/libs/log" +) + +type fakeRefresher struct { + calls int + err error +} + +func (f *fakeRefresher) refresh() error { f.calls++; return f.err } + +func newTrackerForTest() (*L1Tracker, *fakeRefresher) { + f := &fakeRefresher{} + t := &L1Tracker{ + verifier: f, + lagThreshold: 5 * time.Minute, + haltLag: 30 * time.Minute, + logger: tmlog.NewNopLogger(), + } + t.healthy.Store(true) + return t, f +} + +func TestUpdate_LagTripsImmediately(t *testing.T) { + tr, _ := newTrackerForTest() + now := time.Unix(1_000_000, 0) + tr.lastSeen = now + tr.update(now.Add(-31*time.Minute), true, now) + if !tr.IsHalt() { + t.Fatal("an L1 head 31m old should halt immediately") + } +} + +func TestUpdate_LagBoundaryDoesNotTrip(t *testing.T) { + tr, _ := newTrackerForTest() + now := time.Unix(1_000_000, 0) + tr.lastSeen = now + tr.update(now.Add(-30*time.Minute), true, now) // exactly haltLag + if tr.IsHalt() { + t.Fatal("a head exactly haltLag old should NOT halt") + } +} + +func TestUpdate_FirstErrAnchorsAndTripsAfterHaltLag(t *testing.T) { + tr, _ := newTrackerForTest() + base := time.Unix(1_000_000, 0) + tr.lastSeen = base + tr.update(time.Time{}, false, base) // first failure anchors at base + if tr.IsHalt() { + t.Fatal("first failure must not trip") + } + tr.update(time.Time{}, false, base.Add(29*time.Minute)) + if tr.IsHalt() { + t.Fatal("29m of failures must not trip") + } + tr.update(time.Time{}, false, base.Add(31*time.Minute)) + if !tr.IsHalt() { + t.Fatal("31m since the first failure must halt") + } +} + +func TestUpdate_ErrFlagKeepsFirstFailureAnchor(t *testing.T) { + // Without the inErr flag, every failure would re-anchor lastSeen to `now` + // and the gate would never trip. Confirm the anchor stays at the FIRST one. + tr, _ := newTrackerForTest() + base := time.Unix(1_000_000, 0) + tr.lastSeen = base + for i := 0; i <= 31; i++ { + tr.update(time.Time{}, false, base.Add(time.Duration(i)*time.Minute)) + } + if !tr.IsHalt() { + t.Fatal("repeated failures must still trip 30m after the first") + } +} + +func TestUpdate_SuccessClearsErrAndReanchors(t *testing.T) { + tr, _ := newTrackerForTest() + base := time.Unix(1_000_000, 0) + tr.lastSeen = base + tr.update(time.Time{}, false, base) // err run anchored at base + tr.update(base.Add(20*time.Minute), true, base.Add(20*time.Minute)) // success clears inErr, re-anchors + tr.update(time.Time{}, false, base.Add(40*time.Minute)) // new err run anchors at base+40m + tr.update(time.Time{}, false, base.Add(65*time.Minute)) // only 25m since new anchor + if tr.IsHalt() { + t.Fatal("a success should clear the err run and re-anchor the staleness clock") + } +} + +func TestUpdate_RecoversWithRefresh(t *testing.T) { + tr, f := newTrackerForTest() + now := time.Unix(1_000_000, 0) + tr.lastSeen = now + tr.update(now.Add(-31*time.Minute), true, now) // trip + if !tr.IsHalt() { + t.Fatal("should be halted") + } + n1 := now.Add(1 * time.Minute) + tr.update(n1, true, n1) // fresh read -> refresh once, then recover + if tr.IsHalt() { + t.Fatal("should recover on the first fresh read") + } + if f.calls != 1 { + t.Fatalf("verifier.refresh should be called once on recovery, got %d", f.calls) + } + n2 := now.Add(2 * time.Minute) + tr.update(n2, true, n2) // already healthy -> no extra refresh + if f.calls != 1 { + t.Fatalf("no extra refresh while healthy, got %d", f.calls) + } +} + +func TestUpdate_RecoveryBlockedWhenRefreshFails(t *testing.T) { + tr, f := newTrackerForTest() + f.err = errors.New("rpc still flaky") + now := time.Unix(1_000_000, 0) + tr.lastSeen = now + tr.update(now.Add(-31*time.Minute), true, now) // trip + n1 := now.Add(1 * time.Minute) + tr.update(n1, true, n1) // fresh but refresh fails -> stay halted + if !tr.IsHalt() { + t.Fatal("must stay halted while the recovery refresh fails") + } + f.err = nil + n2 := now.Add(2 * time.Minute) + tr.update(n2, true, n2) // refresh succeeds -> recover + if tr.IsHalt() { + t.Fatal("should recover once the refresh succeeds") + } +} diff --git a/node/l1sequencer/verifier.go b/node/l1sequencer/verifier.go index 78c95dd4c..a5022fb61 100644 --- a/node/l1sequencer/verifier.go +++ b/node/l1sequencer/verifier.go @@ -99,6 +99,13 @@ func (c *SequencerVerifier) Stop() { c.cancel() } +// refresh forces an immediate sequencer-history resync from L1. The L1 tracker +// calls it once when L1 recovers, before reopening the gate. Satisfies +// verifierRefresher. +func (c *SequencerVerifier) refresh() error { + return c.syncHistory() +} + // syncHistory fetches the full sequencer history from L1 (finalized tag) and // replaces the local cache if anything changed. func (c *SequencerVerifier) syncHistory() error { diff --git a/node/sequencer/tm_node.go b/node/sequencer/tm_node.go index 2c4bc380e..8b0b7e0b6 100644 --- a/node/sequencer/tm_node.go +++ b/node/sequencer/tm_node.go @@ -61,6 +61,7 @@ func SetupNode( executor *node.Executor, logger tmlog.Logger, verifier *l1sequencer.SequencerVerifier, + l1Tracker *l1sequencer.L1Tracker, signer l1sequencer.Signer, ha tmsequencer.SequencerHA, ) (*tmnode.Node, error) { @@ -77,6 +78,12 @@ func SetupNode( tmVerifier = verifier } + // Adapt the concrete L1 tracker to the tendermint L1Tracker interface. + var tmL1Tracker tmsequencer.L1Tracker + if l1Tracker != nil { + tmL1Tracker = l1Tracker + } + n, err := tmnode.NewNode( tmCfg, executor, @@ -88,6 +95,7 @@ func SetupNode( tmnode.DefaultMetricsProvider(tmCfg.Instrumentation), nodeLogger, tmVerifier, + tmL1Tracker, signer, ha, ) diff --git a/ops/l2-genesis/go.mod b/ops/l2-genesis/go.mod index c2e4cebcc..0b74f8370 100644 --- a/ops/l2-genesis/go.mod +++ b/ops/l2-genesis/go.mod @@ -2,7 +2,7 @@ module morph-l2/morph-deployer go 1.24.0 -replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.0.0-20260602085346-ee68e1bcf49a +replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.3.8-0.20260612101929-7222401d6577 replace github.com/morph-l2/go-ethereum => github.com/morph-l2/go-ethereum v0.0.0-20260608072528-fe02cc1f10bc diff --git a/ops/tools/go.mod b/ops/tools/go.mod index cb0e81dfc..1c1546ea3 100644 --- a/ops/tools/go.mod +++ b/ops/tools/go.mod @@ -2,7 +2,7 @@ module morph-l2/tools go 1.24.0 -replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.0.0-20260602085346-ee68e1bcf49a +replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.3.8-0.20260612101929-7222401d6577 replace github.com/morph-l2/go-ethereum => github.com/morph-l2/go-ethereum v0.0.0-20260608072528-fe02cc1f10bc diff --git a/ops/tools/go.sum b/ops/tools/go.sum index e4b39f120..f5b0ba312 100644 --- a/ops/tools/go.sum +++ b/ops/tools/go.sum @@ -163,8 +163,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/morph-l2/go-ethereum v0.0.0-20260608072528-fe02cc1f10bc h1:2Umr8WRDBKwCgGrQQ8yCdhn71bCuMJuecId2ClK80DU= github.com/morph-l2/go-ethereum v0.0.0-20260608072528-fe02cc1f10bc/go.mod h1:nkVzHjQWCOjvukQW8ittlwX+Xz9gmVHrP7mUi7zoHTs= -github.com/morph-l2/tendermint v0.0.0-20260602085346-ee68e1bcf49a h1:TeuQHBpIpH2/Z8jX9sZLtB0+4mwLBfKfII7BD/J5XME= -github.com/morph-l2/tendermint v0.0.0-20260602085346-ee68e1bcf49a/go.mod h1:qpiwqfcCB89dBYfqVJOc/HjGxDp3OdDlthgttJJYyRs= +github.com/morph-l2/tendermint v0.3.8-0.20260612101929-7222401d6577 h1:u1F8xG9X23TIE/zYeVMOY1BiHPGT9pcfpIoMz2kyrJY= +github.com/morph-l2/tendermint v0.3.8-0.20260612101929-7222401d6577/go.mod h1:qpiwqfcCB89dBYfqVJOc/HjGxDp3OdDlthgttJJYyRs= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= diff --git a/tx-submitter/go.mod b/tx-submitter/go.mod index 2c7a35a15..6f1fd8caa 100644 --- a/tx-submitter/go.mod +++ b/tx-submitter/go.mod @@ -2,7 +2,7 @@ module morph-l2/tx-submitter go 1.24.0 -replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.0.0-20260602085346-ee68e1bcf49a +replace github.com/tendermint/tendermint => github.com/morph-l2/tendermint v0.3.8-0.20260612101929-7222401d6577 replace github.com/morph-l2/go-ethereum => github.com/morph-l2/go-ethereum v0.0.0-20260608072528-fe02cc1f10bc