Skip to content

Conversation

@tac0turtle
Copy link
Contributor

Add header storage and retrieval to DummyDA to support timestamp determinism in tests. This enables tests to use the same header retrieval pattern as the real DA client.

Changes:

  • Add Header struct with Height, Timestamp, and Time() method
  • Store headers with timestamps when blobs are submitted
  • Store headers when height ticker advances
  • Add GetHeaderByHeight method mirroring HeaderAPI.GetByHeight
  • Update Retrieve to use stored timestamps for consistency

Closes #2944

🤖 Generated with Claude Code

Add header storage and retrieval to DummyDA to support timestamp
determinism in tests. This enables tests to use the same header
retrieval pattern as the real DA client.

Changes:
- Add Header struct with Height, Timestamp, and Time() method
- Store headers with timestamps when blobs are submitted
- Store headers when height ticker advances
- Add GetHeaderByHeight method mirroring HeaderAPI.GetByHeight
- Update Retrieve to use stored timestamps for consistency

Closes #2944

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@github-actions
Copy link
Contributor

github-actions bot commented Dec 20, 2025

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

BuildFormatLintBreakingUpdated (UTC)
✅ passed⏩ skipped✅ passed✅ passedDec 23, 2025, 11:47 AM

@claude
Copy link
Contributor

claude bot commented Dec 20, 2025

Claude finished @tac0turtle's task in 2m 11s —— View job


Code Review Complete

I've thoroughly reviewed this PR that adds header support to DummyDA for timestamp determinism. Overall, the implementation is solid and addresses the previous review concerns about non-determinism. Here's my detailed feedback:


Positive Aspects ✅

1. Determinism Achieved

  • Previous reviews flagged time.Now() fallbacks in GetHeaderByHeight and Retrieve as non-deterministic
  • The current implementation correctly addresses this - no more fallback header creation
  • GetHeaderByHeight now returns appropriate errors when headers don't exist (lines 284-289)
  • Retrieve uses a zero-value timestamp when no header exists (line 136), which is deterministic

2. Clean Architecture

  • The Header struct (lines 18-29) properly mirrors the real DA client's structure
  • The Time() method maintains API compatibility with jsonrpc.Header
  • Headers are stored consistently in both Submit (lines 107-113) and StartHeightTicker (lines 236-241)

3. Thread Safety

  • Proper mutex usage around shared state (d.mu protects both blobs and headers maps)
  • Atomic operations for height management remain correct

Areas for Improvement

1. Missing Test Coverage 🔴 High Priority

The test/testda/ directory has no test files. For a test utility, this is problematic. I recommend adding dummy_test.go with tests covering:

// Key scenarios to test:
- GetHeaderByHeight returns header with correct timestamp after Submit
- GetHeaderByHeight returns header with correct timestamp after ticker advance
- GetHeaderByHeight returns ErrHeightFromFuture for future heights
- GetHeaderByHeight returns ErrBlobNotFound for past heights with no header
- Retrieve uses stored header timestamp (deterministic)
- Retrieve uses zero timestamp when header doesn't exist (deterministic)
- Concurrent access to Submit/GetHeaderByHeight (race detection)
- Reset clears headers map

This is especially important since the primary goal is determinism - tests should verify timestamps are consistent across multiple calls.

2. Inconsistent Error Handling in GetHeaderByHeight 🟡 Medium Priority

Location: test/testda/dummy.go:276-292

The error handling logic has a subtle issue:

if header == nil {
    currentHeight := d.height.Load()
    if height > currentHeight {
        return nil, datypes.ErrHeightFromFuture
    }
    return nil, datypes.ErrBlobNotFound  // Line 289
}

Problem: ErrBlobNotFound is semantically incorrect here. A "blob not found" error suggests the blob doesn't exist, but we're querying for a header. The issue is that we might have valid heights (≤ currentHeight) that simply have no header stored.

Scenarios to consider:

  • Height 1-5 exist with headers from Submit
  • Height 6-10 were added by ticker (have headers)
  • Height 11 is the current height (may or may not have a header)
  • Query for height 7 → should succeed ✅
  • Query for height 12 → should return ErrHeightFromFuture
  • Query for height 3 after Reset → should return... what? 🤔

Recommendation: Consider one of these approaches:

Option A: Return a more specific error

var ErrHeaderNotFound = errors.New("header: not found")

// In GetHeaderByHeight:
if height > currentHeight {
    return nil, datypes.ErrHeightFromFuture
}
return nil, ErrHeaderNotFound  // More accurate than ErrBlobNotFound

Option B: Document that missing headers for valid heights indicate an inconsistent state

// GetHeaderByHeight retrieves the header for the given DA height.
// Returns ErrHeightFromFuture if height > current height.
// Returns ErrBlobNotFound if the height is valid but no header was stored.
// Note: All heights should have headers if DummyDA is used correctly
// (either from Submit or StartHeightTicker).

I'd recommend Option A for semantic clarity.

3. Race Condition in GetHeaderByHeight 🟡 Medium Priority

Location: test/testda/dummy.go:279-292

func (d *DummyDA) GetHeaderByHeight(_ context.Context, height uint64) (*Header, error) {
    d.mu.Lock()
    header := d.headers[height]
    d.mu.Unlock()
    
    if header == nil {
        currentHeight := d.height.Load()  // ⚠️ Lock released before reading height
        if height > currentHeight {
            return nil, datypes.ErrHeightFromFuture
        }
        return nil, datypes.ErrBlobNotFound
    }
    return header, nil
}

Problem: There's a window between releasing d.mu and loading currentHeight where:

  1. Another goroutine could call Submit() or the ticker could fire
  2. A header could be created for the requested height
  3. We'd return an error even though the header now exists

Impact: Low in practice (test code, short time window), but violates the determinism goal.

Fix:

func (d *DummyDA) GetHeaderByHeight(_ context.Context, height uint64) (*Header, error) {
    d.mu.Lock()
    header := d.headers[height]
    currentHeight := d.height.Load()  // Read while holding lock
    d.mu.Unlock()
    
    if header == nil {
        if height > currentHeight {
            return nil, datypes.ErrHeightFromFuture
        }
        return nil, datypes.ErrBlobNotFound
    }
    return header, nil
}

4. Documentation Could Be More Explicit 🟢 Low Priority

Location: test/testda/dummy.go:31-32

Current:

// DummyDA is a test implementation of the DA client interface.
// It supports blob storage, height simulation, failure injection, and header retrieval.

Suggestion: Emphasize the determinism guarantee:

// DummyDA is a test implementation of the DA client interface.
// It provides deterministic timestamp behavior by storing headers with timestamps
// when blobs are submitted or heights advance, enabling tests to use the same
// header retrieval pattern as real DA clients.

5. Potential Memory Leak in Long-Running Tests 🟢 Low Priority

The headers map grows unbounded. For tests that run the height ticker for extended periods, this could consume significant memory. Consider:

// Option: Add a cleanup mechanism
func (d *DummyDA) PruneHeadersBefore(height uint64) {
    d.mu.Lock()
    defer d.mu.Unlock()
    for h := range d.headers {
        if h < height {
            delete(d.headers, h)
        }
    }
}

However, since this is test code and Reset() exists, this is likely not a practical concern.


Security & Performance

Security: ✅ No concerns. This is test-only code with no external inputs or sensitive data.

Performance: ✅ Appropriate for test code:

  • O(1) header lookups
  • Minimal lock contention
  • Atomic operations for height are efficient

Concurrency: ✅ Generally good, but see race condition note above.


Summary

This PR successfully achieves its primary goal of adding timestamp determinism to DummyDA. The previous non-determinism issues flagged by Gemini have been properly resolved. The implementation is clean and mirrors the real DA client API well.

Required before merge:

  • Add unit tests to verify determinism (no test file exists currently)

Recommended improvements:

  • Fix the race condition in GetHeaderByHeight (read currentHeight under lock)
  • Use a more semantically appropriate error than ErrBlobNotFound for missing headers

Nice to have:

  • Enhanced documentation emphasizing determinism guarantees

The code quality is good and follows Go idioms. With tests added and the race condition fixed, this will be a solid addition to the test infrastructure.


@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @tac0turtle, 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 significantly enhances the DummyDA test implementation by integrating header storage and retrieval capabilities. The core objective is to achieve timestamp determinism within test scenarios, enabling tests to interact with the DummyDA in a manner that closely mimics the behavior of a real Data Availability (DA) client's header API. This ensures that test environments accurately reflect production system behavior concerning block header information, particularly timestamps, leading to more robust and reliable testing.

Highlights

  • Header Struct Introduction: A new Header struct has been defined within DummyDA to encapsulate height and timestamp information, mirroring the structure used by real Data Availability (DA) clients.
  • Header Storage and Retrieval: The DummyDA now stores Header objects with timestamps when blobs are submitted and when the height ticker advances. A GetHeaderByHeight method has been added to allow retrieval of these stored headers.
  • Timestamp Determinism in Tests: The Retrieve method has been updated to utilize the stored timestamps from headers, ensuring consistent and deterministic timestamp behavior in test environments, aligning with real DA client interactions.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

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.

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

This pull request aims to add header storage and retrieval to DummyDA to improve timestamp determinism in tests. The changes are well-structured, introducing a Header type and integrating it into blob submission, height ticking, and data retrieval. However, I've identified a couple of areas in the new and modified functions where time.Now() is used as a fallback. This undermines the goal of determinism. My review provides specific suggestions to address these points and ensure the test utility is fully deterministic, aligning with the pull request's objective.

Comment on lines 288 to 293
if height <= currentHeight && height > 0 {
return &Header{
Height: height,
Timestamp: time.Now(),
}, nil
}
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The goal of this PR is to introduce timestamp determinism in tests, but this logic does the opposite when a header is not found for a valid height. By creating a new header with time.Now(), any test that relies on this function will become non-deterministic, as each call can return a different timestamp for the same height.

Furthermore, the function's documentation comment says "Returns nil if no header exists for the given height", but this implementation contradicts it by generating a header on the fly.

To ensure determinism and align with the documentation, you should not generate a header here. If a header for an empty block is expected, it should have been created by the StartHeightTicker mechanism. If it's not present, it means the block doesn't exist in the test's context, so returning nil is the correct and deterministic behavior.

if height <= currentHeight && height > 0 {
	// To ensure determinism, return nil. A header for an empty block
	// should be created by the height ticker if needed.
	return nil, nil
}

@codecov
Copy link

codecov bot commented Dec 20, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 59.15%. Comparing base (d386df5) to head (648aa39).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2946      +/-   ##
==========================================
- Coverage   59.18%   59.15%   -0.04%     
==========================================
  Files          90       90              
  Lines        8627     8632       +5     
==========================================
  Hits         5106     5106              
- Misses       2940     2944       +4     
- Partials      581      582       +1     
Flag Coverage Δ
combined 59.15% <ø> (-0.04%) ⬇️

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.

alpe
alpe previously approved these changes Dec 22, 2025
Copy link
Contributor

@alpe alpe left a comment

Choose a reason for hiding this comment

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

Good start. IMHO the GetHeaderByHeight method is a bit inconsistent but I don't want to block you on this.

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
@tac0turtle tac0turtle requested a review from alpe December 22, 2025 13:41
alpe
alpe previously approved these changes Dec 23, 2025
Copy link
Contributor

@alpe alpe left a comment

Choose a reason for hiding this comment

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

Perfect, thanks for the update

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.

Support header getByheader in dummy da

3 participants