Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 62 additions & 6 deletions test/testda/dummy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -81,13 +96,21 @@ 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 {
d.blobs[height] = make(map[string][][]byte)
}
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{
Expand All @@ -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,
},
}
}
Expand All @@ -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 {
Expand All @@ -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,
},
}
}
Expand All @@ -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,
}
Expand Down Expand Up @@ -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
}
Expand All @@ -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()

Expand All @@ -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
}
Loading