From 2cb004224b15811a62c45bfda4370addd2514f11 Mon Sep 17 00:00:00 2001 From: "allen.wu" Date: Tue, 9 Jun 2026 19:11:56 +0800 Subject: [PATCH 1/2] fix(node): resolve golangci-lint failures and broken test Dockerfiles Lint (node module): - errcheck: handle ignored error returns in block_fsm (rc.Close, sink.Cancel), ha_service (transport/stableStore/logStore Close) and enclave_signer (conn.SetDeadline) - gosec G112: set ReadHeaderTimeout on the hakeeper RPC server and the node metrics HTTP server - gosec G301: tighten raft StorageDir permissions from 0o755 to 0o750 - gofmt: reformat ha_service.go, signer.go, verifier.go - misspell: initialised->initialized, initialises->initializes, cancelled->canceled Build/test harness: - drop the dead `COPY ./morph/oracle/go.mod` step from the l2-node and tx-submitter test Dockerfiles (the oracle project no longer exists, so the COPY fails the build) - run-test.sh: fix a non-existent compose service name (sentry-geth-0 -> sentry-el-0) that aborted L2 startup Co-Authored-By: Claude Opus 4.7 (1M context) --- node/cmd/node/main.go | 2 +- node/derivation/derivation.go | 2 +- node/hakeeper/block_fsm.go | 4 ++-- node/hakeeper/ha_service.go | 22 +++++++++---------- node/hakeeper/rpc/server.go | 6 +++-- node/l1sequencer/enclave_signer.go | 4 ++-- node/l1sequencer/signer.go | 1 - node/l1sequencer/verifier.go | 4 ++-- .../Dockerfile.l2-node-test | 1 - .../Dockerfile.tx-submitter-test | 1 - ops/docker-sequencer-test/run-test.sh | 2 +- 11 files changed, 24 insertions(+), 25 deletions(-) diff --git a/node/cmd/node/main.go b/node/cmd/node/main.go index 9296c4ed7..e8abe3b4b 100644 --- a/node/cmd/node/main.go +++ b/node/cmd/node/main.go @@ -377,7 +377,7 @@ func startMetricsServer(addr string, logger tmlog.Logger) { } mux := http.NewServeMux() mux.Handle("/metrics", promhttp.Handler()) - srv := &http.Server{Addr: addr, Handler: mux} + srv := &http.Server{Addr: addr, Handler: mux, ReadHeaderTimeout: 10 * time.Second} go func() { if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { logger.Error("metrics server", "addr", addr, "err", err) diff --git a/node/derivation/derivation.go b/node/derivation/derivation.go index 068f255f5..f7e8ef2a2 100644 --- a/node/derivation/derivation.go +++ b/node/derivation/derivation.go @@ -878,7 +878,7 @@ func (d *Derivation) withReactorsQuiesced(ctx context.Context, batchIndex uint64 return err } defer func() { - // Use background context so a cancelled parent ctx doesn't + // Use background context so a canceled parent ctx doesn't // prevent reactor restart. height := preWrite if cur, readErr := d.l2Client.BlockNumber(context.Background()); readErr == nil { diff --git a/node/hakeeper/block_fsm.go b/node/hakeeper/block_fsm.go index 2a97ee212..d299df5c4 100644 --- a/node/hakeeper/block_fsm.go +++ b/node/hakeeper/block_fsm.go @@ -153,7 +153,7 @@ func (f *BlockFSM) Snapshot() (raft.FSMSnapshot, error) { // Reads the 8-byte appliedHeight from the snapshot. Does NOT call onApplied -- // geth state must be recovered independently (Fullnode P2P sync). func (f *BlockFSM) Restore(rc io.ReadCloser) error { - defer rc.Close() + defer func() { _ = rc.Close() }() data, err := io.ReadAll(rc) if err != nil { @@ -191,7 +191,7 @@ func (s *blockSnapshot) Persist(sink raft.SnapshotSink) error { var buf [8]byte binary.BigEndian.PutUint64(buf[:], s.height) if _, err := sink.Write(buf[:]); err != nil { - sink.Cancel() + _ = sink.Cancel() return fmt.Errorf("blockSnapshot.Persist: write failed: %w", err) } return sink.Close() diff --git a/node/hakeeper/ha_service.go b/node/hakeeper/ha_service.go index 5c1002859..fd3953a52 100644 --- a/node/hakeeper/ha_service.go +++ b/node/hakeeper/ha_service.go @@ -30,13 +30,13 @@ const ( // HAService implements the SequencerHA interface from tendermint/sequencer. // It also satisfies rpc.ConsensusAdapter so it can be passed directly to the RPC server. type HAService struct { - logger tmlog.Logger - cfg *Config + logger tmlog.Logger + cfg *Config advertisedAddr string // resolved once in New(), used throughout - fsm *BlockFSM - rpcServer *hakeeperrpc.Server + fsm *BlockFSM + rpcServer *hakeeperrpc.Server - // Raft internals (initialised in Start) + // Raft internals (initialized in Start) r *raft.Raft transport *raft.NetworkTransport @@ -69,7 +69,7 @@ func (h *HAService) SetOnBlockApplied(fn func(*types.BlockV2) error) { // ── SequencerHA interface ──────────────────────────────────────────────────── -// Start initialises Raft and the management RPC server. +// Start initializes Raft and the management RPC server. // Called by StateV2.OnStart() at upgrade height. func (h *HAService) Start() error { if err := h.initRaft(); err != nil { @@ -271,7 +271,7 @@ func (h *HAService) Addr() string { return h.advertisedAddr } // initRaft creates the Raft instance. Called once from Start(). // On failure, all opened resources are cleaned up via a single deferred closure. func (h *HAService) initRaft() (retErr error) { - if err := os.MkdirAll(h.cfg.StorageDir, 0o755); err != nil { + if err := os.MkdirAll(h.cfg.StorageDir, 0o750); err != nil { return fmt.Errorf("mkdir %q: %w", h.cfg.StorageDir, err) } @@ -287,13 +287,13 @@ func (h *HAService) initRaft() (retErr error) { r.Shutdown() } if transport != nil { - transport.Close() + _ = transport.Close() } if stableStore != nil { - stableStore.Close() + _ = stableStore.Close() } if logStore != nil { - logStore.Close() + _ = logStore.Close() } } }() @@ -364,7 +364,7 @@ func (h *HAService) initRaft() (retErr error) { h.r = r h.transport = transport - h.logger.Info("hakeeper: raft initialised", "bind", bindAddr) + h.logger.Info("hakeeper: raft initialized", "bind", bindAddr) return nil } diff --git a/node/hakeeper/rpc/server.go b/node/hakeeper/rpc/server.go index 90cc3bc33..3cea4ee9b 100644 --- a/node/hakeeper/rpc/server.go +++ b/node/hakeeper/rpc/server.go @@ -4,6 +4,7 @@ import ( "fmt" "net/http" "sync" + "time" ethrpc "github.com/morph-l2/go-ethereum/rpc" "github.com/pkg/errors" @@ -40,8 +41,9 @@ func New(log log.Logger, listenAddr string, listenPort int, cons ConsensusAdapte addr := fmt.Sprintf("%s:%d", listenAddr, listenPort) httpSrv := &http.Server{ - Addr: addr, - Handler: mux, + Addr: addr, + Handler: mux, + ReadHeaderTimeout: 10 * time.Second, } return &Server{ diff --git a/node/l1sequencer/enclave_signer.go b/node/l1sequencer/enclave_signer.go index e83a70fca..53d575440 100644 --- a/node/l1sequencer/enclave_signer.go +++ b/node/l1sequencer/enclave_signer.go @@ -165,7 +165,7 @@ func (s *EnclaveSigner) probe() error { if err := conn.SetDeadline(time.Now().Add(requestTimeout)); err != nil { return fmt.Errorf("set deadline: %w", err) } - defer conn.SetDeadline(time.Time{}) // clear on exit so signOnce can manage its own deadline + defer func() { _ = conn.SetDeadline(time.Time{}) }() // clear on exit so signOnce can manage its own deadline if _, err := conn.Write([]byte{opGetPubkey}); err != nil { return fmt.Errorf("write GetPubkey: %w", err) @@ -220,7 +220,7 @@ func (s *EnclaveSigner) signOnce(conn net.Conn, data []byte) ([]byte, error) { if err := conn.SetDeadline(time.Now().Add(requestTimeout)); err != nil { return nil, err } - defer conn.SetDeadline(time.Time{}) + defer func() { _ = conn.SetDeadline(time.Time{}) }() req := make([]byte, 1+hashLen) req[0] = opSign diff --git a/node/l1sequencer/signer.go b/node/l1sequencer/signer.go index 4ad851304..e99726e7a 100644 --- a/node/l1sequencer/signer.go +++ b/node/l1sequencer/signer.go @@ -53,4 +53,3 @@ func (s *LocalSigner) Sign(data []byte) ([]byte, error) { func (s *LocalSigner) Address() common.Address { return s.address } - diff --git a/node/l1sequencer/verifier.go b/node/l1sequencer/verifier.go index b12be781d..f7df7fd95 100644 --- a/node/l1sequencer/verifier.go +++ b/node/l1sequencer/verifier.go @@ -34,7 +34,7 @@ type sequencerCursor struct { // History is loaded from L1 at construction and refreshed every 5 minutes. // All L1 reads use the finalized block tag to avoid ingesting reorged data. type SequencerVerifier struct { - mu sync.Mutex + mu sync.Mutex history []bindings.L1SequencerHistoryRecord cursor sequencerCursor @@ -127,7 +127,7 @@ func (c *SequencerVerifier) syncHistory() error { return nil } -// refreshLoop polls L1 until ctx is cancelled. +// refreshLoop polls L1 until ctx is canceled. // Uses exponential backoff (10s -> 20s -> ... -> 5min) while history is empty, // then switches to the normal 5-minute interval once loaded. func (c *SequencerVerifier) refreshLoop(ctx context.Context) { diff --git a/ops/docker-sequencer-test/Dockerfile.l2-node-test b/ops/docker-sequencer-test/Dockerfile.l2-node-test index 04414cc53..4358089fe 100644 --- a/ops/docker-sequencer-test/Dockerfile.l2-node-test +++ b/ops/docker-sequencer-test/Dockerfile.l2-node-test @@ -16,7 +16,6 @@ COPY ./morph/node/go.mod ./morph/node/go.sum /polyrepo/morph/node/ COPY ./morph/common/go.mod ./morph/common/go.sum /polyrepo/morph/common/ COPY ./morph/bindings/go.mod ./morph/bindings/go.sum /polyrepo/morph/bindings/ COPY ./morph/contracts/go.mod ./morph/contracts/go.sum /polyrepo/morph/contracts/ -COPY ./morph/oracle/go.mod ./morph/oracle/go.sum /polyrepo/morph/oracle/ COPY ./morph/tx-submitter/go.mod ./morph/tx-submitter/go.sum /polyrepo/morph/tx-submitter/ COPY ./morph/ops/l2-genesis/go.mod ./morph/ops/l2-genesis/go.sum /polyrepo/morph/ops/l2-genesis/ COPY ./morph/ops/tools/go.mod ./morph/ops/tools/go.sum /polyrepo/morph/ops/tools/ diff --git a/ops/docker-sequencer-test/Dockerfile.tx-submitter-test b/ops/docker-sequencer-test/Dockerfile.tx-submitter-test index c38575bd5..09bdf7873 100644 --- a/ops/docker-sequencer-test/Dockerfile.tx-submitter-test +++ b/ops/docker-sequencer-test/Dockerfile.tx-submitter-test @@ -11,7 +11,6 @@ COPY ./morph/node/go.mod ./morph/node/go.sum /polyrepo/morph/node/ COPY ./morph/common/go.mod ./morph/common/go.sum /polyrepo/morph/common/ COPY ./morph/bindings/go.mod ./morph/bindings/go.sum /polyrepo/morph/bindings/ COPY ./morph/contracts/go.mod ./morph/contracts/go.sum /polyrepo/morph/contracts/ -COPY ./morph/oracle/go.mod ./morph/oracle/go.sum /polyrepo/morph/oracle/ COPY ./morph/tx-submitter/go.mod ./morph/tx-submitter/go.sum /polyrepo/morph/tx-submitter/ COPY ./morph/ops/l2-genesis/go.mod ./morph/ops/l2-genesis/go.sum /polyrepo/morph/ops/l2-genesis/ COPY ./morph/ops/tools/go.mod ./morph/ops/tools/go.sum /polyrepo/morph/ops/tools/ diff --git a/ops/docker-sequencer-test/run-test.sh b/ops/docker-sequencer-test/run-test.sh index e36e0b572..68484129f 100755 --- a/ops/docker-sequencer-test/run-test.sh +++ b/ops/docker-sequencer-test/run-test.sh @@ -340,7 +340,7 @@ start_l2_test() { # Start L2 execution nodes log_info "Starting L2 execution nodes..." - $COMPOSE_CMD up -d morph-el-0 morph-el-1 morph-el-2 morph-el-3 sentry-geth-0 + $COMPOSE_CMD up -d morph-el-0 morph-el-1 morph-el-2 morph-el-3 sentry-el-0 sleep 5 From fa3336633301496dafc946c2b210058b00dae181 Mon Sep 17 00:00:00 2001 From: "allen.wu" Date: Wed, 10 Jun 2026 10:02:29 +0800 Subject: [PATCH 2/2] docs(hakeeper): explain why RPC backend does not propagate ctx The APIBackend methods take context.Context only to satisfy the go-ethereum JSON-RPC handler signature; the underlying hashicorp/raft membership and leadership ops are synchronous and not context-cancellable, and already carry their own raftTimeout. Document this so reviewers don't flag the unused ctx as a bug. Co-Authored-By: Claude Opus 4.7 (1M context) --- node/hakeeper/rpc/backend.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/node/hakeeper/rpc/backend.go b/node/hakeeper/rpc/backend.go index 1c9736ae0..bb1be40b8 100644 --- a/node/hakeeper/rpc/backend.go +++ b/node/hakeeper/rpc/backend.go @@ -7,6 +7,14 @@ import ( ) // APIBackend implements API, delegating to a ConsensusAdapter. +// +// Every method takes a context.Context to satisfy the go-ethereum JSON-RPC +// server's handler signature, but intentionally does not propagate it. The +// ConsensusAdapter wraps hashicorp/raft, whose membership and leadership +// operations are synchronous and not context-cancellable; those calls already +// enforce their own bounded deadline (raftTimeout) at the adapter layer, so +// there is nothing a caller-supplied ctx could cancel mid-operation. This is +// deliberate, not an oversight — do not "fix" it by threading ctx through. type APIBackend struct { log log.Logger cons ConsensusAdapter