feat: clear extended provider via ClearExtendedProvider special message#30
Conversation
Adds a new SQS special-message body 'ClearExtendedProvider' that, when received, publishes an advertisement whose ExtendedProvider.Providers field is explicitly empty. storetheindex >= e1afa6b8 (PR #2814) honors this shape via its registry.Update Empty() branch, setting info.ExtendedProviders = nil so subsequent lookups return no ExtendedProvider entries for the publisher peer ID. See ipni/storetheindex#1745 for the design rationale. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
| } | ||
|
|
||
| const value = await ad.encodeAndSign() | ||
| // The library only writes ExtendedProvider when providers.length > 1; attach |
There was a problem hiding this comment.
wait -- won't this invalidate the signature -- value has already been signed, and now you're changing one of its struct members?
There was a problem hiding this comment.
Good check. No — the signature is computed over a fixed subset of fields that does NOT include the ExtendedProvider list, so post-hoc attachment is cryptographically safe.
From @web3-storage/ipni/advertisement.js signableBytes():
return concat([
ad.previous?.bytes ?? new Uint8Array(),
ad.entries.bytes,
text.encode(provider.peerId.toString()),
text.encode(provider.addresses.map(a => a.toString()).join('')),
provider.encodeMetadata(),
new Uint8Array([IsRm])
])Just previous + entries + provider.peerId + provider.addresses + provider.metadata + IsRm. This matches go-libipni's canonical signaturePayload() in ingest/schema/envelope.go, which is what storetheindex reconstructs on ingest to compare against. Since the new value.ExtendedProvider field doesn't intersect any of those signed bytes, the existing value.Signature remains valid against the mutated value.
(There ARE per-provider signatures on each entry in ExtendedProvider.Providers — but we have zero entries, so none to compute.)
Summary
Adds a new SQS special-message body
ClearExtendedProviderto the publishing handler, mirroring the existingAnnounceHTTPprecedent (#27). When the handler receives a message with this body, it publishes a single advertisement whoseExtendedProvider.Providersfield is explicitly empty ([]). storetheindex ≥ commite1afa6b8(from PR #2814) honors this shape via itsregistry.UpdateEmpty()branch, settinginfo.ExtendedProviders = nilso subsequent lookups return no ExtendedProvider entries. Seeipni/storetheindex#1745for the design rationale and @gammazero's recommendation.The base provider (bitswap) is intentionally left intact on the advertisement — this PR only clears the ExtendedProvider half. Operators wanting to also disable the bitswap routing should handle that out-of-band (e.g. by taking the bitswap peer offline at the network layer; its still-advertised multiaddrs will fail to connect).
Deliverables
src/handlers/advertisement.jsencodeAndSignattachment ofExtendedProvider: { Providers: [], Override: false }test/advertisement.test.jstaptestadvertisement - clear extended provider(18 assertions) mirroring the existingextended providertest patternHow the empty-Providers attachment works
@web3-storage/ipni'sAdvertisement.encodeAndSign()only writes theExtendedProviderfield whenproviders.length > 1. The advertisement'sSignatureis computed overprevious + entries + provider.peerId + provider.addresses + provider.metadata + IsRm— it does NOT cover the ExtendedProvider list. So we construct a single-providerAdvertisement(the bitswap base), callencodeAndSign(), then attachvalue.ExtendedProvider = { Providers: [], Override: false }to the returned value before encoding to dag-json. The AD signature remains valid because the bytes it covers are unchanged.Operator runbook
Trigger:
e1afa6b8(PR #2814 merged 2026-04-08). Older indexers will ingest the ad cleanly but treat the empty-Providers attachment as a no-op; forward-compatible.Verify:
curl https://<bucket>.s3.<region>.amazonaws.com/head— confirm it points at a new CID.curl https://<bucket>.s3.<region>.amazonaws.com/<new-cid>and confirm the JSON contains"ExtendedProvider": { "Providers": [], "Override": false }.Out-of-band (if disabling IPFS retrieval entirely): take the bitswap peer offline at the network layer.
Test plan
advertisement - clear extended providerpasses (18/18 assertions)test/advertisement.test.js+ the rest of the suite —npm testshows all 5 test files green)npm run lintclean🤖 Generated with Claude Code