Skip to content
Open
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
265 changes: 265 additions & 0 deletions connections/multistream-select-v2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
# Multistream Select V2

| Lifecycle Stage | Maturity | Status | Latest Revision |
| --------------- | ------------- | ------ | --------------- |
| 1A | Working Draft | Active | r1, 2025-11-13 |

Authors: [@raulk], [@marcopolo]

Interest Group: [@ppopth]

[@marcopolo]: https://github.com/marcopolo
[@ppopth]: https://github.com/ppopth
[@raulk]: https://github.com/raulk

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Could a Overview section be added with the motivation/goal behind this spec?

## Terminology

Server: The endpoint advertising its supported protocol strings.

Client: The endpoint receiving the advertisement and using protocol string
abbreviations when opening streams.

Abbreviation Tree: The tree data structure that determines the abbreviation to
use for a given protocol string.
Comment on lines +22 to +23

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

here, a non-circular definition

Suggested change
Abbreviation Tree: The tree data structure that determines the abbreviation to
use for a given protocol string.
Abbreviation Tree: a trie over the hashed protocol strings, in which each string's depth is the length of the shortest hash prefix that uniquely identifies it; that prefix is the string's abbreviation.


Note, A peer can behave as both a client and server. For the purpose of defining
this protocol, it's useful to focus on the client/server interaction.
Comment on lines +17 to +26

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit

Suggested change
Server: The endpoint advertising its supported protocol strings.
Client: The endpoint receiving the advertisement and using protocol string
abbreviations when opening streams.
Abbreviation Tree: The tree data structure that determines the abbreviation to
use for a given protocol string.
Note, A peer can behave as both a client and server. For the purpose of defining
this protocol, it's useful to focus on the client/server interaction.
- **Server**: the endpoint advertising its supported protocol strings.
- **Client*: the endpoint receiving the advertisement and using protocol string
abbreviations when opening streams.
- **Abbreviation Tree*: a trie over the hashed protocol strings, in which each string's depth is the length of the shortest hash prefix that uniquely identifies it; that prefix is the string's abbreviation.
**Note**: a peer can behave as both a client and server. For the purpose of defining
this protocol, it's useful to focus on the client/server interaction.


## Abbreviation Tree

A list of protocol strings are abbreviated by creating a minimal hash prefix

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
A list of protocol strings are abbreviated by creating a minimal hash prefix
Protocol strings are abbreviated by creating a minimal hash prefix

tree.

### Construction

Each protocol string is hashed and inserted into the tree as shallowly as
possible. Each node in the tree has 256 leaf branches representing a byte of the
hash (2^8). If multiple protocol strings share a common byte prefix, they are
distinguished the next level down the tree.

Each protocol string in the tree has a tombstone bit associated with it. This is

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe add Tombstone bit to the terminology section?

set to true if the protocol is currently not supported (but was previously).

A node in the tree may contain a value as well as children. This only happens
when introducing a new supported protocol introduces a conflict. This does not

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
when introducing a new supported protocol introduces a conflict. This does not
when adding a new supported protocol creates a conflict. This does not

happen on initial construction.

### Example

```txt
(root)
|
+-- 0xaa -> "/some-protocol/a/v1"
|
+-- 0xbb -> "/some-protocol/b/v1"
|
+-- 0xcc
| |
| +-- 0x01 -> "/some-protocol/c/v1"
| |
| +-- 0x02 -> "/some-protocol/c'/v1"
|
+-- 0xdd -> "/some-protocol/d/v1" tombstone=true
```
Comment on lines +47 to +63

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
### Example
```txt
(root)
|
+-- 0xaa -> "/some-protocol/a/v1"
|
+-- 0xbb -> "/some-protocol/b/v1"
|
+-- 0xcc
| |
| +-- 0x01 -> "/some-protocol/c/v1"
| |
| +-- 0x02 -> "/some-protocol/c'/v1"
|
+-- 0xdd -> "/some-protocol/d/v1" tombstone=true
```
### Example
The hash values in this and all following examples are illustrative and
chosen for readability. Real implementations use the mapping algorithm
defined below (SHA256); no real protocol string hashes to these values.
Protocol strings are inserted in the following order, with these (example)
hashes:
| Step | Protocol string | Example hash | Result |
|------|------------------------|--------------|-------------------------------------------------------------|
| 1 | `/some-protocol/a/v1` | `0xaa…` | No conflict, inserted at `0xaa`. |
| 2 | `/some-protocol/b/v1` | `0xbb…` | No conflict, inserted at `0xbb`. |
| 3 | `/some-protocol/c1/v1` | `0xcc01…` | No conflict, inserted at `0xcc`. |
| 4 | `/some-protocol/c2/v1` | `0xcc02…` | Conflicts with step 3 at `0xcc`. Both move one level down: `c1/v1` to `0xcc01`, `c2/v1` to `0xcc02`. |
| 5 | `/some-protocol/d/v1` | `0xdd…` | No conflict, inserted at `0xdd`. |
| 6 | remove `/some-protocol/d/v1` | | Tombstone bit set; the entry stays at `0xdd` so the prefix remains reserved. |
Resulting tree (node labels are the full prefix, i.e. the bytes sent on the wire):
```txt
(root)
|
+-- 0xaa -> "/some-protocol/a/v1"
|
+-- 0xbb -> "/some-protocol/b/v1"
|
+-- 0xcc
| |
| +-- 0xcc01 -> "/some-protocol/c1/v1"
| |
| +-- 0xcc02 -> "/some-protocol/c2/v1"
|
+-- 0xdd -> "/some-protocol/d/v1" tombstone=true
```
Note that step 4 moves `c1/v1` down during initial construction. This is the
only case in which an existing entry moves: once the tree is in use, entries
are never removed or relocated (see "Inserting a New Protocol String").


### Inserting a New Protocol String

The protocol string is inserted into the tree as shallowly as possible. If there
is a conflict, the protocol string should be inserted one level down and
duplicate the conflicting protocol string to the new level if it is not
tombstoned. The conflicting protocol string MUST not removed from its original

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
tombstoned. The conflicting protocol string MUST not removed from its original
tombstoned. The conflicting protocol string MUST NOT be removed from its original

position as it may still be referenced.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

when would a client actually need an old abbreviation to keep working after the protocol set changes? could we just let that fail and retry with the updated set?


#### Example

Using the initial example as the initial state and we add three new protocol
strings:

1. `"/new-protocol/foo"` that hashes to `0xaa02`. This example highlights a
conflict with an existing protocol string.
2. `"/new-protocol/bar"` that hashes to `0xee...`. This example highlights no
conflicts and inserting shallowly.
3. `"/new-protocol/d"` that hashes to `0xdd02`. This example highlights a
conflict with a tombstoned protocol string.

```txt
(root)
|
+-- 0xaa -> "/some-protocol/a/v1"
Comment thread
MarcoPolo marked this conversation as resolved.
| |
| +-- 0x01 -> "/some-protocol/a/v1"
| |
| +-- 0x02 -> "/new-protocol/foo"
|
+-- 0xbb -> "/some-protocol/b/v1"
|
+-- 0xcc
| |
| +-- 0x01 -> "/some-protocol/c/v1"
| |
| +-- 0x02 -> "/some-protocol/c'/v1"
|
+-- 0xdd -> "/some-protocol/d/v1" tombstone=true
| |
| +-- 0xdd01 -> "/new-protocol/d"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
| +-- 0xdd01 -> "/new-protocol/d"
| +-- 0x02 -> "/new-protocol/d"

to match what's it's written in line 82

|
+-- 0xee -> "/new-protocol/bar"
```
Comment on lines +73 to +107

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
#### Example
Using the initial example as the initial state and we add three new protocol
strings:
1. `"/new-protocol/foo"` that hashes to `0xaa02`. This example highlights a
conflict with an existing protocol string.
2. `"/new-protocol/bar"` that hashes to `0xee...`. This example highlights no
conflicts and inserting shallowly.
3. `"/new-protocol/d"` that hashes to `0xdd02`. This example highlights a
conflict with a tombstoned protocol string.
```txt
(root)
|
+-- 0xaa -> "/some-protocol/a/v1"
| |
| +-- 0x01 -> "/some-protocol/a/v1"
| |
| +-- 0x02 -> "/new-protocol/foo"
|
+-- 0xbb -> "/some-protocol/b/v1"
|
+-- 0xcc
| |
| +-- 0x01 -> "/some-protocol/c/v1"
| |
| +-- 0x02 -> "/some-protocol/c'/v1"
|
+-- 0xdd -> "/some-protocol/d/v1" tombstone=true
| |
| +-- 0xdd01 -> "/new-protocol/d"
|
+-- 0xee -> "/new-protocol/bar"
```
#### Example
Starting from the tree in the construction example above, three protocol
strings are inserted, in this order. Note that `/some-protocol/a/v1`'s full hash is `0xaa01…`; its first byte alone was
enough to place it originally.
| Step | Protocol string | Example hash | Result |
|------|----------------------|--------------|--------|
| 1 | `/new-protocol/foo` | `0xaa02…` | Conflicts with `a/v1` at `0xaa`. `foo` is inserted one level down at `0xaa02`. `a/v1` is not tombstoned, so it is duplicated to `0xaa01`; its original entry at `0xaa` MUST stay, since clients that built their tree earlier still refer to `a/v1` as `0xaa`. |
| 2 | `/new-protocol/bar` | `0xee…` | No conflict, inserted at `0xee`. |
| 3 | `/new-protocol/d` | `0xdd02…` | Conflicts with the tombstoned `d/v1` at `0xdd`. `new-protocol/d` is inserted one level down at `0xdd02`. `d/v1` is tombstoned, so it is not duplicated; its entry at `0xdd` stays so that a stale client sending `0xdd` is rejected rather than misrouted to `new-protocol/d`. |
Resulting tree:
```txt
(root)
|
+-- 0xaa -> "/some-protocol/a/v1"
| |
| +-- 0xaa01 -> "/some-protocol/a/v1"
| |
| +-- 0xaa02 -> "/new-protocol/foo"
|
+-- 0xbb -> "/some-protocol/b/v1"
|
+-- 0xcc
| |
| +-- 0xcc01 -> "/some-protocol/c1/v1"
| |
| +-- 0xcc02 -> "/some-protocol/c2/v1"
|
+-- 0xdd -> "/some-protocol/d/v1" tombstone=true
| |
| +-- 0xdd02 -> "/new-protocol/d"
|
+-- 0xee -> "/new-protocol/bar"
```
After step 1, `0xaa` is a node that holds a value and has children. A client
that connected before step 1 sends `0xaa` for `a/v1`; a client that connects
after step 1 builds its tree from the advertised list and sends `0xaa01`. The
server accepts both.


### Removing a Protocol String

For each instance of the protocol string in the tree, the tombstone bit is set
to true. The protocol string MUST not be removed from the tree as that could
lead to inconsistencies if a new protocol string is introduced with the same
prefix hash.

#### Example

Using the initial example as the initial state and we remove protocol
string `"/some-protocol/b/v1"`.

```txt
(root)
|
+-- 0xaa -> "/some-protocol/a/v1"
|
+-- 0xbb -> "/some-protocol/b/v1" tombstone=true
|
+-- 0xcc
| |
| +-- 0x01 -> "/some-protocol/c/v1"
| |
| +-- 0x02 -> "/some-protocol/c'/v1"
|
+-- 0xdd -> "/some-protocol/d/v1" tombstone=true
```
Comment on lines +116 to +135

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
#### Example
Using the initial example as the initial state and we remove protocol
string `"/some-protocol/b/v1"`.
```txt
(root)
|
+-- 0xaa -> "/some-protocol/a/v1"
|
+-- 0xbb -> "/some-protocol/b/v1" tombstone=true
|
+-- 0xcc
| |
| +-- 0x01 -> "/some-protocol/c/v1"
| |
| +-- 0x02 -> "/some-protocol/c'/v1"
|
+-- 0xdd -> "/some-protocol/d/v1" tombstone=true
```
#### Example
Starting from the tree in the construction example above, `/some-protocol/b/v1`
is removed.
`b/v1` appears once in the tree, at `0xbb`. Its tombstone bit is set to true.
Resulting tree:
```txt
(root)
|
+-- 0xaa -> "/some-protocol/a/v1"
|
+-- 0xbb -> "/some-protocol/b/v1" tombstone=true
|
+-- 0xcc
| |
| +-- 0xcc01 -> "/some-protocol/c1/v1"
| |
| +-- 0xcc02 -> "/some-protocol/c2/v1"
|
+-- 0xdd -> "/some-protocol/d/v1" tombstone=true
```
If the removed protocol string appears at more than one position (see the
insertion example, where `a/v1` exists at both `0xaa` and `0xaa01`), the
tombstone bit is set on every occurrence.


### Reintroducing a previously removed protocol string

For each instance of the protocol string in the tree, the tombstone bit should
be set to false. The protocol string should then be inserted into the tree as if
inserting a new protocol string. If the protocol string is already present as a
leaf node, no changes are required.

#### Example

Using the example for removing a protocol string as the initial state. We
reintroduce protocol string `"/some-protocol/b/v1"`.

```txt
(root)
|
+-- 0xaa -> "/some-protocol/a/v1"
|
+-- 0xbb -> "/some-protocol/b/v1"
|
+-- 0xcc
| |
| +-- 0x01 -> "/some-protocol/c/v1"
| |
| +-- 0x02 -> "/some-protocol/c'/v1"
|
+-- 0xdd -> "/some-protocol/d/v1" tombstone=true
```
Comment on lines +144 to +163

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
#### Example
Using the example for removing a protocol string as the initial state. We
reintroduce protocol string `"/some-protocol/b/v1"`.
```txt
(root)
|
+-- 0xaa -> "/some-protocol/a/v1"
|
+-- 0xbb -> "/some-protocol/b/v1"
|
+-- 0xcc
| |
| +-- 0x01 -> "/some-protocol/c/v1"
| |
| +-- 0x02 -> "/some-protocol/c'/v1"
|
+-- 0xdd -> "/some-protocol/d/v1" tombstone=true
```
#### Example
Starting from the tree in the removal example above, `/some-protocol/b/v1` is
reintroduced.
`b/v1` appears once in the tree, at `0xbb`, with its tombstone bit set. The
bit is set back to false. `0xbb` is a leaf, so `b/v1` is already at the
shallowest position that identifies it and no insertion is needed. The tree
is identical to the one before the removal.
Resulting tree:
```txt
(root)
|
+-- 0xaa -> "/some-protocol/a/v1"
|
+-- 0xbb -> "/some-protocol/b/v1"
|
+-- 0xcc
| |
| +-- 0xcc01 -> "/some-protocol/c1/v1"
| |
| +-- 0xcc02 -> "/some-protocol/c2/v1"
|
+-- 0xdd -> "/some-protocol/d/v1" tombstone=true
```


#### Example with a new leaf node

If the tombstoned protocol string is no longer at a leaf position we need to
insert a new protocol string, as well as untombstoning the existing protocol
string.

Initial State:

```txt
(root)
|
+-- 0xaa -> "/some-protocol/a/v1"
|
+-- 0xbb -> "/some-protocol/b/v1" tombstone=true
| |
| +-- 0x02 -> "/some-protocol/b'/v1"
|
+-- 0xcc
| |
| +-- 0x01 -> "/some-protocol/c/v1"
| |
| +-- 0x02 -> "/some-protocol/c'/v1"
|
+-- 0xdd -> "/some-protocol/d/v1" tombstone=true
```

After reintroducing `"/some-protocol/b/v1"`:

```txt
(root)
|
+-- 0xaa -> "/some-protocol/a/v1"
|
+-- 0xbb -> "/some-protocol/b/v1"
| |
| +-- 0x01 -> "/some-protocol/b/v1"
| |
| +-- 0x02 -> "/some-protocol/b'/v1"
|
+-- 0xcc
| |
| +-- 0x01 -> "/some-protocol/c/v1"
| |
| +-- 0x02 -> "/some-protocol/c'/v1"
|
+-- 0xdd -> "/some-protocol/d/v1" tombstone=true
```
Comment on lines +165 to +211

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
#### Example with a new leaf node
If the tombstoned protocol string is no longer at a leaf position we need to
insert a new protocol string, as well as untombstoning the existing protocol
string.
Initial State:
```txt
(root)
|
+-- 0xaa -> "/some-protocol/a/v1"
|
+-- 0xbb -> "/some-protocol/b/v1" tombstone=true
| |
| +-- 0x02 -> "/some-protocol/b'/v1"
|
+-- 0xcc
| |
| +-- 0x01 -> "/some-protocol/c/v1"
| |
| +-- 0x02 -> "/some-protocol/c'/v1"
|
+-- 0xdd -> "/some-protocol/d/v1" tombstone=true
```
After reintroducing `"/some-protocol/b/v1"`:
```txt
(root)
|
+-- 0xaa -> "/some-protocol/a/v1"
|
+-- 0xbb -> "/some-protocol/b/v1"
| |
| +-- 0x01 -> "/some-protocol/b/v1"
| |
| +-- 0x02 -> "/some-protocol/b'/v1"
|
+-- 0xcc
| |
| +-- 0x01 -> "/some-protocol/c/v1"
| |
| +-- 0x02 -> "/some-protocol/c'/v1"
|
+-- 0xdd -> "/some-protocol/d/v1" tombstone=true
```
#### Example: reintroducing a protocol string that is no longer a leaf
Starting from the tree in the withdrawal example above, two things happen in
order, using illustrative hashes as before:
| Step | Operation | Example hash | Result |
|------|----------------------------------|--------------|--------|
| 1 | insert `/some-protocol/b2/v1` | `0xbb02…` | Conflicts with the tombstoned `b/v1` at `0xbb`. `b2/v1` is inserted at `0xbb02`. `b/v1` is tombstoned, so it is not duplicated down. `0xbb` is now a non-leaf node holding a tombstoned value. |
| 2 | reintroduce `/some-protocol/b/v1` | `0xbb01…` | See below. |
State after step 1:
```txt
(root)
|
+-- 0xaa -> "/some-protocol/a/v1"
|
+-- 0xbb -> "/some-protocol/b/v1" tombstone=true
| |
| +-- 0xbb02 -> "/some-protocol/b2/v1"
|
+-- 0xcc
| |
| +-- 0xcc01 -> "/some-protocol/c1/v1"
| |
| +-- 0xcc02 -> "/some-protocol/c2/v1"
|
+-- 0xdd -> "/some-protocol/d/v1" tombstone=true
```
Step 2 does two things:
1. The tombstone bit on the existing `b/v1` entry at `0xbb` is set back to
false. Clients that built their tree before the withdrawal still send
`0xbb` for `b/v1`, and those requests must be accepted again.
2. `b/v1` is inserted as if it were a new protocol string. `0xbb` is no longer
a leaf, so the shallowest unambiguous position is `0xbb01`. A client that
connects after this point builds its tree from the advertised list
`{a/v1, b/v1, b2/v1, c1/v1, c2/v1}` and will send `0xbb01` for `b/v1`; the
server must recognise that prefix too.
Resulting tree:
```txt
(root)
|
+-- 0xaa -> "/some-protocol/a/v1"
|
+-- 0xbb -> "/some-protocol/b/v1"
| |
| +-- 0xbb01 -> "/some-protocol/b/v1"
| |
| +-- 0xbb02 -> "/some-protocol/b2/v1"
|
+-- 0xcc
| |
| +-- 0xcc01 -> "/some-protocol/c1/v1"
| |
| +-- 0xcc02 -> "/some-protocol/c2/v1"
|
+-- 0xdd -> "/some-protocol/d/v1" tombstone=true
```
`b/v1` now exists at both `0xbb` and `0xbb01`, and the server accepts either.


## Limits

Implementations SHOULD limit the number of protocol strings they track
(including tombstoned protocol strings).

## Mapping Algorithm

A mapping algorithm maps a protocol string to byte array. This byte array is

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
A mapping algorithm maps a protocol string to byte array. This byte array is
A mapping algorithm maps a protocol string to a byte array. This byte array is

used for the abbreviation tree.

SHA256 MUST be used for the mapping algorithm.

TODO: Do we want to consider faster keyed hashes? Risks complicating
implementations.

TODO: Do we need a hash function at all? Would an index into the list of a
server's protocols work instead?
Comment on lines +228 to +229

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think hashing adds anything for us here, it only makes the idea more cumbersome and less human-readable

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

yeah, this also makes me wonder about the following:

iiuc, the problem described in #700 is that once peers know each other's supported protocols, opening a stream shouldn't require sending the full protocol string again. we just need a compact mapping from protocol ids to abbreviations for that.

to solve this, this pr also defines how that mapping behaves across protocol additions, removals, readding protocols, and stale protocol sets, which introduces some complexity around tombstones and the abbreviation tree.

is all of this required to solve #700? or could something simpler, like a dictionary scoped to the connection, be enough, with peers learning about new protocols via identify push when enabled?


## Initial Server Client Exchange

When a client connects to a server, the server shares all the state necessary to
create an abbreviation tree it can use to communicate with the server. Namely:
the list of supported protocol strings, and the list of previously supported
protocol strings (tombstoned protocol strings).

The client's abbreviation tree will differ from the server's abbreviation tree
only in that protocol strings will only exist at leaf nodes in the client's
abbreviation tree. This is because non-leaf protocol strings only occur when
adding a protocol string to an existing tree.

# Multistream Select V2

## Wire Format

TODO define the wire format of multistream select v2.

## Client opening a new stream

A client identifies the protocol string it wishes to use on a stream by the
minimal hash prefix as determined by the abbreviate stream.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
minimal hash prefix as determined by the abbreviate stream.
minimal hash prefix as determined by the abbreviation tree.


## Server accepting a new stream

The server identifies the protocol string by looking up the minimal hash prefix
in the tree.

## Version Negotiation

Multistream Select V2 does not support negotiating different protocols on a
single stream like Multistream Select V1 does. A client specifies what protocol
they would like to use for a stream and MAY start sending protocol immediately.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
they would like to use for a stream and MAY start sending protocol immediately.
they would like to use for a stream and MAY start sending protocol data immediately.

If the server does not support this protocol, it MUST close the stream with

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

close or reset?

error code (TODO specify this).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggested change
error code (TODO specify this).
an error code (TODO specify this).