Skip to content

feat(ipns): Support adding custom metadata to IPNS record#1085

Merged
lidel merged 13 commits intoipfs:mainfrom
phaseloop:ipns-metadata
Apr 9, 2026
Merged

feat(ipns): Support adding custom metadata to IPNS record#1085
lidel merged 13 commits intoipfs:mainfrom
phaseloop:ipns-metadata

Conversation

@phaseloop
Copy link
Copy Markdown
Contributor

@phaseloop phaseloop commented Jan 8, 2026

Allow adding custom metadata to IPNS record - stored as additional keys in DAG-CBOR data.

IPNS specification allows to store arbitrary data in the record but this was not supported by NewRecord() method.

cc: @lidel

@phaseloop phaseloop requested a review from a team as a code owner January 8, 2026 22:57
Copy link
Copy Markdown
Member

@lidel lidel left a comment

Choose a reason for hiding this comment

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

Thank you for opening this @phaseloop, having ability to set/get metadata CBOR feels sensible, but this PR needs bit more work around two things:

  1. We should not use third-party type from legacy IPLD library in public API here
  2. You implemented way to set, but its missing method to read custom metadata from unmarshaled records

I suggested one way of addressing both below, but other ideas welcome.
In general, we want to move away from IPLD language and focus on deterministic subset of CBOR (dag-cbor).

Note that DAG-CBOR itself becomes problematic if you try to store something more than basic types:

This is why my suggestion is to limit the API for storing/reading metadata to raw []byte with entire dag-cbor + limited ability to set specific key as either String, []byte, boolean and maybe Integer (look at linked test results and pick minimal set of CBOR types that works everywhere)

Comment thread ipns/record.go Outdated
Comment thread ipns/record.go Outdated
@phaseloop
Copy link
Copy Markdown
Contributor Author

phaseloop commented Jan 10, 2026

Thanks a lot! I'll work on it. Off topic question: why DAG-CBOR was chosen instead of protobuf if implementations are so fragmented and buggy outside basic types? Is the storage saving so big compared to protobuf that it was worth the hassle? CBOR/DAG-CBOR seems to be pretty obscure.

@lidel
Copy link
Copy Markdown
Member

lidel commented Jan 10, 2026

Thanks! I'd prefer to avoid mischaracterizing work done by others, but my understanding is that the signed CBOR map was introduced years ago hastily to fix a security issue in V1, it was never a well thought out decision for metadata. But since we have this signed CBOR map, we can put other values there now.

I agree, CBOR creates unnecessary complexity, but it is too late to change V2 without breaking existing clients. Note that to this day, Kubo is producing V1+V2 records to keep backward compatibility.

This PR is probably not the place to discuss it, so let's avoid off-topic, but if you are interested in cleaning up this, feel free to open discussion in https://github.com/ipfs/specs/pulls (in theory, we could introduce V3 of IPNS records that improves the wire format, but upgrade path for ecosystem is a tricky part, see some prior art in https://specs.ipfs.tech/ipips/ipip-0428/).

@phaseloop
Copy link
Copy Markdown
Contributor Author

Thanks! I was just curious, so far I have no intention to rework IPNS (if it works, it works) - but I'm working on using IPNS as distributed DNS system:

ipfs/specs#528

@guillaumemichel guillaumemichel added the need/author-input Needs input from the original author label Jan 13, 2026
@gammazero gammazero added need/maintainers-input Needs input from the current maintainer(s) and removed need/author-input Needs input from the original author labels Jan 20, 2026
@phaseloop
Copy link
Copy Markdown
Contributor Author

@lidel I reworked the PR according to your comments

@phaseloop
Copy link
Copy Markdown
Contributor Author

@gammazero PR was modified according to comments, AFAIK "need/author-input" label can be removed now :)

@gammazero gammazero requested a review from lidel February 17, 2026 15:38
Copy link
Copy Markdown
Member

@lidel lidel left a comment

Choose a reason for hiding this comment

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

@phaseloop thanks, i had no bandwidth to do proper review in Kubo (hopefully after 0.40 ships), but small asks inline + you may want to rebase this on top of latest main

Comment thread ipns/record.go Outdated
Comment thread ipns/record_test.go Outdated
Comment thread ipns/record.go Outdated
@lidel lidel marked this pull request as draft February 24, 2026 01:27
@phaseloop phaseloop changed the base branch from main to release-v0.27.4 March 3, 2026 11:18
@phaseloop phaseloop changed the base branch from release-v0.27.4 to main March 3, 2026 11:19
@phaseloop
Copy link
Copy Markdown
Contributor Author

@lidel - thanks for suggestions, done

lidel added 2 commits March 3, 2026 14:51
- ipns/record.go: rename constructors to idiomatic Go short names
  (StringValue, BytesValue, IntValue, BoolValue), add godoc comments,
  guard Metadata() against reserved IPNS keys, preallocate map in
  createNode
- ipns/errors.go: fix typo, add trailing periods to comments
- ipns/record_test.go: use require.Error, add reserved-key read test,
  add marshal/unmarshal roundtrip test
- CHANGELOG.md: rewrite entry with API names and PR link
@phaseloop phaseloop marked this pull request as ready for review March 5, 2026 13:12
lidel added 5 commits April 9, 2026 04:42
Use map for reservedKeys so lookups are O(1).
MetadataExists now returns false for reserved IPNS fields,
matching Metadata() which already returned ErrMetadataConflict.
Add MetadataEntries() iter.Seq2 for discovering custom keys.
Add doc comment noting only scalar types are supported.
Clean up test names and add iterator coverage.
Prevent empty-string metadata keys and nil-node panics on
MetadataValue accessors. Add test coverage for edge cases.

- errors.go: add ErrMetadataEmptyKey, ErrMetadataValueNotSet sentinels
- record.go: reject "" key in createNode, nil-guard in As* methods
- record_test.go: empty key, zero-value, type mismatch, nil/empty map,
  CBOR key ordering tests
- CHANGELOG.md: reflect current scope of metadata API
Accept map[string]any on the write side (WithMetadata) with internal
type validation, return typed MetadataValue with Kind() discriminator
on the read side. Follows slog.Value/otel attribute.Value patterns.

- record.go: WithMetadata accepts map[string]any, anyToNode validates
  and converts supported types (string, []byte, int64, int, bool),
  MetadataKind enum with Kind() method on MetadataValue, remove
  StringValue/BytesValue/IntValue/BoolValue constructors
- errors.go: add ErrMetadataUnsupportedType sentinel
- record_test.go: use plain Go values in all metadata tests, add
  Kind() assertions, test unsupported types and nil rejection
- doc.go: add Metadata section with usage example, link to spec
- record.go: document all previously undocumented exported methods
  (ValidityType, Sequence, TTL, PubKey, WithV1Compatibility,
  WithPublicKey), add spec anchors, improve MetadataValue accessor
  docs with error behavior, document MetadataKindInvalid
- validation.go: fix "Validates" typo in Validate godoc
Copy link
Copy Markdown
Member

@lidel lidel left a comment

Choose a reason for hiding this comment

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

Thank you.
LGTM, pushed slight rework of the API:

  • Changed write API from map[string]MetadataValue to map[string]any for ergonomic Go literals instead of constructor wrappers
  • Added MetadataEntries() iter.Seq2[string, MetadataValue] for iterating custom keys
  • Added MetadataKind type with Kind() discriminator for inspecting values read from third-party records
  • Added nil guards on MetadataValue accessors to prevent panics on zero values
  • Added int support alongside int64
  • Added validation for empty keys, nil values, and unsupported types with dedicated errors
  • Added reserved key guard on read, not just write (Metadata() returns ErrMetadataConflict, MetadataExists() returns false)
  • Changed reservedKeys from slice to map for O(1) lookups
  • Expanded test coverage with cases for edge cases and CBOR key ordering
  • Improved godocs across the ipns package with spec links to https://specs.ipfs.tech/ipns/ipns-record/

@lidel lidel merged commit bc4103e into ipfs:main Apr 9, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

need/maintainers-input Needs input from the current maintainer(s)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants