fix(identify): swallow StreamStateError on close#3423
fix(identify): swallow StreamStateError on close#3423wjmelements wants to merge 8 commits intolibp2p:mainfrom
Conversation
|
Hi @wjmelements do you think you could reproduce this behavior inside of a test? |
byteStream will only push the read buffer back to the stream if there are bytes leftover. any idea what those bytes are? |
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
I pushed a test that reproduces the same exception I was seeing.
It's To summarize that code, the identify response gets split up if there is a SignedPeerRecord and the message will be larger than |
This is not spec-compliant behaviour, the spec clearly says the remote should send To move forward here we should probably read multiple identify messages until the stream closes (with an appropriate timeout) and compose the final message from whatever we receive. We definitely want to prefer signed peer records if they are available. |
|
It looks like go iterates over 10 messages with a 5 second timeout for the identify stream. It collects all the messages and merges them before writing to the peer store. All identify responses are dropped if the stream times out. |
Ok, I plan to implement similar behavior for js. |
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
There is an update to the Identify spec with message-splitting taking shape here. Any changes made here should make the js implementation conform to the newly more detailed spec. |
Assisted-by: Claude:claude-sonnet-4-6
…ge has addresses Assisted-by: Claude:claude-sonnet-4-6
| while (remaining.length > 0) { | ||
| if (!guaranteeFirst || current.length > 0) { | ||
| const candidate = buildCandidate([...current, remaining[0]]) | ||
| if (IdentifyMessage.encode(candidate).length > maxSize) { | ||
| break | ||
| } | ||
| } | ||
| current.push(remaining.shift()!) |
There was a problem hiding this comment.
I also tried a binary search approach. The speedup was negligible until there were an unreasonable number of addresses so I don't think it justifies the additional complexity.
⎿ --- 10 addresses, 20 protocols ---
linear 5000 iterations 43.8 ms (8.8 µs/op)
binary search 5000 iterations 32.2 ms (6.4 µs/op)
speedup: 1.36x
address count matches: 10
--- 20 addresses, 20 protocols ---
linear 5000 iterations 37.3 ms (7.5 µs/op)
binary search 5000 iterations 38.5 ms (7.7 µs/op)
speedup: 0.97x
address count matches: 20
--- 100 addresses, 20 protocols ---
linear 2000 iterations 35.1 ms (17.6 µs/op)
binary search 2000 iterations 33.3 ms (16.6 µs/op)
speedup: 1.06x
address count matches: 100
--- 500 addresses, 20 protocols ---
linear 500 iterations 4445.9 ms (8891.8 µs/op)
binary search 500 iterations 199.6 ms (399.1 µs/op)
speedup: 22.28x
address count matches: 500
--- 1000 addresses, 20 protocols ---
linear 100 iterations 2066.3 ms (20662.7 µs/op)
binary search 100 iterations 157.9 ms (1578.8 µs/op)
speedup: 13.09x
address count matches: 1000
Reviewer @tabcat
This was causing failures with identify when I was doing a quick Helia bitswap with Kubo.
Kubo closes the stream fully immediately after sending the identify response.
The JS identify client calls pb.unwrap().unwrap().close() after pb.read(); this throws StreamStateError because byteStream.unwrap() tries to push the internal read buffer onto a stream whose readStatus is closed.
The error is caught and the stream aborted, discarding a successfully-read message, so consumeIdentifyMessage is never called, and peer:identify never fires.
Consequently, Helia never gets onConnect called for Kubo peers.
(I'm not too familiar with this code so maybe there is a better way to fix this.)
Changes