Skip to content

fix(l2): rollup store SQLite migration for v9 to v10 upgrade#6514

Merged
avilagaston9 merged 1 commit intomainfrom
fix/rollup-store-sqlite-migration-latest-sent
Apr 22, 2026
Merged

fix(l2): rollup store SQLite migration for v9 to v10 upgrade#6514
avilagaston9 merged 1 commit intomainfrom
fix/rollup-store-sqlite-migration-latest-sent

Conversation

@avilagaston9
Copy link
Copy Markdown
Contributor

Motivation

When upgrading an ethrex L2 from v9 to v10, the v10 binary panics on startup with:

Failed to create StoreRollup: SQLQueryError(SqliteFailure(1, "table latest_sent has 2 columns but 3 values were supplied"))

Description

v10 added a verified_at column to the latest_sent table. The DB_SCHEMA array runs as a single transaction during init_db(). On an existing v9 database, CREATE TABLE IF NOT EXISTS is a no-op (table already exists with 2 columns), but INSERT INTO latest_sent VALUES (0, 0, 0) fails because it supplies 3 values for a 2-column table. This rolls back the entire schema transaction, so the ALTER TABLE migration that adds the column never executes.

The fix uses explicit column names in the INSERT: INSERT INTO latest_sent (_id, batch) VALUES (0, 0). This works on both the old 2-column table and the new 3-column table (verified_at gets its DEFAULT 0).

How to Test

  1. Start an L2 with v9, let it commit a few batches
  2. Stop the L2, upgrade to v10
  3. Start v10 — should no longer panic on latest_sent

The DB_SCHEMA array runs as a single transaction during init_db(). On an
existing v9 database the latest_sent table has two columns (_id, batch),
but the INSERT statement tried to supply three values (including the new
verified_at column added in v10). This caused a SQLite error that rolled
back the entire schema transaction, preventing the subsequent ALTER TABLE
migration from ever executing.

By specifying column names in the INSERT, the statement works on both the
old 2-column table and the new 3-column table (verified_at gets its
DEFAULT 0). The ALTER TABLE migration then adds the column to existing
databases as intended.
@avilagaston9 avilagaston9 changed the title Fix rollup store SQLite migration for v9 to v10 upgrade fix(l2): rollup store SQLite migration for v9 to v10 upgrade Apr 21, 2026
@github-actions github-actions Bot added the L2 Rollup client label Apr 21, 2026
@avilagaston9 avilagaston9 force-pushed the fix/rollup-store-sqlite-migration-latest-sent branch from d59190e to bd11d24 Compare April 21, 2026 20:27
@avilagaston9 avilagaston9 marked this pull request as ready for review April 21, 2026 21:28
Copilot AI review requested due to automatic review settings April 21, 2026 21:28
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 21, 2026

Greptile encountered an error while reviewing this PR. Please reach out to support@greptile.com for assistance.

@github-actions
Copy link
Copy Markdown

🤖 Kimi Code Review

crates/l2/storage/src/store_db/sql.rs

This change follows SQL best practices by explicitly specifying column names rather than relying on positional ordering. This makes the schema initialization more robust against future column additions or reorderings.

The behavior is functionally equivalent since verified_at has a DEFAULT 0 constraint (line 49), but the explicit column list is more maintainable.

No issues found. This is a correct code quality improvement.


Automated review by Kimi (Moonshot AI) · kimi-k2.5 · custom prompt

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes an L2 rollup store SQLite migration issue encountered when upgrading an existing v9 database to a v10 binary by making the latest_sent seed insert compatible with both the old (2-column) and new (3-column) table layouts.

Changes:

  • Update the latest_sent seed statement to use explicit column names ((_id, batch)) so it doesn’t fail on pre-migration schemas.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@github-actions
Copy link
Copy Markdown

🤖 Claude Code Review

Now I have the full context. Here is my review:


Code Review: fix(l2): rollup store SQLite migration for v9 to v10 upgrade

Overview

A one-line targeted fix for a startup panic when upgrading an L2 node from v9 to v10. The DB_SCHEMA array executes as a single transaction in init_db(). The old positional INSERT INTO latest_sent VALUES (0, 0, 0) failed on existing v9 databases (which still have only 2 columns at that point), rolling back the entire schema transaction and preventing the subsequent ALTER TABLE migration from ever running.


The Fix

crates/l2/storage/src/store_db/sql.rs:50

-- Before
INSERT INTO latest_sent VALUES (0, 0, 0) ON CONFLICT(_id) DO NOTHING
-- After
INSERT INTO latest_sent (_id, batch) VALUES (0, 0) ON CONFLICT(_id) DO NOTHING

This is correct and minimal. Using explicit column names ensures the statement is valid against both the old 2-column schema and the new 3-column schema — verified_at gets its DEFAULT 0 on fresh installs, and the ALTER TABLE (which runs outside the transaction with its error intentionally ignored) handles the upgrade path.

All three cases are covered:

  • Fresh v10 install: 3-column table created, INSERT omits verified_at → uses DEFAULT 0. ALTER TABLE fails silently (column exists). ✓
  • v9→v10 upgrade: CREATE TABLE IF NOT EXISTS is a no-op, INSERT now works on the 2-column table, then ALTER TABLE successfully adds verified_at. ✓
  • v10 restart: ON CONFLICT(_id) DO NOTHING prevents a duplicate row. ✓

Observations and Concerns

Two other tables have the same latent risk.

Lines 48 and 57 use the same positional VALUES pattern:

-- line 48
INSERT INTO operation_count VALUES (0, 0, 0, 0) ON CONFLICT(_id) DO NOTHING
-- line 57
INSERT INTO latest_sent_to_aligned VALUES (0, 0, 0) ON CONFLICT(_id) DO NOTHING

If either of these tables gains a column in a future version, the same startup panic will recur. Applying explicit column names consistently to all three singleton-row tables (operation_count, latest_sent, latest_sent_to_aligned) would eliminate the class of bug entirely. This isn't blocking for this PR since those tables haven't changed, but it is worth a follow-up.

Silent error handling in init_db() (lines 112–117) is intentional but opaque.

let _ = self
    .execute("ALTER TABLE latest_sent ADD COLUMN verified_at INT DEFAULT 0", ())
    .await;

The let _ discard is the right choice here — it handles both "column already exists" (fresh install) and "column added" (upgrade) in one shot. However, it would also silently swallow real errors (disk full, permission issues, SQL typos). A tighter approach would be to check the error kind and only ignore SqliteFailure with code 1 (SQLITE_ERROR, i.e. "duplicate column name"). That said, given the simplicity of the statement and that a subsequent read of verified_at would fail loudly anyway, this is a low-priority concern.

No automated migration test.

The upgrade path (v9 schema → v10 binary) is exercised only by the manual steps in the PR description. A test that spins up an in-memory SQLite with the old 2-column latest_sent, then calls SQLStore::new / init_db, and asserts that verified_at is present and readable would catch regressions here and provide documentation of the migration invariant. Worth adding, but not a blocker.


Summary

The fix is correct, surgically minimal, and clearly explained. The root cause is accurately diagnosed. No new issues are introduced. The main actionable takeaways are:

  • Consider updating operation_count and latest_sent_to_aligned INSERTs to use explicit column names as a proactive follow-up.
  • Consider a migration integration test.

Verdict: Approve with the above as optional follow-up items.


Automated review by Claude (Anthropic) · sonnet · custom prompt

@github-actions
Copy link
Copy Markdown

🤖 Codex Code Review

No findings.

The change in sql.rs looks correct: inserting only (_id, batch) lets SQLite apply the verified_at INT DEFAULT 0 default on fresh databases, while remaining compatible with older databases that still had the pre-migration two-column latest_sent table before the later ALTER TABLE at sql.rs. That preserves the verified_at > 0 timeout guard behavior used by the proof sender at l1_proof_sender.rs.

I verified the SQL behavior directly with sqlite3 for both fresh-schema and old-schema-then-migrate cases; both produce the expected sentinel row (0, 0, 0). I could not run cargo test in this environment because rustup cannot create temp files on the read-only filesystem.


Automated review by OpenAI Codex · gpt-5.4 · custom prompt

@avilagaston9 avilagaston9 added this pull request to the merge queue Apr 22, 2026
Merged via the queue into main with commit b6250d0 Apr 22, 2026
100 of 114 checks passed
@avilagaston9 avilagaston9 deleted the fix/rollup-store-sqlite-migration-latest-sent branch April 22, 2026 19:20
avilagaston9 added a commit that referenced this pull request Apr 27, 2026
Brings in main commits since the prior merge: #6516 EIP-8025 compliance
(Electra-aligned ExecutionRequests typed container in NewPayloadRequest,
MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD corrected from 1 to 2,
to_encoded_requests() helper for EIP-7685 bytes, removal of
ExecutionPayloadHeader/NewPayloadRequestHeader, new byte-oriented
execution_program entrypoint that decodes the wire format internally and
returns valid: false instead of erroring on post-decode failures), #6463
BAL withdrawal reverse check (DB->BAL direction so a malicious builder
can't omit a withdrawal recipient from the BAL), #6505 Kademlia k-bucket
revert (PeerTableServer::spawn no longer takes a node_id), plus snap-sync
observability + dashboards (#6470), pivot-update crash fix (#6475),
weighted peer selection (#6428), txpool_contentFrom/txpool_inspect RPC
(#6446), block-by-block exec fallback (#6464), Amsterdam EELS branch pin
(#6495), and rollup store SQLite v9->v10 migration (#6514).

Conflict resolutions:
- crates/common/types/stateless_ssz.rs: this branch had already moved
  the EIP-8025 SSZ types out of crates/common/types/eip8025_ssz.rs into
  stateless_ssz.rs and tucked the native-rollup containers below them.
  Kept that layout, applied #6516's content updates to the EIP-8025
  section (renamed spec-limit constants, ExecutionRequests typed
  container with to_encoded_requests, dropped header types and their
  tests), pulled in the EncodedRequests import, and kept both the new
  test_execution_requests_to_encoded_bytes and the branch's stateless
  round-trip tests.
- crates/guest-program/src/l1/program.rs: adopted #6516's new
  execution_program(bytes: &[u8], crypto) API with the internal
  decode_eip8025 call, the validate_eip8025_execution helper, and the
  decode-failure test. Rewrote all `eip-8025` feature gates as
  `experimental-devnet` and all `eip8025_ssz::` paths as
  `stateless_ssz::` to match this branch's renames.
- crates/guest-program/bin/{sp1,risc0,zisk,openvm}/src/main.rs: applied
  #6516's simplification (drop decode_eip8025 import, pass &input
  straight to execution_program) under the experimental-devnet feature
  gate. Also flipped the rkyv::rancor::Error import gate from the old
  `eip-8025` name to `experimental-devnet` so the non-devnet build still
  has the import it needs.
- crates/prover/src/backend/exec.rs: kept #6516's updated comment ("raw
  input bytes" instead of "(NewPayloadRequest, ExecutionWitness)") under
  the experimental-devnet feature gate.

Auto-merged regions checked: crates/vm/backends/levm/mod.rs picked up
all of #6463's Part B (DB->BAL) reverse check intact, and
cmd/ethrex/l2/initializers.rs picked up #6505's PeerTableServer::spawn
signature change. Verified cargo fmt --all clean, cargo check --workspace
clean, cargo check --workspace --tests clean, and cargo check -p
ethrex-guest-program --features experimental-devnet --tests clean.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

L2 Rollup client

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

6 participants