Skip to content

Conversation

@julienrbrt
Copy link
Member

Overview

ref: #2906

Add based block time based on epoch da time.

DO NOT MERGE YET, based on #2908 to avoid conflicts later.

@github-actions
Copy link
Contributor

github-actions bot commented Dec 5, 2025

The latest Buf updates on your PR. Results from workflow CI / buf-check (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed⏩ skipped✅ passed✅ passedDec 10, 2025, 9:35 AM

@claude
Copy link
Contributor

claude bot commented Dec 5, 2025

Claude finished @julienrbrt's task in 2m 36s —— View job


Comprehensive Code Review

I've completed a thorough review of the based batch time implementation. Here are my findings:


Summary

This PR successfully integrates DA layer block timestamps into the based sequencer's batching process, replacing placeholder timestamps with actual DA block times. The implementation is well-designed with strong test coverage (91.66%). The timestamp calculation strategy using millisecond offsets ensures deterministic, strictly increasing block times when DA epochs span multiple blocks.


Code Quality & Architecture ⭐⭐⭐⭐½

Strengths:

  • Clean interface design: The ForcedInclusionEvent struct cleanly extends to include Timestamp field
  • Proper separation of concerns: DA client retrieval, forced inclusion processing, and sequencer logic are well-separated
  • Idiomatic Go: Code follows Go conventions with proper error wrapping, context usage, and struct initialization
  • Deterministic design: The timestamp calculation daEndTime - (remainingTxs * 1ms) provides predictable, monotonic timestamps

Observations:

  1. Timestamp calculation logic (sequencers/based/sequencer.go:136-140): The implementation correctly handles partial batches by calculating remaining transactions. This ensures deterministic ordering when a single DA epoch must be split across multiple blocks.

  2. Zero value handling (sequencers/based/sequencer.go:162): Returns time.Time{} (zero value) when there's a "height from future" scenario. This is appropriate and handled correctly in the calling code.

Minor Suggestion:

  • Consider documenting the timestamp calculation strategy more explicitly in code comments, particularly why millisecond granularity was chosen and how it handles edge cases.

Error Handling & Resilience ⭐⭐⭐⭐⭐

Strengths:

  • Proper error wrapping with context using fmt.Errorf("%w", ...)
  • Graceful handling of ErrHeightFromFuture with backoff semantics
  • Comprehensive error type checking with errors.Is()
  • Proper context propagation throughout the call chain

Specific observations:

  1. DA availability handling (sequencers/based/sequencer.go:163-169): Correctly handles future heights without erroring, allowing the system to wait for DA block production. Returns zero timestamp which is safely handled.

  2. Checkpoint persistence (sequencers/based/sequencer.go:131-133): Errors during checkpoint save are properly propagated, preventing data loss scenarios.

  3. Partial epoch processing (block/internal/da/forced_inclusion_retriever.go:103-136): Uses errors.Join() to collect multiple errors during epoch processing, ensuring visibility into partial failures.


Security ⭐⭐⭐⭐⭐

Strengths:

  • No credential exposure or secret leakage
  • Input validation on blob sizes against maxBytes limit
  • Proper bounds checking in array access patterns
  • UTC normalization for timestamps prevents timezone manipulation

Observations:

  1. Size validation (sequencers/based/sequencer.go:179-186): Properly validates forced inclusion blob sizes before processing, preventing oversized blobs from causing issues. Logs warnings appropriately.

  2. Timestamp integrity (block/internal/da/forced_inclusion_retriever.go:163-165): Takes the latest timestamp from processed blobs, ensuring timestamp monotonicity within epochs.

No security concerns identified.


Performance & Resource Efficiency ⭐⭐⭐⭐

Strengths:

  • Efficient caching of currentBatchTxs to avoid redundant DA fetches
  • Proper use of atomic operations for daHeight access
  • Batched blob retrieval with configurable batch sizes
  • Pre-allocated slices with capacity hints

Observations:

  1. Memory efficiency (sequencers/based/sequencer.go:210-224): Pre-allocates result slice and tracks totalBytes to avoid over-allocation.

  2. Concurrent access (sequencers/based/sequencer.go:34,248-255): Uses atomic.Uint64 for thread-safe DA height access, though the sequencer appears to be single-threaded in practice based on checkpoint semantics.

Minor optimization opportunity:

  • The ForceIncludedMask allocation (sequencers/based/sequencer.go:227-230) could use make([]bool, len(result)) with a loop setting all to true, though the current approach is more explicit about intent.

Testing & Reliability ⭐⭐⭐⭐⭐

Strengths:

  • Comprehensive test coverage (91.66% with only 1 line missing)
  • Excellent edge case coverage including:
    • Empty DA batches
    • Partial batches across multiple blocks
    • MaxBytes constraints
    • Checkpoint persistence and recovery
    • Error scenarios (not configured, future heights)
    • Timestamp adjustment with various batch sizes
  • Table-driven tests for client operations
  • Proper use of mocks with expectation verification

Specific praise:

  1. Timestamp testing (sequencers/based/sequencer_test.go:544-681): Three dedicated tests cover timestamp calculation for:

    • Full batches (all transactions consumed)
    • Partial batches (some transactions remaining)
    • Empty batches

    These tests validate the critical determinism requirement.

  2. Batched retrieval tests (block/internal/da/client_test.go:593-748): Comprehensive testing of the new batching mechanism including:

    • Full batches
    • Partial batches
    • Error handling mid-batch
  3. Checkpoint recovery (sequencers/based/sequencer_test.go:438-481): Tests demonstrate proper crash recovery with checkpoint restoration.

Coverage gap:
The one missing line (91.66% coverage) appears to be in error path logging. This is acceptable for a PR at this stage.


Documentation & Developer Experience ⭐⭐⭐⭐

Strengths:

  • Clear code comments explaining timestamp calculation strategy
  • Descriptive variable names (currentDAEndTime, remainingTxs)
  • Helpful log messages at appropriate levels
  • Test names clearly describe scenarios

Areas for enhancement:

  1. The timestamp calculation logic at sequencers/based/sequencer.go:136-140 could benefit from a more detailed comment explaining:

    • Why millisecond granularity is used
    • How this ensures strictly increasing timestamps
    • Edge case behavior when all transactions are consumed
  2. Consider adding a comment in ForcedInclusionEvent struct explaining that Timestamp represents the DA block timestamp of the epoch end.


Specific Code Review Comments

sequencers/based/sequencer.go:40-41

The addition of currentDAEndTime time.Time as a cached field is appropriate. Consider using var currentDAEndTime time.Time pattern for clarity about zero-value initialization (as suggested by reviewer @alpe).

sequencers/based/sequencer.go:136-140

remainingTxs := uint64(len(s.currentBatchTxs)) - s.checkpoint.TxIndex
timestamp := s.currentDAEndTime.Add(-time.Duration(remainingTxs) * time.Millisecond)

This is clever and correct. The timestamp calculation ensures:

  • When all txs are consumed (remainingTxs = 0): timestamp = daEndTime
  • When some remain: timestamp is earlier, preserving ordering

Suggestion: Add a comment explaining this deterministic property for future maintainers.

block/internal/da/forced_inclusion_retriever.go:163-165

if result.Timestamp.After(event.Timestamp) {
    event.Timestamp = result.Timestamp
}

Good approach to take the latest timestamp across the epoch. This handles multi-height epochs correctly.

block/internal/da/client_test.go:747

Per @gemini-code-assist's comment, this assertion could be stronger:

assert.Assert(t, result.Message != "")

Could be:

assert.ErrorContains(t, errors.New(result.Message), "network error in batch 2")

However, the current assertion is acceptable for testing that an error message exists.


Review of Discussion Points

Re: Timestamp jitter based on tx index (from review comments):
The current implementation using daEndTime - (remainingTxs * 1ms) already provides deterministic, strictly increasing timestamps within an epoch. The latest commit (e7cd0af "proper implementation") appears to address @alpe's feedback about deterministic, incremental block times.

Re: Empty time.Time initialization:
Using time.Time{} for zero-value initialization is idiomatic Go. While var t time.Time is more explicit, both are acceptable. The current code is consistent throughout.


Final Assessment

Overall Rating: ⭐⭐⭐⭐½ (4.5/5)

This is high-quality production-ready code with excellent test coverage and thoughtful error handling. The implementation correctly addresses the requirement for deterministic DA-based timestamps while maintaining backward compatibility and robustness.

Recommendations:

  1. Approve for merge (after PR refactor(sequencers): implement batch position persistance #2908 is merged, as noted)
  2. Consider adding inline documentation for the timestamp calculation strategy
  3. The one missing line of coverage is acceptable and likely in error logging

The code demonstrates strong Go idioms, comprehensive testing, and careful consideration of edge cases. The timestamp calculation approach is elegant and maintains deterministic ordering properties critical for blockchain sequencing.


@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @julienrbrt, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the based sequencer by integrating the Data Availability (DA) layer's block timestamp directly into the batch creation process. This change ensures that each generated batch is associated with a precise and verifiable timestamp from the DA layer, improving the determinism and reliability of the sequencer's operation. Additionally, new tests have been introduced to thoroughly validate the DA client's data retrieval capabilities, including batching mechanisms.

Highlights

  • DA Block Timestamp Integration: The Data Availability (DA) layer's block timestamp is now retrieved and propagated through the fetchNextDAEpoch function to the GetNextBatch method in the based sequencer.
  • Deterministic Batch Timing: The GetNextBatchResponse now includes the actual DA block timestamp, replacing a placeholder, which ensures deterministic timing for sequencer batches.
  • Forced Inclusion Event Enhancement: The ForcedInclusionEvent struct has been updated to include a Timestamp field, which is populated and updated during the processing of forced inclusion blobs.
  • Expanded DA Client Testing: New unit tests have been added for the DA client's retrieval methods, specifically RetrieveHeaders, RetrieveData, and Retrieve with batching, covering various scenarios including partial batches and error handling.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@julienrbrt julienrbrt mentioned this pull request Dec 5, 2025
13 tasks
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

The pull request introduces changes to incorporate the Data Availability (DA) block timestamp into the batching process for the BasedSequencer. This addresses a previous TODO to use the DA block timestamp for determinism. The ForcedInclusionEvent struct now includes a Timestamp field, which is populated during the retrieval of forced inclusion blobs. The BasedSequencer's fetchNextDAEpoch method has been updated to return this timestamp, which is then used in the GetNextBatchResponse. New test cases have been added to client_test.go to cover batch retrieval scenarios, and existing tests in forced_inclusion_retriever_test.go have been updated to assert on the new timestamp field. The changes are well-implemented and improve the determinism of the sequencer.

return &coresequencer.GetNextBatchResponse{
Batch: batch,
Timestamp: time.Time{}, // TODO(@julienrbrt): we need to use DA block timestamp for determinism
Timestamp: t,
Copy link
Member Author

Choose a reason for hiding this comment

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

We currently want strictly increasing time and not the same time. So maybe we can have a jitter based on the tx index (so if it doesn't fit all in the same block the time is the same for everyone)

Copy link
Member Author

Choose a reason for hiding this comment

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

What do you think @alpe?

Copy link
Contributor

Choose a reason for hiding this comment

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

The block time should be incremental and deterministic, indeed. Either some logic based on the tx index or a new block counter per epoch comes to my mind. Not sure if it makes sense to use block time here as well. In any case, the time of the block must not overlap with the the next epoch. I hope this helps

Copy link
Member Author

Choose a reason for hiding this comment

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

Yep, did that already. Have you seen the last commit?

@julienrbrt julienrbrt removed the blocked label Dec 5, 2025
@julienrbrt julienrbrt marked this pull request as draft December 5, 2025 13:22
@codecov
Copy link

codecov bot commented Dec 5, 2025

Codecov Report

❌ Patch coverage is 91.66667% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 65.88%. Comparing base (f1aa2cb) to head (5bffe1d).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
sequencers/based/sequencer.go 90.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2911      +/-   ##
==========================================
+ Coverage   65.73%   65.88%   +0.14%     
==========================================
  Files          87       87              
  Lines        7985     7990       +5     
==========================================
+ Hits         5249     5264      +15     
+ Misses       2162     2153       -9     
+ Partials      574      573       -1     
Flag Coverage Δ
combined 65.88% <91.66%> (+0.14%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@julienrbrt julienrbrt force-pushed the julien/based-block-time branch from 5f2b4af to e7cd0af Compare December 8, 2025 16:07
@julienrbrt julienrbrt marked this pull request as ready for review December 8, 2025 16:08
// If we have no cached transactions or we've consumed all from the current DA block,
// fetch the next DA epoch
daHeight := s.GetDAHeight()
t := time.Time{}
Copy link
Contributor

Choose a reason for hiding this comment

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

personal preference: prefer var t time.Time for an empty time to make it more readable

return &coresequencer.GetNextBatchResponse{
Batch: batch,
Timestamp: time.Time{}, // TODO(@julienrbrt): we need to use DA block timestamp for determinism
Timestamp: t,
Copy link
Contributor

Choose a reason for hiding this comment

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

The block time should be incremental and deterministic, indeed. Either some logic based on the tx index or a new block counter per epoch comes to my mind. Not sure if it makes sense to use block time here as well. In any case, the time of the block must not overlap with the the next epoch. I hope this helps

Base automatically changed from julien/persist-base to main December 8, 2025 16:53
@julienrbrt julienrbrt enabled auto-merge December 10, 2025 09:36
@julienrbrt julienrbrt added this pull request to the merge queue Dec 10, 2025
Merged via the queue into main with commit 0fa3370 Dec 10, 2025
25 checks passed
@julienrbrt julienrbrt deleted the julien/based-block-time branch December 10, 2025 09:48
alpe added a commit that referenced this pull request Dec 15, 2025
* main:
  chore: execute goimports to format the code (#2924)
  refactor(block)!: remove GetLastState from components (#2923)
  feat(syncing): add grace period for missing force txs inclusion (#2915)
  chore: minor improvement for docs (#2918)
  feat: DA Client remove interface part 2,  add client for celestia blob api   (#2909)
  chore: update rust deps (#2917)
  feat(sequencers/based): add based batch time (#2911)
  build(deps): Bump golangci/golangci-lint-action from 9.1.0 to 9.2.0 (#2914)
  refactor(sequencers): implement batch position persistance (#2908)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants