Skip to content

Conversation

@insipx
Copy link
Contributor

@insipx insipx commented Dec 3, 2025

Add icebox support by requiring a CursorStore in protocol.order.Ordered and icing unresolved orphans during OrderedEnvelopeCollection::order

Introduce a store-backed ordering flow that recovers children, sorts by timestamp/causal order, requests dependencies as RequiredDependency, and ices unresolved orphans in the store; update resolver and traits to accept RequiredDependency and propagate store/decode errors. Core changes center on protocol.order.Ordered and dependent combinators/resolvers, with Envelope dropping consume and tests updated accordingly.

📍Where to Start

Start with the OrderedEnvelopeCollection for Ordered::order implementation in order.rs, then review required_dependencies, recover_lost_children, and the resolver changes in network_backoff.rs.


📊 Macroscope summarized 38c66a7. 12 files reviewed, 3 issues evaluated, 3 issues filtered, 0 comments posted

🗂️ Filtered Issues

xmtp_api_d14n/src/protocol/order.rs — 0 comments posted, 1 evaluated, 1 filtered
  • line 176: The filter logic change reduces matching precision: the old code matched envelopes by BOTH topic AND cursor using MissingEnvelope::new(topic, cursor), but the new code only matches by cursor alone. This could cause the resolver to return envelopes with matching cursors but incorrect topics, potentially masking bugs where topic matching is important. If RequiredDependency contains topic information that should be used for matching, it's being ignored. [ Previously rejected ]
xmtp_api_d14n/src/protocol/resolve/network_backoff.rs — 0 comments posted, 1 evaluated, 1 filtered
  • line 53: The resolve function sleeps before making the first network query. On the first iteration with attempts = 0, self.backoff.backoff(0, time_spent) returns Some(duration) (base duration + jitter), causing an unnecessary delay before any query attempt. The first resolution attempt should be made immediately without sleeping. [ Out of scope ]
xmtp_api_d14n/src/protocol/traits.rs — 0 comments posted, 1 evaluated, 1 filtered
  • line 88: In is_retryable() for EnvelopeError, Self::Decode(_) => true marks prost::DecodeError as retryable. However, decode errors indicate malformed protobuf bytes, and retrying a decode operation on the same malformed bytes will produce the same error. This is inconsistent with the rationale documented in ConversionError's is_retryable() implementation which explicitly states that conversions are not retryable because "the bytes are static". This could cause infinite retry loops or wasted retry attempts on permanently failing operations. [ Already posted ]

@insipx
Copy link
Contributor Author

insipx commented Dec 3, 2025

@claude
Copy link

claude bot commented Dec 3, 2025

Claude finished @insipx's task —— View job


PR Review - December 15, 2025, 7:10 PM UTC

This PR successfully integrates icebox functionality into the ordering flow. The core implementation is solid with good property-based testing. Here are the key findings:

Critical Issues

1. Decode Error Retryability (xmtp_api_d14n/src/protocol/traits.rs:88)

  • Already flagged by Macroscope, but worth emphasizing: Self::Decode(_) => true marks protobuf decode errors as retryable
  • Decode errors on static bytes will never succeed on retry - this creates futile retry loops
  • The comment in ConversionError::is_retryable() explicitly states conversions aren't retryable because "the bytes are static"
  • Recommendation: Change to Self::Decode(_) => false for consistency and correctness

Correctness Concerns

2. Potential Infinite Loop in Recovery (xmtp_api_d14n/src/protocol/order.rs:136)

  • recover_lost_children() is called inside the resolution loop after each partial resolution
  • If a child is recovered but still has missing dependencies, it gets added to missing, resolution partially succeeds on unrelated envelopes, child gets re-recovered in next iteration
  • This could cause the same orphaned child to be recovered repeatedly until resolution timeout
  • The deduplication via cursors HashSet (line 87) only prevents duplicates within a single recovery, not across loop iterations
  • Recommendation: Consider tracking recovered children across the entire order() call to prevent repeated recovery attempts

3. Unresolved Dependencies Silently Dropped (xmtp_api_d14n/src/protocol/order.rs:128)

  • Resolved { mut resolved, .. } ignores the unresolved field with .. pattern
  • If resolver returns partial results with some dependencies marked as unresolved, those are never tracked
  • Old code explicitly handled unresolved envelopes; new code may loop until timeout if resolver keeps returning different resolved items while some remain unresolved
  • Recommendation: Consider logging or tracking unresolved to provide visibility into why resolution may be taking multiple iterations

Design Observations

4. Test Coverage for Icebox Recovery (xmtp_api_d14n/src/protocol/order.rs:67-95)

  • The recover_lost_children() logic is not directly tested in the order.rs tests
  • Property test covers unresolvable dependencies but uses NoCursorStore which always returns empty for resolve_children()
  • Recommendation: Add test cases with a mock CursorStore that returns orphaned children to verify the recovery logic

5. Performance: Multiple HashSet Constructions (xmtp_api_d14n/src/protocol/order.rs:68-83)

  • Already addressed by other reviewers, but line 68 collects cursors into Vec, then line 83 constructs HashSet from it
  • Recommendation: Already suggested to optimize this

Security & Safety

No security concerns identified. The changes maintain proper error handling and don't introduce unsafe operations.

Summary

The core icebox integration is well-designed with solid property-based testing. Main concerns are:

  1. Must fix: Decode error retryability inconsistency
  2. Should investigate: Potential for repeated child recovery in loops
  3. Nice to have: Better visibility into unresolved dependencies, direct testing of icebox recovery

The codecov report shows 83.7% patch coverage with 22 missing lines. Missing coverage appears concentrated in error paths and test utilities, which is acceptable.


@insipx insipx force-pushed the insipx/icebox-in-ordering branch 2 times, most recently from 83b5cb3 to 12d47bd Compare December 3, 2025 22:37
@insipx insipx force-pushed the insipx/cursor-store-icebox branch from a81a59b to da907f5 Compare December 3, 2025 22:37
@insipx insipx force-pushed the insipx/icebox-in-ordering branch from 12d47bd to 6e03d9f Compare December 3, 2025 22:38
@insipx insipx force-pushed the insipx/cursor-store-icebox branch from da907f5 to c5e17ad Compare December 3, 2025 22:38
@codecov
Copy link

codecov bot commented Dec 3, 2025

Codecov Report

❌ Patch coverage is 82.48175% with 24 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.61%. Comparing base (0949b7d) to head (38c66a7).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
xmtp_api_d14n/src/protocol/order.rs 81.48% 10 Missing ⚠️
..._api_d14n/src/protocol/utils/test/test_envelope.rs 80.00% 5 Missing ⚠️
xmtp_api_d14n/src/protocol/sort/causal.rs 0.00% 4 Missing ⚠️
xmtp_api_d14n/src/protocol/traits.rs 0.00% 2 Missing ⚠️
..._d14n/src/protocol/traits/dependency_resolution.rs 33.33% 2 Missing ⚠️
.../protocol/utils/test/dependency_resolution_test.rs 92.85% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2882      +/-   ##
==========================================
+ Coverage   73.49%   73.61%   +0.11%     
==========================================
  Files         405      405              
  Lines       51656    51710      +54     
==========================================
+ Hits        37965    38064      +99     
+ Misses      13691    13646      -45     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@insipx insipx force-pushed the insipx/cursor-store-icebox branch from c5e17ad to 4f2808e Compare December 5, 2025 00:42
@insipx insipx force-pushed the insipx/icebox-in-ordering branch from 6e03d9f to d7c373b Compare December 5, 2025 00:42
@insipx insipx moved this to Icebox & Dependencies in Decentralization Client Dec 8, 2025
@insipx insipx force-pushed the insipx/cursor-store-icebox branch from 4f2808e to 80f2a62 Compare December 8, 2025 23:30
@insipx insipx force-pushed the insipx/icebox-in-ordering branch from d7c373b to 798f6ab Compare December 8, 2025 23:30
@insipx insipx marked this pull request as draft December 8, 2025 23:32
@insipx insipx force-pushed the insipx/cursor-store-icebox branch from 80f2a62 to 9d5c60c Compare December 9, 2025 20:23
@insipx insipx force-pushed the insipx/icebox-in-ordering branch from 798f6ab to c7cbc5c Compare December 9, 2025 20:23
@insipx insipx force-pushed the insipx/icebox-in-ordering branch from c7cbc5c to f5c5824 Compare December 9, 2025 20:30
@insipx insipx force-pushed the insipx/icebox-in-ordering branch from fe451e6 to c8856e5 Compare December 10, 2025 21:39
@insipx insipx force-pushed the insipx/cursor-store-icebox branch from c49f3ba to e3540d3 Compare December 10, 2025 22:04
@insipx insipx force-pushed the insipx/icebox-in-ordering branch from c8856e5 to 37ca7b6 Compare December 10, 2025 22:04
@insipx insipx changed the title add icebox to ordering impl add icebox to OrderedEnvelopeCollection implementation Dec 10, 2025
@insipx insipx force-pushed the insipx/icebox-in-ordering branch from 37ca7b6 to b4407a8 Compare December 11, 2025 16:05
@insipx insipx force-pushed the insipx/cursor-store-icebox branch from e3540d3 to 7a08a24 Compare December 11, 2025 16:05
@insipx insipx force-pushed the insipx/cursor-store-icebox branch from 7a08a24 to e626807 Compare December 11, 2025 19:43
@insipx insipx force-pushed the insipx/icebox-in-ordering branch 2 times, most recently from 3b21dc8 to 9a5370b Compare December 11, 2025 20:42
@insipx insipx force-pushed the insipx/cursor-store-icebox branch from e626807 to 35ec6e7 Compare December 11, 2025 20:42
@insipx insipx force-pushed the insipx/icebox-in-ordering branch from 9a5370b to debc116 Compare December 11, 2025 21:02
Base automatically changed from insipx/cursor-store-icebox to main December 15, 2025 17:16
@insipx insipx force-pushed the insipx/icebox-in-ordering branch from debc116 to 5ab6d8e Compare December 15, 2025 17:18
@insipx insipx force-pushed the insipx/icebox-in-ordering branch from 5ab6d8e to 7840c4a Compare December 15, 2025 17:53
@insipx insipx force-pushed the insipx/icebox-in-ordering branch from 7840c4a to 38c66a7 Compare December 15, 2025 19:08
@insipx insipx enabled auto-merge (squash) December 15, 2025 19:08
@insipx insipx merged commit 7f5684f into main Dec 15, 2025
16 checks passed
@insipx insipx deleted the insipx/icebox-in-ordering branch December 15, 2025 19:21
rygine pushed a commit that referenced this pull request Dec 28, 2025
<!-- Macroscope's pull request summary starts here -->
<!-- Macroscope will only edit the content between these invisible
markers, and the markers themselves will not be visible in the GitHub
rendered markdown. -->
<!-- If you delete either of the start / end markers from your PR's
description, Macroscope will post its summary as a comment. -->
### Add icebox support to
`protocol.order.OrderedEnvelopeCollection::order` by integrating a
`CursorStore` into `Ordered` and the ordered query path
Introduce a store-backed ordering flow that computes
`RequiredDependency`, ices unresolved envelopes, and periodically
recovers orphaned children, updating resolver signatures to use
`RequiredDependency` and removing `Envelope::consume`. Key changes land
in
[order.rs](https://github.com/xmtp/libxmtp/pull/2882/files#diff-78b9f275c691ca4b70a53c2a710b84b741cf86e28ac909dc8b250cbe9b61ed1e)
with corresponding combinator and resolver updates.

#### 📍Where to Start
Start with `Ordered<T, R, S>::order` in
[order.rs](https://github.com/xmtp/libxmtp/pull/2882/files#diff-78b9f275c691ca4b70a53c2a710b84b741cf86e28ac909dc8b250cbe9b61ed1e),
then review the `ResolveDependencies` signature change and handling of
`RequiredDependency` in
[network_backoff.rs](https://github.com/xmtp/libxmtp/pull/2882/files#diff-de7799794fbc3300554d0474e251ec4117a5b0773a085ba0f94dd6f863c2f51e).

----
<!-- Macroscope's review summary starts here -->

<details>
<summary>📊 <a href="https://app.macroscope.com">Macroscope</a>
summarized 7840c4a. 12 files reviewed, 5 issues evaluated, 5 issues
filtered, 0 comments posted</summary>

### 🗂️ Filtered Issues
<details>
<summary>xmtp_api_d14n/src/protocol/order.rs — 0 comments posted, 3
evaluated, 3 filtered</summary>

- [line
87](https://github.com/xmtp/libxmtp/blob/7840c4a18d70ca3574b8d1e9cdf7449354df8fa5/xmtp_api_d14n/src/protocol/order.rs#L87):
In `recover_lost_children`, orphaned envelopes retrieved from the store
are decoded using `T::decode` and any `prost::DecodeError` will
propagate as `EnvelopeError::Decode`. However, in the `RetryableError`
impl at line 87 of `traits.rs`, `Self::Decode(_) => true` marks decode
errors as retryable. Decode errors typically indicate
malformed/corrupted data that won't be fixed by retrying, so this could
cause unnecessary retry loops. <b>[ Already posted ]</b>
- [line
125](https://github.com/xmtp/libxmtp/blob/7840c4a18d70ca3574b8d1e9cdf7449354df8fa5/xmtp_api_d14n/src/protocol/order.rs#L125):
The `unresolved` field from `Resolved` is ignored with the `..` pattern
on line 125. If the resolver returns partial results where some
dependencies are resolved and some are `unresolved`, the unresolved
dependencies are no longer tracked or handled. The old code explicitly
handled unresolved envelopes by removing them from the missing list.
Now, if resolution returns some resolved items but also reports some as
unresolved, those unresolved envelopes will just be re-appended and
potentially loop until they eventually get iced when
`resolved.is_empty()`, which may never happen if other unrelated
envelopes keep getting resolved. <b>[ Low confidence ]</b>
- [line
173](https://github.com/xmtp/libxmtp/blob/7840c4a18d70ca3574b8d1e9cdf7449354df8fa5/xmtp_api_d14n/src/protocol/order.rs#L173):
The resolver now only matches envelopes by `cursor`, ignoring the
`topic` that was previously checked. If cursors are not globally unique
and can repeat across different topics, this could return incorrect
envelopes that happen to share the same cursor but belong to a different
topic. The old code verified both `topic` AND `cursor` matched via
`MissingEnvelope::new(topic, cursor)`, but the new code discards topic
information entirely. <b>[ Previously rejected ]</b>
</details>

<details>
<summary>xmtp_api_d14n/src/protocol/resolve/network_backoff.rs — 0
comments posted, 1 evaluated, 1 filtered</summary>

- [line
53](https://github.com/xmtp/libxmtp/blob/7840c4a18d70ca3574b8d1e9cdf7449354df8fa5/xmtp_api_d14n/src/protocol/resolve/network_backoff.rs#L53):
The `resolve` function sleeps before making the first network query. On
the first iteration with `attempts = 0`, `self.backoff.backoff(0,
time_spent)` returns `Some(duration)` (base duration + jitter), causing
an unnecessary delay before any query attempt. The first resolution
attempt should be made immediately without sleeping. <b>[ Out of scope
]</b>
</details>

<details>
<summary>xmtp_api_d14n/src/protocol/traits.rs — 0 comments posted, 1
evaluated, 1 filtered</summary>

- [line
88](https://github.com/xmtp/libxmtp/blob/7840c4a18d70ca3574b8d1e9cdf7449354df8fa5/xmtp_api_d14n/src/protocol/traits.rs#L88):
In `is_retryable()` for `EnvelopeError`, `Self::Decode(_) => true` marks
`prost::DecodeError` as retryable. However, decode errors indicate
malformed protobuf bytes, and retrying a decode operation on the same
malformed bytes will produce the same error. This is inconsistent with
the rationale documented in `ConversionError`'s `is_retryable()`
implementation which explicitly states that conversions are not
retryable because "the bytes are static". This could cause infinite
retry loops or wasted retry attempts on permanently failing operations.
<b>[ Already posted ]</b>
</details>


</details><!-- Macroscope's review summary ends here -->

<!-- macroscope-ui-refresh -->
<!-- Macroscope's pull request summary ends here -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

No open projects
Status: Icebox & Dependencies

Development

Successfully merging this pull request may close these issues.

3 participants