Skip to content

Commit 58671bf

Browse files
committed
ipni: drop ad-fetch tracking, return adCid in piece status instead
1 parent e0294b0 commit 58671bf

5 files changed

Lines changed: 55 additions & 125 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
CREATE TABLE IF NOT EXISTS ipni_ad_fetches (
2+
ad_cid TEXT NOT NULL,
3+
fetched_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
4+
);
5+
6+
CREATE INDEX IF NOT EXISTS ipni_ad_fetches_ad_cid_time ON ipni_ad_fetches(ad_cid, fetched_at DESC);
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- ipni_ad_fetches recorded raw ad-body fetches, which only proves an indexer
2+
-- pulled the ad, not that it finished processing it. The piece-status
3+
-- endpoint now returns the ad's CID directly so callers can check sync status
4+
-- against the indexer themselves, and "advertised" is tracked in memory by
5+
-- the ipni-provider process (see market/ipni/ipni-provider), so nothing here
6+
-- needs replacing.
7+
DROP TABLE IF EXISTS ipni_ad_fetches;

market/ipni/ipni-provider/ipni-provider.go

Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ const IPNIRoutePath = "/ipni-provider/"
5151
// IPNIPath is a constant that represents the path for IPNI API requests.
5252
const IPNIPath = "/ipni/v1/ad/"
5353

54-
const PublishInterval = 5 * time.Second
54+
const PublishInterval = 1 * time.Second
5555

5656
const (
5757
PDPv0ProviderType = "PDP_v0"
@@ -460,8 +460,6 @@ func (p *Provider) handleGet(w http.ResponseWriter, r *http.Request) {
460460
if err != nil {
461461
log.Errorw("failed to write HTTP response", "err", err)
462462
}
463-
// Log advertisement fetch for indexing status tracking
464-
go p.logPDPFetch(providerID, b.String())
465463
return
466464
case ipnisync.CidSchemaEntryChunk:
467465
content = "entry"
@@ -522,26 +520,10 @@ func (p *Provider) handleGet(w http.ResponseWriter, r *http.Request) {
522520
if err != nil {
523521
log.Errorw("failed to write HTTP response", "err", err)
524522
}
525-
// Log advertisement fetch for indexing status tracking
526-
go p.logPDPFetch(providerID, b.String())
527523
return
528524
}
529525
}
530526

531-
func (p *Provider) logPDPFetch(peer, b string) {
532-
p.mu.RLock()
533-
info, ok := p.providerInfos[peer]
534-
p.mu.RUnlock()
535-
if !ok || info.SPID > 0 {
536-
return
537-
}
538-
logCtx := context.Background()
539-
_, err := p.db.Exec(logCtx, `INSERT INTO ipni_ad_fetches (ad_cid, fetched_at) VALUES ($1, NOW())`, b)
540-
if err != nil {
541-
log.Warnw("failed to log ad fetch", "ad_cid", b, "err", err)
542-
}
543-
}
544-
545527
// Routes sets up the routes for the IPNI provider.
546528
// It registers a handler function for the GET request at the IPNIRoutePath.
547529
// The handler function is provided by the Provider struct.
@@ -589,22 +571,6 @@ func (p *Provider) startPublishing(ctx context.Context) {
589571
p.announceURLs = urls
590572
}
591573

592-
// Populated latest head cid from the ipni_head table
593-
p.mu.RLock()
594-
peers := make([]string, 0, len(p.providerInfos))
595-
for pr := range p.providerInfos {
596-
peers = append(peers, pr)
597-
}
598-
p.mu.RUnlock()
599-
for _, provider := range peers {
600-
c, err := p.getHeadCID(ctx, provider)
601-
if err != nil {
602-
log.Errorw("failed to get head CID", "provider", provider, "error", err)
603-
continue
604-
}
605-
p.latest[provider] = c
606-
}
607-
608574
ticker := time.NewTicker(PublishInterval)
609575

610576
for {

pdp/README.md

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -162,9 +162,12 @@ All endpoints are rooted at `/pdp`.
162162
"pieceCid": "<piece-CID-v2>",
163163
"status": "<status>",
164164
"indexed": <boolean>,
165+
"indexedAt": "<RFC3339-timestamp-or-omitted>",
166+
"adCreated": <boolean>,
167+
"adCreatedAt": "<RFC3339-timestamp-or-omitted>",
168+
"adCid": "<ad-CID-or-omitted>",
165169
"advertised": <boolean>,
166-
"retrieved": <boolean>,
167-
"retrievedAt": "<RFC3339-timestamp-or-omitted>"
170+
"advertisedAt": "<RFC3339-timestamp-or-omitted>"
168171
}
169172
```
170173

@@ -174,12 +177,14 @@ All endpoints are rooted at `/pdp`.
174177
- `"pending"` – Not yet indexed.
175178
- `"indexing"` – CAR indexing task is in progress.
176179
- `"creating_ad"` – IPNI advertisement is being created.
177-
- `"announced"` – Advertisement published to IPNI network.
178-
- `"retrieved"` – Piece has been retrieved by a client.
180+
- `"announced"` – The advertisement row exists. Not a guarantee it's been broadcast (see `advertised`) or indexed (see `adCid`).
179181
- `indexed`: Whether the piece has been indexed and is ready for IPNI.
180-
- `advertised`: Whether an IPNI advertisement has been published.
181-
- `retrieved`: Whether the piece has been retrieved by a client.
182-
- `retrievedAt`: Timestamp of last retrieval (omitted if never retrieved).
182+
- `indexedAt`: Timestamp CAR indexing completed (omitted if not yet indexed).
183+
- `adCreated`: Whether an IPNI advertisement has been created for this piece.
184+
- `adCreatedAt`: Timestamp the advertisement was created (omitted if not yet created).
185+
- `adCid`: This piece's advertisement CID, once created. Curio doesn't check whether an indexer finished processing it - callers can, e.g. `GET https://cid.contact/sync/status/ad/{adCid}`.
186+
- `advertised`: Whether the provider has sent an HTTP announce covering this ad.
187+
- `advertisedAt`: Approximate, not a fixed record: it's the last known successful announce time for the *provider*, not this ad specifically, so it can drift forward on later calls once the provider announces newer ads, and is lost on a Curio restart (the provider re-announces its current head once on startup to recover it).
183188

184189
#### Errors
185190

pdp/handlers.go

Lines changed: 29 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -270,18 +270,16 @@ func (p *PDPService) handleGetPieceStatus(w http.ResponseWriter, r *http.Request
270270

271271
// Query status from database
272272
var results []struct {
273-
PieceCID string `db:"piece_cid"`
274-
PieceRawSize uint64 `db:"piece_raw_size"`
275-
CreatedAt time.Time `db:"created_at"`
276-
Indexed bool `db:"indexed"`
277-
IndexedAt sql.NullTime `db:"indexed_at"`
278-
AdvertisementCreated bool `db:"advertisement_created"`
279-
AdvertisementCreatedAt sql.NullTime `db:"advertisement_created_at"`
280-
AdCID sql.NullString `db:"ad_cid"`
281-
AdvertisementRetrieved bool `db:"advertisement_retrieved"`
282-
AdvertisementRetrievedAt sql.NullTime `db:"advertisement_retrieved_at"`
283-
Status string `db:"status"`
284-
Provider sql.NullString `db:"provider"`
273+
PieceCID string `db:"piece_cid"`
274+
PieceRawSize uint64 `db:"piece_raw_size"`
275+
CreatedAt time.Time `db:"created_at"`
276+
Indexed bool `db:"indexed"`
277+
IndexedAt sql.NullTime `db:"indexed_at"`
278+
AdvertisementCreated bool `db:"advertisement_created"`
279+
AdvertisementCreatedAt sql.NullTime `db:"advertisement_created_at"`
280+
AdCID sql.NullString `db:"ad_cid"`
281+
Status string `db:"status"`
282+
Provider sql.NullString `db:"provider"`
285283
}
286284

287285
err = p.db.Select(ctx, &results, `
@@ -299,19 +297,16 @@ func (p *PDPService) handleGetPieceStatus(w http.ResponseWriter, r *http.Request
299297
pr.advertisement_created_at as advertisement_created_at,
300298
ia.ad_cid,
301299
302-
-- Advertisement Fetch status
303-
ia.fetched_at IS NOT NULL as advertisement_retrieved,
304-
ia.fetched_at as advertisement_retrieved_at,
305-
306-
-- Determine overall status
300+
-- Determine overall status. "announced" only means the ad row
301+
-- exists, not that an indexer finished processing it; callers
302+
-- check that themselves via adCid.
307303
CASE
308-
WHEN ia.fetched_at IS NOT NULL THEN 'retrieved'
309304
WHEN ia.ad_cid IS NOT NULL THEN 'announced'
310305
WHEN pr.ipni_task_id IS NOT NULL THEN 'creating_ad'
311306
WHEN pr.indexing_task_id IS NOT NULL THEN 'indexing'
312307
ELSE 'pending'
313308
END as status,
314-
309+
315310
ia.provider
316311
317312
FROM pdp_piecerefs pr
@@ -320,8 +315,7 @@ func (p *PDPService) handleGetPieceStatus(w http.ResponseWriter, r *http.Request
320315
LEFT JOIN LATERAL (
321316
SELECT
322317
MIN(i.ad_cid) as ad_cid,
323-
MIN(i.provider) as provider,
324-
MIN((SELECT MIN(af.fetched_at) FROM ipni_ad_fetches af WHERE af.ad_cid = i.ad_cid)) as fetched_at
318+
MIN(i.provider) as provider
325319
FROM ipni i
326320
WHERE i.piece_cid = pr.piece_cid
327321
AND i.provider = (SELECT peer_id FROM ipni_peerid WHERE sp_id = $3)
@@ -357,16 +351,14 @@ func (p *PDPService) handleGetPieceStatus(w http.ResponseWriter, r *http.Request
357351
IndexedAt *time.Time `json:"indexedAt,omitempty"`
358352
AdCreated bool `json:"adCreated"`
359353
AdCreatedAt *time.Time `json:"adCreatedAt,omitempty"`
354+
AdCid *string `json:"adCid,omitempty"`
360355
Advertised bool `json:"advertised"`
361356
AdvertisedAt *time.Time `json:"advertisedAt,omitempty"`
362-
Retrieved bool `json:"retrieved"`
363-
RetrievedAt *time.Time `json:"retrievedAt,omitempty"`
364357
}{
365358
PieceCID: pieceInfo.CidV2.String(),
366359
Status: result.Status,
367360
Indexed: result.Indexed,
368361
AdCreated: result.AdvertisementCreated,
369-
Retrieved: result.AdvertisementRetrieved,
370362
}
371363

372364
if !result.IndexedAt.Valid {
@@ -381,72 +373,26 @@ func (p *PDPService) handleGetPieceStatus(w http.ResponseWriter, r *http.Request
381373
response.AdCreatedAt = &result.AdvertisementCreatedAt.Time
382374
}
383375

384-
if !result.AdvertisementRetrievedAt.Valid {
385-
response.RetrievedAt = nil
386-
} else {
387-
response.RetrievedAt = &result.AdvertisementRetrievedAt.Time
388-
}
389-
390-
// Advertised and AdvertisedAt are derived from three signals, in order:
391-
// 1. A recorded fetch of this ad in ipni_ad_fetches is the strongest per-ad
392-
// signal. If an indexer fetched the ad, it must have been advertised
393-
// already. Since we do not store the actual first publish time for each
394-
// ad, AdvertisedAt is estimated as the earlier of
395-
// advertisement_created_at + PublishInterval and the first fetch time.
396-
// This keeps AdvertisedAt from appearing after RetrievedAt.
397-
// 2. The in-process IPNI provider exposes LastPublishTime per provider, not
398-
// per ad. It is useful only when there is no fetch record and we know
399-
// when this ad was created. A provider publish after this ad was created
400-
// means the ad should have been included in the announced head; an older
401-
// provider publish means it was not, and we do not fall through to the
402-
// timing heuristic.
403-
// 3. Without either signal, fall back to the old timing heuristic: after
404-
// PublishInterval has elapsed from ad creation, assume the ad was
405-
// announced.
406-
if result.AdvertisementRetrieved {
407-
response.Advertised = true
408-
if result.AdvertisementRetrievedAt.Valid {
409-
advertisedAt := result.AdvertisementRetrievedAt.Time
410-
if result.AdvertisementCreatedAt.Valid {
411-
createdAtEstimate := result.AdvertisementCreatedAt.Time.Add(ipni_provider.PublishInterval)
412-
if createdAtEstimate.Before(advertisedAt) {
413-
advertisedAt = createdAtEstimate
414-
}
415-
}
416-
response.AdvertisedAt = &advertisedAt
417-
} else {
418-
response.AdvertisedAt = nil
419-
}
376+
if result.AdCID.Valid {
377+
response.AdCid = &result.AdCID.String
420378
}
421379

422-
advertisedFromProvider := false
423-
if !response.Advertised && result.AdvertisementCreatedAt.Valid && p.ipp != nil && result.Provider.Valid {
424-
publishedAt := p.ipp.LastPublishTime(result.Provider.String)
380+
// LastPublishTime is per-provider, not per-ad, so it only confirms this ad
381+
// was advertised once it postdates the ad's creation. Falls back to the
382+
// old timing heuristic if that signal isn't available.
383+
if result.AdvertisementCreatedAt.Valid {
384+
var publishedAt *time.Time
385+
if p.ipp != nil && result.Provider.Valid {
386+
publishedAt = p.ipp.LastPublishTime(result.Provider.String)
387+
}
425388
if publishedAt != nil {
426-
advertisedFromProvider = true
427389
if publishedAt.After(result.AdvertisementCreatedAt.Time) {
428390
response.Advertised = true
429391
response.AdvertisedAt = new(*publishedAt)
430-
if publishedAt.After(time.Now().Add(ipni_provider.PublishInterval)) {
431-
response.AdvertisedAt = new(result.AdvertisementCreatedAt.Time.Add(ipni_provider.PublishInterval))
432-
}
433-
} else {
434-
response.Advertised = false
435-
response.AdvertisedAt = nil
436-
}
437-
}
438-
}
439-
440-
if !advertisedFromProvider && !response.Advertised {
441-
if result.AdvertisementCreated && result.AdvertisementCreatedAt.Valid {
442-
if time.Since(result.AdvertisementCreatedAt.Time) > ipni_provider.PublishInterval {
443-
// More than 5 seconds since advertisement was created, assume it's published
444-
response.Advertised = true
445-
response.AdvertisedAt = new(result.AdvertisementCreatedAt.Time.Add(ipni_provider.PublishInterval))
446-
} else {
447-
response.Advertised = false
448-
response.AdvertisedAt = nil
449392
}
393+
} else if time.Since(result.AdvertisementCreatedAt.Time) > ipni_provider.PublishInterval {
394+
response.Advertised = true
395+
response.AdvertisedAt = new(result.AdvertisementCreatedAt.Time.Add(ipni_provider.PublishInterval))
450396
}
451397
}
452398

0 commit comments

Comments
 (0)