Skip to content
Closed
Show file tree
Hide file tree
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
236 changes: 236 additions & 0 deletions IPIP/0000-ordered-car-responses.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
# IPIP 0000: Block Ordering in CAR Responses

- Start Date: 2022-10-07
- Related Issues:
- Car Spec https://ipld.io/specs/transport/car/carv1/

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://datatracker.ietf.org/doc/html/rfc2119).

## Summary

Specify a traversal order allowing exchange of ready-to-unpack car files.

## Motivation

We want to make it easier to build light-clients for IPFS.

Thoses uses the [trustless gateway](../http-gateways/TRUSTLESS_GATEWAY.md) specs to download raw blocks and car files and only do content verifications.

This allows to delegate most expensive tasks to remote servers, and only do cryptographic verifications.

We want them to have low memory footprints on arbitrary sized files.
The main pain point preventing this is the fact that CAR ordering isn't specified.

This require to keeping some kind of reference either on disk, or in memory to previously seen blocks for two reasons.
1. Blocks can arrive out of order, meaning when a block is consumed (data is red and returned to the consumer) and when it's received might not match.
1. Blocks can be reused multiple times, this is handy for cases when you plan to cache on disk but not at all when you want to process a stream with use & forget policy.

What we really want is for the gateway to help us a bit, and give us blocks in a useful order.
Copy link
Member

Choose a reason for hiding this comment

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

@Jorropo Is this the proper characterization of the desired end goal? Or do we want to support other use cases (not clear from the document):

Suggested change
What we really want is for the gateway to help us a bit, and give us blocks in a useful order.
What we really want is for the gateway to help us a bit, and give us blocks in an order that removes the requirement of managing a block cache.


## Detailed design

Add a qualifying parameter to the `Accept` Header to how the blocks should be transfered.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Add a qualifying parameter to the `Accept` Header to how the blocks should be transfered.
Add support for a new optional `order` parameter in the `Accept: `application/vnd.ipld.car` HTTP header sent in requests, allowing clients to hint how the blocks inside of a CAR response should be ordered.


The block order correspond to someone reading the car file `Data` section (see car specification) from low index to high indexes.
In other words, blocks are sent first when streaming the car file over a fifo byte stream such as TCP connection, unix pipe, ... they are readable first.

`BlockOrder`, `enum (string)`, defaults to `Unordered`
Copy link
Member

Choose a reason for hiding this comment

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

⚠️ @Jorropo I know this is tedious, but trust me, it will save us time ;)

This change will most likely be requested by IANA anyway, if we ever submit this as an update to existing https://www.iana.org/assignments/media-types/application/vnd.ipld.car, so better to clean this up from the beginning:

  • Media type's optional parameters are lowercase, and shorter is better.
    • 👉 Rename BlockOrder to order, use lowercase for all values too (e.g. FooBarBuzfoo-bar-buz)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok I thought we could put anything there. I'll


They are generally adding increasing level of compatible constraints,
Copy link
Member

Choose a reason for hiding this comment

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

The following text should be moved to ## Compatibility section from the template.

this mean a server implementation might choose to implement only some of the most restrictive one and use overly restrictive one for continence.
The Compatibility graph looks like this:
```graphviz
digraph G {
{ForwardDFSWithSkippingOfDuplicates, BackwardDFSWithSkippingOfDuplicates} -> ProvenUsefullFirst
ForwardDFSWithDuplicates -> ForwardDFSWithSkippingOfDuplicates
BackwardDFSWithDuplicates -> BackwardDFSWithSkippingOfDuplicates

"ProvenUsefullFirst-OnlyUsefull" -> {ProvenUsefullFirst, "Unordered-OnlyUsefull"} -> Unordered
"ForwardDFSWithSkippingOfDuplicates-OnlyUsefull" -> {ForwardDFSWithSkippingOfDuplicates, "ProvenUsefullFirst-OnlyUsefull"}
"BackwardDFSWithSkippingOfDuplicates-OnlyUsefull" -> {BackwardDFSWithSkippingOfDuplicates, "ProvenUsefullFirst-OnlyUsefull"}
"ForwardDFSWithDuplicates-OnlyUsefull" -> ForwardDFSWithDuplicates
"BackwardDFSWithDuplicates-OnlyUsefull" -> BackwardDFSWithDuplicates

Query
}
```
Comment on lines +42 to +56
Copy link
Member

Choose a reason for hiding this comment

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

nit: unable to read this, either replace with pre-rendered version or use mermaids syntax that is renedered by github and other tools:


Compatible means that gateway is MAY answer with childs (direct or indirect) of a particular ordering if it wish to.

A gateway MUST answer with a stronger compatible ordering if one is available.

A gateway is allowed and SHOULD answer with a more strict `BlockOrder` type if this is what is really used under the hood.
For example an implementation that doesn't do any particular more optimisation when asked `Unordered` and actually answer `ProvenUsefullFirst` SHOULD set `Content-Type` to `application/vnd.ipld.car; BlockOrder=ProvenUsefullFirst`.

### Orderings

Note for all of them, we follow multihash keying,
if they allows not sending the same blocks multiple times, if multihash A has been sent with codec 1,
and then if multihash A is seen again but with codec 2, you may not send this block,
however you MUST NOT stop the traversal because the same hash decoded two way may have different descent.

For gateways:

`Unordered` is the only MUST implement ordering.

- `ProvenUsefullFirst`
- `ForwardDFSWithSkippingOfDuplicates`
- `ForwardDFSWithDuplicates`

are SHOULD implement orderings.

- `BackwardDFSWithSkippingOfDuplicates`
- `BackwardDFSWithDuplicates`

are MAY implement orderings.
Comment on lines +72 to +85
Copy link
Member

Choose a reason for hiding this comment

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

I see 5 types of ordering, but no rationale why we need them.

Which one will be used by a low memory IoT device which is fetching firmware update as a CAR?
Are there other use cases that require different orders and handling of duplicates?


Adding `-OnlyUsefull` SHOULD be implemented by the gateway assuming they actually implement such feature.

Implementing a stricter compatible ordering counts as ordering other compatible orderings.

#### `Unordered`

This is the current state of things and the default when `BlockOrder` requests.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
This is the current state of things and the default when `BlockOrder` requests.
This is the implicit default when `order` parameter is omitted.


All blocks found doing a DFS search of all the roots must eventually be found in the file.
Copy link
Member

Choose a reason for hiding this comment

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

nit: first time DFS is mentioned in the document: define what DFS is or link to relevant spec.


Order is totally undefined and blocks might have duplicates as well as include blocks not part of the request.

Altho you can have duplicates, this really meant to allows reverse proxies to cache more restrictive ones if they wish.
Implementations SHOULD NOT emit duplicated blocks because this is a waste of bandwidth.

##### `Unordered-OnlyUsefull`

Because `Unordered` never cares about which blocks are received while reading, ensuring all blocks are useful would need to be finalized at the end by the client.

#### `ProvenUsefullFirst`
Copy link
Member

Choose a reason for hiding this comment

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

We really, really need better/shorter ordering names.

This one should be something self-explanatory, like order=expected-first or even order=traversal (because we want blocks in order of traversal (any traversal), right?.


This is a slightly stricter version of `Unordered`.

When a block is found in the stream but have not been proven useful yet, it MUST be ignored by clients (and thus the gateway MUST NOT assume the client have correctly received it).
A block become proven useful when it has been seen as link of an other already proved useful block.
The roots are the starting proven useful when starting the request.
Comment on lines +111 to +112
Copy link
Member

Choose a reason for hiding this comment

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

"proven useful" is mentioned all over the document, but this is the first time it is defined, in the middle of one of the ordering definitions here.

suggestion: rename "proven useful" to "expected" and move the definition from this paragraph to ### Expected Blocks section before ### Orderings.


Note, the reason it is allowed but ignored to send useless blocks is for padding purposes, some implementations include small empty blocks in order to pad data to 4096.
We want to be compatible with such implementations.

We expect most concurrent oriented gateways to use this as their default because it keep bandwidth usage small and correspond to concurrent dag traversal patterns.

The server SHOULD avoid sending the same blocks multiple times.

#### `ForwardDFSWithSkippingOfDuplicates`, `BackwardDFSWithSkippingOfDuplicates`
Copy link
Member

Choose a reason for hiding this comment

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

Is there a use case where we need to choose between forward vs backward?
Could we have a single order=dfs and a single order=dfs-uniq?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thoses mainly targets unixfs light clients.
Backward is if you want to read the tail end of the document first (for thoses rare file formats that works like that).
Forward is if you want to read the start of the document first (what is expected to be used most of the time).

This info needs to be included in the document.


In this mode, DFS is precisely required either `Forward` or `Backward` (thoses indicates respectively weather maps and list should be traversed from low or high indexes first).

In case a block that isn't precisely the next block in the DFS traversal is found it MUST be skipped by the client (and thus the gateway MUST NOT assume the client have correctly received it).

The gateway SHOULD avoid sending the same blocks multiple times,
the canonical way to do this is to create a seen CID set, add every CID traversed to it, then when traversing if the CID is in the set skip this branch completely.
Comment on lines +127 to +128
Copy link
Member

Choose a reason for hiding this comment

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

Why is this a SHOULD and not a MUST ? My expectation from order=dfs-uniq is to get every block only once (MUST).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is so a gateway is able to send a ForwardDFSWithDuplicates file to someone requesting a ForwardDFSWithSkippingOfDuplicates (because then when the gateway hits a duplicated part of the file the client is supposed to think this is padding data (or whatever reason someone would want to put useless blocks in car files) and ignore thoses unwanted blocks).

You add the -OnlyUsefull suffix, this is disallowed.


### `ForwardDFSWithDuplicates`, `BackwardDFSWithDuplicates`

In this mode you do a DFS either `Forward` or `Backward` (thoses indicates respectively weather maps and list should be traversed from low or high indexes first).

But unlike `ForwardDFSWithSkippingOfDuplicates` and `BackwardDFSWithSkippingOfDuplicates` you do not keep track of already seen blocks (and thus MUST resend duplicates if they show up in multiple places in the DAG).

In case a block this isn't precisely the next block in the DFS traversal is found it MUST be skipped by the client (and thus the gateway MUST NOT assume the client have correctly received it).

This traversal order is actually in the goal of saving bandwidth.

Clients using this would not keep any reference to previously downloaded data, so if the gateway didn't supported this mode, it would need to do many more requests to redownload skipped part of the DAG.

### `Query`

This order MUST NOT be suppported by the gateway.

This is the canonical way to trigger a failure and thus query which orderings are supported.

Comment on lines +142 to +147
Copy link
Member

Choose a reason for hiding this comment

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

I quess if you want to have dedicated query convention, it could be 415 Unsupported Media Type HTTP error and Accept header:

Suggested change
### `Query`
This order MUST NOT be suppported by the gateway.
This is the canonical way to trigger a failure and thus query which orderings are supported.
### `Query`
This order MUST NOT be suppported by the gateway.
This is the canonical way to trigger a failure and thus query which orderings are supported.
Returned HTTP Error MUST have supported orderings listed in the `Accept` response header field.
Lack of error, or a response with `Content-Type` without `order` means `order` parameter is not supported by the gateway.

That being said, having a dedicated fake-order type is an unnecessary spec complication, but against how content negotiation works in HTTP.

HTTP client can send multiple orderings in Accept with the request already (comma-separated), and even attach q weight to each one of them:

Accept: text/html, application/xhtml+xml, application/xml;q=0.9, image/webp, */*;q=0.8
Accept: application/vnd.ipld.car;order=foo;q=0.9, application/vnd.ipld.car;order=bar;q=0.8, application/vnd.ipld.car

### `-OnlyUsefull` suffix

You can add `-OnlyUsefull` suffix to all orderings except `Query`, this mean every time a block would have been skipped by the client, this is now an error instead.

Clients are free to read `-OnlyUsefull` cars like non `-OnlyUsefull` cars (and thus skip blocks if they don't wish to do the verification).

This can also be a hint to cache layers (a `-OnlyUsefull` car file would be more interesting because it might be smaller)

### Requests

Clients MAY specify it by qualifying the `Accept` header.

For example:

- `Accept: application/vnd.ipld.car; BlockOrder=ForwardDFSWithSkippingOfDuplicates` means it is intrested in either one of:
- `ForwardDFSWithSkippingOfDuplicates`
- `ForwardDFSWithSkippingOfDuplicates-OnlyUsefull`
- `ForwardDFSWithDuplicates`
- `ForwardDFSWithDuplicates-OnlyUsefull`
- `Accept: application/vnd.ipld.car; BlockOrder=Unordered` would accept anything.

Clients may qualify it multiple times, for example
`Accept: application/vnd.ipld.car; BlockOrder=ForwardDFSWithSkippingOfDuplicates-OnlyUsefull; BlockOrder=Unordered`
in this case this is a list of preferences (lower indexes is better), the client would really like `ForwardDFSWithSkippingOfDuplicates-OnlyUsefull`
however if this isn't available it would accept `Unordered` (so anything) instead.

Clients SHOULD NOT ask for an Ordering that already covered by a previous ask, for example:
`Accept: application/vnd.ipld.car; BlockOrder=ForwardDFSWithSkippingOfDuplicates; BlockOrder=ForwardDFSWithDuplicates`
is illegal because `ForwardDFSWithDuplicates` is already compatible with `ForwardDFSWithSkippingOfDuplicates`.

In case a gateway sees this it should read this left to right and ignore already covered orderings (it can try matching it multiple times, but this would do nothing).
Comment on lines +160 to +178
Copy link
Member

Choose a reason for hiding this comment

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

⚠️

HTTP RFC 9110, 12.5.1. Accept already defines how to specify an HTTP request is accepting multiple response types:

Accept: application/vnd.ipld.car;order=foo;q=0.9, application/vnd.ipld.car;order=bar;q=0.8, application/vnd.ipld.car

We should simplify this spec, remove magical implicit fallbacks: format decision belongs to the client, server should not try to be smart and give "next best thing" – only fulfill request as-is.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I didn't knew Accept worked this way.
Yeah definitely better.


### Responses

#### Success

The response code MUST be 200, the body must be the car payload correctly ordered.

And the `Content-Type` header must contain the qualifier of the matching ordering, only one is allowed.

If ordering is `Unordered` ordering qualifier MAY be omitted but SHOULD NOT (this is to force compatibility with older gateways upon clients).

#### Ordering Incompatiblity

If none of the ordering requested by the client is supported the gateway MUST answer with `415 Unsupported Media Type`.
Copy link
Member

Choose a reason for hiding this comment

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

nit: add this code to https://github.com/ipfs/specs/blob/main/http-gateways/PATH_GATEWAY.md#response-status-codes + link to it.

I suggest we include note from https://httpwg.org/specs/rfc9110.html#status.415:

The Accept response header field can be used to indicate which media types would have been accepted in the request.

Sidenote: i believe this is how what you wanted to do with order=query should work.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes it is, and if it's how HTTP is supposed to work then it's better to follow this.


The body first line must be *some* string followed by the magic `: ` trigger.

This indicates the start of the list of supported orderings which is a `, ` list of ordering up until the next newline, which indicates the end of the list (anything past this line MUST be ignored by clients).

The list doesn't need to include implicitly compatible orderings.

Regex:
```
.*: ((((Forward|Backward)DFSWith(SkippingOf|out)Duplicates)|Unordered|ProvenUsefullFirst)(-OnlyUsefull)?)(, ((((Forward|Backward)DFSWith(SkippingOf|out)Duplicates)|Unordered|ProvenUsefullFirst)(-OnlyUsefull)?))*$
```

Example:
```
BackwardDFSWithSkippingOfDuplicates is not a supported ordering, supported orderings are: ForwardDFSWithSkippingOfDuplicates-OnlyUsefull, ForwardDFSWithDuplicates-OnlyUsefull
```
Comment on lines +196 to +208
Copy link
Member

Choose a reason for hiding this comment

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

⚠️

The magical Regex should be removed.

It is fine to have an example of human-readable HTTP error body, but the actual list of supported orderings (parsable by clients) should live in Accept header returned with the error response.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because I'll move to use 415 and Accept headers, this parsing of the error body is not needed anymore.


## Test fixtures

`<TODO>`

### User benefit

We will have extremely light clients allowing to use IPFS trustlessly in embeded platforms, mobile and where you need repeatable and low memory usage.

### Compatibility

Kubo already implements `ForwardDFSWithSkippingOfDuplicates-OnlyUsefull` by implementation details luck.

### Security

Not than I can think of.

### Alternatives

Heavier lighter clients that can support `Unordered` modes (default).

For example keeping preferences in memory or disk, or by using multiple requests (such as `Raw`).

Which are either impossible because of platform constraints or would cause many many more requests.

### Copyright

Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).
68 changes: 0 additions & 68 deletions IPIP/0000-template.md

This file was deleted.