Commit 35d1ca7
committed
fix(sync): gate peer 'started' status on the peer's own Synchronize, not core.update()
Flip status to 'started' when the peer's own first Synchronize wire
message is processed - hypercore records exactly this per-peer fact as
peer.remoteSynced, set synchronously at the top of peer.onsync(). We
intercept by shadowing onsync on the peer instance, the same
interception already used for onbitfield/onrange (hypercore dispatches
wire messages by property lookup on the channel's userData). This fixes
all three failure modes of the update()-based signal covered by the
previous commit's tests. It also removes an unhandled-rejection hazard:
the old .then() had no rejection handler, and hypercore rejects pending
update() calls when a core closes.
One real case exists where the first Synchronize never arrives on a live
channel: a close/open crossing on the same protomux - both sides disable
and re-enable a namespace around the same time, which routinely happens
during the invite flow's capability churn. One side ends up with a fresh
peer holding no baseline while the remote believes its old session is
still live, so it never re-sends its state, and hypercore does not
recover on its own. There is no reliable in-band repair: no message we
can send guarantees a Synchronize in reply, the duplicate channel open
is dropped by protomux before hypercore sees it, and forcing a channel
renegotiation from this observation layer destabilizes the layers that
own the channel lifecycle (tried; it could strand a peer with no channel
at all). So after 5s the peer is treated as started with its
channel-level state unknown. This does not fabricate sync state: a
peer's blocks are counted from its pre-have bitfields (broadcast over
the project creator core at connect and on append) merged with the
channel-level bitfield, so a peer that has data is still counted
correctly - the fallback stops gating counts that are already correct,
and a Synchronize arriving later still updates state. The root fix is
upstream: protomux channel renegotiation needs a close reason / channel
generation (hypercore's own Peer#onclose has a TODO to this effect).
Peers whose Synchronize was processed before the core was attached are
detected via remoteSynced and marked started immediately - defensive
only: production wiring attaches cores synchronously on 'add-core',
before a channel handshake can complete, but nothing in this class
enforces that.
Adds tests for the fallback (a silent session must not block sync state
forever, asserted to have resolved without a Synchronize) and for the
attach-late invariant.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CzqT9t19AV5voYFM2vYV8h1 parent a45e48c commit 35d1ca7
2 files changed
Lines changed: 160 additions & 7 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
43 | 43 | | |
44 | 44 | | |
45 | 45 | | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
46 | 55 | | |
47 | 56 | | |
48 | 57 | | |
| |||
279 | 288 | | |
280 | 289 | | |
281 | 290 | | |
282 | | - | |
283 | | - | |
284 | | - | |
285 | | - | |
286 | | - | |
287 | | - | |
288 | 291 | | |
289 | 292 | | |
290 | 293 | | |
| |||
294 | 297 | | |
295 | 298 | | |
296 | 299 | | |
297 | | - | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
298 | 303 | | |
299 | 304 | | |
300 | 305 | | |
| |||
307 | 312 | | |
308 | 313 | | |
309 | 314 | | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
310 | 388 | | |
311 | 389 | | |
312 | 390 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
640 | 640 | | |
641 | 641 | | |
642 | 642 | | |
| 643 | + | |
| 644 | + | |
| 645 | + | |
| 646 | + | |
| 647 | + | |
| 648 | + | |
| 649 | + | |
| 650 | + | |
| 651 | + | |
| 652 | + | |
| 653 | + | |
| 654 | + | |
| 655 | + | |
| 656 | + | |
| 657 | + | |
| 658 | + | |
| 659 | + | |
| 660 | + | |
| 661 | + | |
| 662 | + | |
| 663 | + | |
| 664 | + | |
| 665 | + | |
| 666 | + | |
| 667 | + | |
| 668 | + | |
| 669 | + | |
| 670 | + | |
| 671 | + | |
| 672 | + | |
| 673 | + | |
| 674 | + | |
| 675 | + | |
| 676 | + | |
| 677 | + | |
| 678 | + | |
| 679 | + | |
| 680 | + | |
| 681 | + | |
| 682 | + | |
| 683 | + | |
| 684 | + | |
| 685 | + | |
| 686 | + | |
| 687 | + | |
| 688 | + | |
| 689 | + | |
| 690 | + | |
| 691 | + | |
| 692 | + | |
| 693 | + | |
| 694 | + | |
| 695 | + | |
| 696 | + | |
| 697 | + | |
| 698 | + | |
| 699 | + | |
| 700 | + | |
| 701 | + | |
| 702 | + | |
| 703 | + | |
| 704 | + | |
| 705 | + | |
| 706 | + | |
| 707 | + | |
| 708 | + | |
| 709 | + | |
| 710 | + | |
| 711 | + | |
| 712 | + | |
| 713 | + | |
| 714 | + | |
| 715 | + | |
| 716 | + | |
| 717 | + | |
643 | 718 | | |
644 | 719 | | |
645 | 720 | | |
| |||
0 commit comments