diff --git a/test/testda/dummy.go b/test/testda/dummy.go index ecfa5cc85..633bf1cf9 100644 --- a/test/testda/dummy.go +++ b/test/testda/dummy.go @@ -15,13 +15,27 @@ const ( DefaultMaxBlobSize = 2 * 1024 * 1024 ) +// Header contains DA layer header information for a given height. +// This mirrors the structure used by real DA clients like Celestia. +type Header struct { + Height uint64 + Timestamp time.Time +} + +// Time returns the block time from the header. +// This mirrors the jsonrpc.Header.Time() method. +func (h *Header) Time() time.Time { + return h.Timestamp +} + // DummyDA is a test implementation of the DA client interface. -// It supports blob storage, height simulation, and failure injection. +// It supports blob storage, height simulation, failure injection, and header retrieval. type DummyDA struct { mu sync.Mutex height atomic.Uint64 maxBlobSz uint64 blobs map[uint64]map[string][][]byte // height -> namespace -> blobs + headers map[uint64]*Header // height -> header (with timestamp) failSubmit atomic.Bool tickerMu sync.Mutex @@ -50,6 +64,7 @@ func New(opts ...Option) *DummyDA { d := &DummyDA{ maxBlobSz: DefaultMaxBlobSize, blobs: make(map[uint64]map[string][][]byte), + headers: make(map[uint64]*Header), } for _, opt := range opts { opt(d) @@ -81,6 +96,7 @@ func (d *DummyDA) Submit(_ context.Context, data [][]byte, _ float64, namespace blobSz += uint64(len(b)) } + now := time.Now() d.mu.Lock() height := d.height.Add(1) if d.blobs[height] == nil { @@ -88,6 +104,13 @@ func (d *DummyDA) Submit(_ context.Context, data [][]byte, _ float64, namespace } nsKey := string(namespace) d.blobs[height][nsKey] = append(d.blobs[height][nsKey], data...) + // Store header with timestamp for this height + if d.headers[height] == nil { + d.headers[height] = &Header{ + Height: height, + Timestamp: now, + } + } d.mu.Unlock() return datypes.ResultSubmit{ @@ -96,7 +119,7 @@ func (d *DummyDA) Submit(_ context.Context, data [][]byte, _ float64, namespace Height: height, BlobSize: blobSz, SubmittedCount: uint64(len(data)), - Timestamp: time.Now(), + Timestamp: now, }, } } @@ -109,6 +132,11 @@ func (d *DummyDA) Retrieve(_ context.Context, height uint64, namespace []byte) d if byHeight != nil { blobs = byHeight[string(namespace)] } + // Get timestamp from header if available, otherwise use current time + var timestamp time.Time + if header := d.headers[height]; header != nil { + timestamp = header.Timestamp + } d.mu.Unlock() if len(blobs) == 0 { @@ -117,7 +145,7 @@ func (d *DummyDA) Retrieve(_ context.Context, height uint64, namespace []byte) d Code: datypes.StatusNotFound, Height: height, Message: datypes.ErrBlobNotFound.Error(), - Timestamp: time.Now(), + Timestamp: timestamp, }, } } @@ -128,7 +156,7 @@ func (d *DummyDA) Retrieve(_ context.Context, height uint64, namespace []byte) d Code: datypes.StatusSuccess, Height: height, IDs: ids, - Timestamp: time.Now(), + Timestamp: timestamp, }, Data: blobs, } @@ -202,7 +230,16 @@ func (d *DummyDA) StartHeightTicker(interval time.Duration) func() { for { select { case <-ticker.C: - d.height.Add(1) + now := time.Now() + height := d.height.Add(1) + d.mu.Lock() + if d.headers[height] == nil { + d.headers[height] = &Header{ + Height: height, + Timestamp: now, + } + } + d.mu.Unlock() case <-stopCh: return } @@ -219,11 +256,12 @@ func (d *DummyDA) StartHeightTicker(interval time.Duration) func() { } } -// Reset clears all stored blobs and resets the height. +// Reset clears all stored blobs, headers, and resets the height. func (d *DummyDA) Reset() { d.mu.Lock() d.height.Store(0) d.blobs = make(map[uint64]map[string][][]byte) + d.headers = make(map[uint64]*Header) d.failSubmit.Store(false) d.mu.Unlock() @@ -234,3 +272,21 @@ func (d *DummyDA) Reset() { } d.tickerMu.Unlock() } + +// GetHeaderByHeight retrieves the header for the given DA height. +// This mirrors the HeaderAPI.GetByHeight method from the real DA client. +// Returns nil if no header exists for the given height. +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() + if height > currentHeight { + return nil, datypes.ErrHeightFromFuture + } + return nil, datypes.ErrBlobNotFound + } + return header, nil +}