Skip to content

Conversation

@mishmosh
Copy link
Contributor

@mishmosh mishmosh commented Apr 3, 2025

Currently, CIDs can be generated with a variety of settings and optimizations for chunking, DAG width, and more. This means the same file can yield multiple, different CIDs depending on which tools and settings are used, and it is not possible to reliably reproduce or verify the CID.

This proposal introduces profiles for IPFS CIDs. Profiles explicitly define CID version, hash algorithm, chunk size, DAG width, layout, and other parameters. They can be used to verify data across implementations, provide recommended settings depending on retrieval performance goals, and more.

@mishmosh mishmosh requested a review from a team as a code owner April 3, 2025 14:03
@mishmosh mishmosh changed the title Create ipip-0000.md: CID profiles IPIP 0499: CID Profiles Apr 3, 2025
lidel added a commit to ipfs/kubo that referenced this pull request Apr 15, 2025
lets make the fanout match the max links from files
and rename profile to `-wide`

this will make it easier to discuss in ipfs/specs#499
lidel and others added 2 commits April 15, 2025 23:41
Import.* config params for controlling DAG width were added in:
ipfs/kubo#10774
@lidel
Copy link
Member

lidel commented Apr 15, 2025

Thank you for kicking this off, and filling initial state.

I've incorporated specific "dag width" settings for File, Directory and HAMTDirectory nodes,
and updated the table to reflect state from ipfs/kubo#10774
and profiles that exist in Kubo master branch: legacy-cid-v0, test-cid-v1 and test-cid-v1-wide:

Next:

  • agree what "cid-2025" profile should look like
    • this will be new default in "Kubo v1.0"
    • we have test-cid-v1 and test-cid-v1-wide in Kubo as potential candidates
  • switch to PR from local branch (so we have build preview)
  • figure out how to render the information (currently the table is not supported by https://github.com/ipfs/spec-generator)

@SethDocherty

This comment was marked as off-topic.

1. UnixFS DAG layout (e.g. balanced, trickle)
1. UnixFS DAG width (max number of links per `File` node)
1. `HAMTDirectory` fanout (must be a power of 2)
1. `HAMTDirectory` threshold (max `Directory` size before switching to `HAMTDirectory`): based on an estimate of the block size by counting the size of PNNode.Links
Copy link
Contributor

Choose a reason for hiding this comment

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

If this number is dynamic based on the lengths of the actual link entries in the dag, we will need to specify what algorithm that estimation follows. I would put such things in a special "ipfs legacy" profile to be honest, along with cidv0, non-raw leaves etc. We probably should heavily discourage coming up with profiles that do weird things, like dynamically setting params or not using raw-leaves for things.

Copy link
Contributor

Choose a reason for hiding this comment

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

So, each layout would have its own set of layout-params:

  • balanced:
    • max-links: N
  • trickle:
    • max-leaves-per-level: N

Copy link
Member

Choose a reason for hiding this comment

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

We probably should heavily discourage coming up with profiles that do weird things, like dynamically setting params or not using raw-leaves for things.

Yeah, that's exactly what we're doing by defining this profile.

Copy link
Collaborator

Choose a reason for hiding this comment

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

wait is kubo dynamically assigning HAMT Directory threshold, currently? i was assuming this was a static number!

Copy link
Collaborator

Choose a reason for hiding this comment

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

The current spec mentions fanout but not threshold, so i'm a little confused what current implementations are doing and if it's even worth fitting into the profile system or just giving up and letting a significant portion of HAMT-shared legacy data just but unprofiled/not-recreatable using the profiles...

Copy link
Contributor

Choose a reason for hiding this comment

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

@lidel Is this written down in any of the specs? Or is it just in the code at this point?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@lidel @hsanjuan Trying to understand/resolve this thread. Can you confirm if this is current kubo behavior?

HAMTDirectory threshold (max Directory size before switching to HAMTDirectory): based on an estimate of the block size by counting the size of PNNode.Links

Copy link
Member

@lidel lidel Nov 13, 2025

Choose a reason for hiding this comment

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

AFAK decision when to do HAMTDirectory is an implementation-specific behavior. So far the rule of thumb is to keep blocks under 1-2MiB and usually good idea to match chunk size defined (default or defined by user).

Implementation-wise both GO (Boxo/Kubo) and JS (Helia) have size-based heuristic that makes decision when to switch from normal Directory to HAMTDirectory:

iirc (from 2 year old memory, something to check/confirm) is that the size estimation details may/are likely different between GO and JS. They both estimate the serialized DAGNode size by calculating the aggregate byte length of directory entries (link names + CIDs), though the JavaScript implementation appears to include additional metadata in its calculation:

  • Kubo's size estimation method is likely estimatedSize = sum(len(link.Name) + len(link.Cid.Bytes()) for each link)
  • Helia is likely "the size of the final DAGNode (including link names, sizes, optional metadata fields etc)"

If true, the slight differences in calculation methods might result in directories sharding at marginally different sizes.

Copy link
Member

Choose a reason for hiding this comment

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

If you want to be exact you have to take into account any non-zero value fields in the serialized root UnixFS metadata since these affect the block size.

It's quite possible that Kubo will produce a HAMT block that's too big with a certain combination of directory entry names if someone has also changed the encoded directory's default mtime or whatever, probably because the "should-I-shard" feature pre-dates Kubo's ability to add UnixFSv1.5 metadata to things.

Really there's no need to estimate anything - it's trivial to count the actual bytes that a block will take up and then shard if necessary.

Comment on lines 57 to 58
1. Whether empty directories are included in the DAG
- Some implementations apply filtering before merkleizing filesystem entries in the DAG.
Copy link
Contributor

Choose a reason for hiding this comment

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

This is weird, because then we need to consider empty files, hidden files, unreadable files, symlinks and symlink follows, so probably need to mention all those as part of the profile too?

Copy link
Member

@2color 2color Aug 20, 2025

Choose a reason for hiding this comment

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

This is motivated by Git's default behaviour which ignores empty directories.

But we can mention here the rest.

Copy link
Collaborator

Choose a reason for hiding this comment

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

wait, do @hsanjuan , do you mean mentioning whether empty files, hidden files, etc affect the decision of whether a directory is empty, or do you mean that each of those files might be divergently handled by different implementations and should be a variable in the profile? I would much rather behavior for all of those file types be a UnixFS concern and specified in UnixFS spec, modulo any historic variations worth including in a profile...

Copy link
Contributor

Choose a reason for hiding this comment

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

Do we have existing implementations that support filtering differently on all of these? Because unless we do, I would really rather not specify all possible variants. And I agree with @bumblefudge: let's have two behaviours if possible, and punt to the UnixFS spec for how to describe them.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, these choices are being made today and it'd be nice to be explicit about them. e.g. default Helia leaves all of these options default (false): https://github.com/ipfs/helia/blob/027bd3549da9ef5a6f07eaac346942cf24f3fc24/packages/unixfs/src/utils/glob-source.ts#L12-L42

But in filecoin-pin currently I've opted to include hidden files: https://github.com/filecoin-project/filecoin-pin/blob/9ab3f8c110ce0b6c6bf21c1fcdbcf84ade557953/src/core/unixfs/car-builder.ts#L30-L32 (I'm rethinking that choice now, but I'd like to know Kubo's defaults as well).

I'd prefer to align to a standard profile for file filtering so we collectively have "one standard default behaviour", but I understand it's a bit more work to explicate all of that. So maybe it can be a hand-wave for now and tightened up later because you could argue it's external to a unixfs spec and more about the choice of what to feed into a unixfsification process.

Copy link
Member

@lidel lidel Nov 13, 2025

Choose a reason for hiding this comment

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

Feels like mixing abstractions, no? To me filtering of input is an userland software decision, out of scope. I don't think Ext4, NTFS or APFS specs guide implementers if hidden files are included when copying directories. We could mention it in "Appendix: Notes for Implementers" to flag potential discrepancy if filtering is involved, and that implementation should provide user with ability to disable/adjust default filters.

Copy link
Contributor

Choose a reason for hiding this comment

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

Right, so we would include a directory if it's there, even if empty. I was never sure if there is a good reason for git's approach but always seemed exotic.

Copy link
Member

Choose a reason for hiding this comment

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

filtering of input is an userland software decision

And yet it still impacts the resulting root CID, so I think it's in scope here, no? How about we make it a "SHOULD" advisory.

For consistent output implementations should by default not apply file and directory filtering and include empty directories, but may opt to allow user-driven decisions to filter out entities such as hidden files, dot files, and empty directories.

1. UnixFS DAG layout (e.g. balanced, trickle etc...)
1. UnixFS DAG width (max number of links per `File` node)
1. `HAMTDirectory` bitwidth, i.e. the number of bits determines the fanout of the `HAMTDirectory` (default bitwidth is 8 == 256 leaves).
1. `HAMTDirectory` threshold (max `Directory` size before switching to `HAMTDirectory`): based on an estimate of the block size by counting the size of PNNode.Links
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
1. `HAMTDirectory` threshold (max `Directory` size before switching to `HAMTDirectory`): based on an estimate of the block size by counting the size of PNNode.Links
1. `HAMTDirectory` threshold (max `Directory` size before switching to `HAMTDirectory`): based on an estimate of the block size by counting the size of PNNode.Links. We do not include details about the estimation algorithm as we do not encourage implementations to support it.

Copy link
Member

@lidel lidel Nov 13, 2025

Choose a reason for hiding this comment

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

Bit odd to discourage, when both most popular implementations in GO and JS use size-based heurstic - #499 (comment)

Unsure how to handle this. Perhaps clarify the heuristic is implementation-specific, and when deterministic behavior is expected, a specific heuristic should be used?

Copy link
Member

@achingbrain achingbrain Nov 13, 2025

Choose a reason for hiding this comment

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

I don't think we should be estimating the block size as it's trivial to calculate it exactly. Can we not just define this (and punt to the spec for the details) to make it less hand-wavey?

Suggested change
1. `HAMTDirectory` threshold (max `Directory` size before switching to `HAMTDirectory`): based on an estimate of the block size by counting the size of PNNode.Links
1. `HAMTDirectory` threshold (max `Directory` size before switching to `HAMTDirectory`): based on the final size of the serialized form of the [PBNode protobuf message](https://specs.ipfs.tech/unixfs/#dag-pb-node) that represents the directory.

@rvagg
Copy link
Member

rvagg commented Nov 12, 2025

Hey, I'd love to be able to reference this, even if it's in "draft" form, could we just merge it and continue to iterate on top of it to get it right?

Fixed outdated references, consistent profile names, streamlined Summary and Motivation sections.
@github-actions
Copy link

github-actions bot commented Nov 15, 2025

🚀 Build Preview on IPFS ready

@mishmosh
Copy link
Contributor Author

I made a few changes/fixes, aiming to land this early next week.

  • Added links to UnixFS spec (now that it exists)
  • Specified calendar versioning for profile names (line 64), per @b5 suggestion
    • @lidel I gave the 3 kubo profiles names that matched the naming scheme. This would mean minor updates to kubo, but is probably better for future-proofing. Acceptable? Also happy to discuss live.
  • Changed the "current defaults" section into a series of legacy profile names, that implementations MAY support. This allows those profile sets to be referenced/used across implementations.
  • We were using fanout and bitwidth interchangeably. I changed them all to fanout, in keeping with the UnixFS terminology. If we prefer bitwidth, I can PR that to UnixFS spec and then also here.
  • Streamlined lots of duplicate language from Summary and Motivation sections

Open questions:


As an alternative to profiles, users can store and transfer CAR files of UnixFS content, which include the merkle DAG nodes needed to verify the CID.

## Test fixtures
Copy link
Member

Choose a reason for hiding this comment

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

Just noting this is (imo) a blocker.

We did not merge UnixFS spec until we had sensible set of fixtures that people could use as reference.

The spec may be incomplete, but a fixture will let people reverse-engineer any details, and then PR improvement to spec.

Without fixtures for each UnixFS node type, we risk unknown unknown silently impacting final CID (e.g. because we did not know that someone may decide to place leaves one level sooner as "optimization" and someone else always at bottom, as "formal consistency")

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Tracking this in ipfs/kubo#11071

Copy link
Member

Choose a reason for hiding this comment

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

Thanks!

@mishmosh
Copy link
Contributor Author

Just synced with @lidel. He wants to ship this with test fixtures in place, (tracked in kubo/issues/11071). In the meantime, we don't anticipate changes to the profiles themselves so you can can reference this PR.

@icidasset
Copy link

Great work, glad to see this!

Couple notes/questions:

  • The profiles (legacy + new) don't say if the chunks are of a fixed size, or which algorithm they use.
  • Small typo under "Compatibility": "support the the set of" (double the)
  • Would it also be interesting to note if an implementation respects symlinks and if so, how the different kinds of symlinks are translated?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.