Skip to content

feat(#217): resync trade_key_index to the max recovered index - #239

Open
codaMW wants to merge 2 commits into
MostroP2P:mainfrom
codaMW:feat/217-trade-key-resync
Open

feat(#217): resync trade_key_index to the max recovered index#239
codaMW wants to merge 2 commits into
MostroP2P:mainfrom
codaMW:feat/217-trade-key-resync

Conversation

@codaMW

@codaMW codaMW commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Refs #217 sub-issue of #142. Stacked on #215 (consumes the RestoreData
handling that PR introduces).

Problem

After a restore, the local trade_key_index counter is still at its
post-install value (0/1) while the recovered trades already occupy higher
indexes. The next order the user creates therefore reuses a trade key already
bound to a recovered trade a correctness bug (the daemon rejects the reused
index with CantDo(InvalidTradeIndex), and worse, two trades would share a key).

What it does

When a valid RestoreData is processed, raise trade_key_index to the maximum
recovered index across both orders and disputes. Monotonic a restore never
rewinds the counter.

  • identity::ensure_trade_key_index_at_least(floor) bumps the counter to
    max(current, floor) under the identity write lock. No-op when already ahead
    (idempotent). Persists with the same discipline as derive_trade_key: if the
    counter moves, the write must succeed or the call fails a bumped-but-
    unpersisted counter would regress on the next restart and reopen this bug.
  • orders::recovered_max_trade_index(info) the max trade_index over
    restore_orders and restore_disputes. Indexes are i64 on the wire;
    u32::try_from drops negatives and anything beyond u32::MAX rather than
    truncating a garbage value into the counter. Returns None when the restore
    carried no trades (nothing to resync to).
  • Wired into restore_session's Restored arm: resync before returning the
    info. A resync failure fails the restore rather than returning "success" with
    a counter that could hand out a reused key.

Lands on the payload shape available today (trade_index is already present)
does not wait for the snapshot contract in #216.

Acceptance criteria

  • trade_key_index raised to max(order indexes, dispute indexes) on a
    valid restore
  • Never lowered, including a restore returning fewer/older trades
  • Regression: after restoring at index > 1, the next derive_trade_key()
    returns a fresh index, not a recovered one
  • Idempotent: applying the same RestoreData twice leaves the counter
    unchanged

Tests

  • load_derive_then_delete_identity_lifecycle extended (kept in the one
    stateful test so parallel threads never race the identity_lock singleton):
    a floor below current is a no-op (never lowers), a higher floor raises,
    re-applying the same floor is idempotent, and the next derive_trade_key()
    returns a fresh index past the recovered set.
  • recovered_max_trade_index: None when empty, max spans both orders and
    disputes, negatives and out-of-range values dropped.
  • The wiring into the Restored arm is exercised end-to-end by Restore: RestoreSession handshake (send, correlate reply, subscribe) #215's
    restore_e2e_tests (needs a live regtest daemon).

cargo test --lib 115 passed / clippy -D warnings clean / flutter analyze
clean.

Merge order

Stacked on #215 depends on its RestoreData handling. Since #215 isn't in
main yet, this PR's diff currently includes #215's commits; the resync itself is
commit aa7409b (identity.rs + orders.rs, ~153 lines). Once #215 merges,
I'll rebase this onto main and the diff will show only the resync.

Summary by CodeRabbit

  • Bug Fixes

    • Prevented reuse of trade key indexes after restoring orders or disputes.
    • Improved session restoration when responses do not include a nonce.
    • Ensured invalid recovered trade indexes are ignored.
    • Improved peer-session chat key setup and node switching behavior.
  • Tests

    • Added coverage for trade index recovery, idempotent restoration, and restore-session response handling.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The restore flow now computes the highest valid recovered trade index across orders and disputes, then monotonically resynchronizes the persisted identity trade-key index before completing. Identity lifecycle and recovery-index tests cover idempotency, invalid values, and fresh derivation.

Changes

Restore recovery flow

Layer / File(s) Summary
Monotonic trade-key resynchronization
rust/src/api/identity.rs
Adds an idempotent helper that raises and persists the trade-key index when below a recovered floor, with lifecycle coverage confirming fresh subsequent derivation.
Recovered index integration
rust/src/api/orders.rs
Computes the maximum valid trade index across restored orders and disputes, applies it after successful restoration, and tests empty, combined, negative, and out-of-range inputs.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related issues

  • MostroP2P/app issue 217 — Directly covers monotonic trade-key resynchronization after recovery.
  • MostroP2P/app issue 218 — Relates to advancing the recovered trade-key index during transactional restore.
  • MostroP2P/app issue 142 — Covers preventing reuse of recovered trade keys during restore.

Possibly related PRs

  • MostroP2P/app#204 — Modifies the restore-session flow and recovered trade-index handling.
  • MostroP2P/app#225 — Extends restore-session integration with identity recovery and pending correlation.

Suggested reviewers: grunch

Poem

I’m a rabbit guarding keys,
No recycled numbers, please.
Restore orders, disputes too,
Lift the index fresh and new.
Fifty hops, then fifty-one—
Safe derivation, carrots won! 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: resyncing trade_key_index to the maximum recovered index.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🧹 Nitpick comments (2)
rust/src/api/orders.rs (1)

2922-2953: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Doc comment for restore_session is misattached to recovered_max_trade_index.

Lines 2922-2929 describe restore_session's send/await behavior and key-correlation design, but there's no blank line before Line 2930, so the whole block (2922-2936) becomes one contiguous rustdoc comment attached to fn recovered_max_trade_index (Line 2937) instead. pub async fn restore_session() (Line 2953) ends up with no doc comment of its own.

♻️ Proposed fix
-/// Send a `RestoreSession` to the active daemon and return the user's active
-/// trades/disputes. Mirrors create_order's send/await, minus the order payload.
-///
-/// Correlation: the request is sent from a fresh TRADE key (event.sender) while
-/// the Seal carries the IDENTITY key (event.identity). The daemon looks up
-/// trades by identity/master key and replies to the trade key
-/// (mostro restore_session.rs: master_key = event.identity, reply -> event.sender),
-/// so we subscribe on the trade key and correlate the reply by that pubkey.
-/// Highest trade-key index across all recovered orders and disputes (`#217`).
+/// Highest trade-key index across all recovered orders and disputes (`#217`).
 ///
 /// The counter must be raised to this so the next `derive_trade_key()` cannot
 /// hand out an index a recovered trade already owns. Returns `None` when the
 /// restore carried no trades (nothing to resync to). Indexes are `i64` on the
 /// wire; a value that is negative or beyond `u32::MAX` is not a real trade
 /// index, so it is dropped rather than truncated into the counter.
 fn recovered_max_trade_index(
     info: &mostro_core::message::RestoreSessionInfo,
 ) -> Option<u32> {

And restore the removed lines as the doc comment directly above pub async fn restore_session() (Line 2952-2953).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/src/api/orders.rs` around lines 2922 - 2953, Separate the
restore_session-specific rustdoc from recovered_max_trade_index by ending it
before the helper’s documentation, then restore that documentation immediately
above pub async fn restore_session. Keep the recovered_max_trade_index
explanation attached only to recovered_max_trade_index and preserve the existing
restore_session send/await and key-correlation details on the public function.
rust/src/api/identity.rs (1)

297-317: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep ensure_trade_key_index_at_least out of the Dart-callable API.

This setter is only used by the ignore-marked orders::restore_session() path and by tests; make it non-pub instead of pub(crate) and regenerate FRB bindings if needed.

♻️ Proposed fix
-pub async fn ensure_trade_key_index_at_least(floor: u32) -> Result<()> {
+async fn ensure_trade_key_index_at_least(floor: u32) -> Result<()> {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/src/api/identity.rs` around lines 297 - 317, Make
ensure_trade_key_index_at_least private by removing its public visibility, since
it is only used internally by orders::restore_session() and tests. Confirm the
change does not expose it through Dart-callable or generated FRB bindings, and
regenerate bindings only if required.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@rust/src/api/orders.rs`:
- Around line 2952-3033: Update the Action::CantDo rejection handling to
correlate restore requests with take_matching_restore(trade_pubkey_hex) instead
of the nonce-based take_matching_request path when the request kind is Restore.
Ensure the matched Restore waiter receives the rejection so restore_session()
returns the actual reason immediately, while preserving nonce-based correlation
for other request kinds.

---

Nitpick comments:
In `@rust/src/api/identity.rs`:
- Around line 297-317: Make ensure_trade_key_index_at_least private by removing
its public visibility, since it is only used internally by
orders::restore_session() and tests. Confirm the change does not expose it
through Dart-callable or generated FRB bindings, and regenerate bindings only if
required.

In `@rust/src/api/orders.rs`:
- Around line 2922-2953: Separate the restore_session-specific rustdoc from
recovered_max_trade_index by ending it before the helper’s documentation, then
restore that documentation immediately above pub async fn restore_session. Keep
the recovered_max_trade_index explanation attached only to
recovered_max_trade_index and preserve the existing restore_session send/await
and key-correlation details on the public function.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e7ce263d-b70c-4593-98a1-8b519886f74c

📥 Commits

Reviewing files that changed from the base of the PR and between 1931ac0 and aa7409b.

📒 Files selected for processing (3)
  • rust/src/api/identity.rs
  • rust/src/api/orders.rs
  • rust/src/mostro/actions.rs

Comment thread rust/src/api/orders.rs
codaMW added 2 commits July 30, 2026 11:32
After a restore, the local trade_key_index still sits at its post-install value
while recovered trades already occupy higher indexes — so the next order would
reuse a trade key already bound to a recovered trade. On a valid RestoreData,
raise the counter to the max recovered index across orders and disputes.

- identity::ensure_trade_key_index_at_least(floor): monotonic bump under the
  identity lock, persisted with the same fail-on-error discipline as
  derive_trade_key. No-op when already ahead (idempotent), never lowers.
- orders::recovered_max_trade_index(info): max trade_index over recovered orders
  and disputes; drops negative / out-of-range values via u32::try_from rather
  than truncating a garbage index into the counter.
- Wired into restore_session's Restored arm: resync before returning; a persist
  failure fails the restore (an un-resynced counter reopens the reuse bug).

Tests: lifecycle test extended to assert raise / never-lower / idempotent and a
fresh derive past the recovered set; pure tests for recovered_max_trade_index
(spans both collections, drops negatives/out-of-range, None when empty). The
resync wiring into the Restored arm is exercised by MostroP2P#215's E2E restore suite.

Stacked on MostroP2P#215 (consumes its RestoreData handling). Refs MostroP2P#217
…lity

- Move the restore_session rustdoc back onto restore_session; inserting
  recovered_max_trade_index between the doc and the fn had reattached it to the
  helper. Each doc now sits with its own function.
- ensure_trade_key_index_at_least: pub -> pub(crate). It's only called from
  orders::restore_session and tests, so it should not reach the FRB/Dart
  surface. (pub(crate) rather than a plain private fn, since the call crosses
  the identity/orders module boundary.)

Refs MostroP2P#217
@codaMW
codaMW force-pushed the feat/217-trade-key-resync branch from 8b4ef14 to e7471ce Compare July 30, 2026 09:43

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
rust/src/api/identity.rs (1)

1-1: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Un-rolled-back persist failure in ensure_trade_key_index_at_least lets a retried restore silently report success without ever persisting the raised counter. The root cause is in identity.rs; orders.rs only surfaces the downstream effect.

  • rust/src/api/identity.rs#L414-434: state.identity_info.trade_key_index = raised is set before db.save_identity and never rolled back on failure; combined with the raised == current no-op short-circuit, a retry with the same floor silently returns Ok(()) without retrying the persist. Also missing a publish_index call so the secure-storage mirror (issue #249) never learns of the raised index. Roll back trade_key_index to current on save failure and call publish_index(trade_key_index_tx(), raised) after a successful save.
  • rust/src/api/orders.rs#L3040-3051: because of the above, a retried restore_session() call can return Ok(info) even though the DB's trade_key_index was never durably raised — no local change needed once the identity.rs fix lands, but flagging so the contract is understood.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/src/api/identity.rs` at line 1, Update ensure_trade_key_index_at_least
to restore state.identity_info.trade_key_index to current when db.save_identity
fails, allowing retries to persist the requested floor instead of taking the
raised == current no-op path; after a successful save, call
publish_index(trade_key_index_tx(), raised) to update the secure-storage mirror.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@rust/src/api/identity.rs`:
- Around line 414-434: Update ensure_trade_key_index_at_least to restore the
in-memory index if save_identity fails, allowing a retry with the same floor to
persist it again. After a successful persistence, publish the raised index
through the existing trade-key index channel, such as
publish_index/trade_key_index_tx, and preserve the no-op behavior when the
current index already meets the floor. Extend the lifecycle test to assert
publication and add coverage for persistence failure followed by a successful
retry.

In `@rust/src/api/orders.rs`:
- Around line 3040-3051: The restore path in the match arm handling
DaemonReply::Restored depends on ensure_trade_key_index_at_least retrying
persistence when the stored counter is already high enough. Update
ensure_trade_key_index_at_least in identity.rs so each call verifies or retries
the durable counter update instead of returning early solely because the
in-memory/current value meets the floor, while preserving monotonicity and
propagating persistence failures to the restore caller.

---

Outside diff comments:
In `@rust/src/api/identity.rs`:
- Line 1: Update ensure_trade_key_index_at_least to restore
state.identity_info.trade_key_index to current when db.save_identity fails,
allowing retries to persist the requested floor instead of taking the raised ==
current no-op path; after a successful save, call
publish_index(trade_key_index_tx(), raised) to update the secure-storage mirror.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b4e4403f-d7c9-4a2d-abc8-afb81a0494a7

📥 Commits

Reviewing files that changed from the base of the PR and between aa7409b and e7471ce.

📒 Files selected for processing (2)
  • rust/src/api/identity.rs
  • rust/src/api/orders.rs

Comment thread rust/src/api/identity.rs
Comment on lines +414 to +434
pub(crate) async fn ensure_trade_key_index_at_least(floor: u32) -> Result<()> {
let mut guard = identity_lock().write().await;
let state = guard.as_mut().ok_or_else(|| anyhow!("NoIdentity"))?;
let current = state.identity_info.trade_key_index;
let raised = current.max(floor);
if raised == current {
// Already ahead of (or level with) the recovered set — no-op, no write.
return Ok(());
}
state.identity_info.trade_key_index = raised;
if let Some(db) = crate::db::app_db::db() {
db.save_identity(&state.identity_info).await.map_err(|e| {
anyhow!("StorageError: failed to persist resynced trade_key_index {raised}: {e}")
})?;
}
crate::api::logging::blog_info(
"restore",
format!("trade_key_index resynced {current} -> {raised} from recovered trades"),
);
Ok(())
}

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Persist failure leaves the counter permanently un-synced and silently swallowed on retry.

Two related durability gaps in this function:

  1. No rollback on persist failure. state.identity_info.trade_key_index = raised is set before db.save_identity runs, and is never reverted if the save fails. Because of the if raised == current { return Ok(()) } short-circuit at the top, a retry with the same floor will now see current == raised (already mutated in memory) and return Ok(()) immediately — without ever retrying the persist. Concretely: restore_session() (orders.rs L3040-3051) calls this with ? and returns Ok(info) on success — so a transient DB failure followed by a retried restore would report success to the caller while the DB's trade_key_index was never actually raised, silently reopening the exact key-reuse bug this PR closes. This is different from derive_trade_key_with, whose "keep the forward mutation on failure" design is safe only because it has no such idempotency short-circuit — every call there unconditionally attempts a fresh persist.
  2. Never publishes the raised index. Unlike derive_trade_key_with (publish_index(tx, candidate_index)) and reconcile_and_publish_to, this function never calls publish_index/trade_key_index_tx(). The whole point of that channel is so Flutter's secure-storage mirror survives loss of mostro.db (issue #249) — but a resync via restore never reaches that mirror, so a later DB loss would let load_identity_from_mnemonic fall back to a stale, pre-resync index.
🐛 Proposed fix
     state.identity_info.trade_key_index = raised;
     if let Some(db) = crate::db::app_db::db() {
-        db.save_identity(&state.identity_info).await.map_err(|e| {
-            anyhow!("StorageError: failed to persist resynced trade_key_index {raised}: {e}")
-        })?;
+        if let Err(e) = db.save_identity(&state.identity_info).await {
+            // Roll back — otherwise a retry with the same/lower floor sees
+            // `raised == current` and silently no-ops without retrying persist.
+            state.identity_info.trade_key_index = current;
+            return Err(anyhow!(
+                "StorageError: failed to persist resynced trade_key_index {raised}: {e}"
+            ));
+        }
     }
+    // Mirror to secure storage like `derive_trade_key` does — the DB is not
+    // the only durable record of this counter (issue `#249`).
+    publish_index(trade_key_index_tx(), raised);
     crate::api::logging::blog_info(
         "restore",
         format!("trade_key_index resynced {current} -> {raised} from recovered trades"),
     );
     Ok(())

Once fixed, the lifecycle test at L744-759 should also assert the raised index reaches published (the way reconciliation_publishes_only_when_the_database_is_ahead does), and a new test should cover the persist-failure/retry path.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub(crate) async fn ensure_trade_key_index_at_least(floor: u32) -> Result<()> {
let mut guard = identity_lock().write().await;
let state = guard.as_mut().ok_or_else(|| anyhow!("NoIdentity"))?;
let current = state.identity_info.trade_key_index;
let raised = current.max(floor);
if raised == current {
// Already ahead of (or level with) the recovered set — no-op, no write.
return Ok(());
}
state.identity_info.trade_key_index = raised;
if let Some(db) = crate::db::app_db::db() {
db.save_identity(&state.identity_info).await.map_err(|e| {
anyhow!("StorageError: failed to persist resynced trade_key_index {raised}: {e}")
})?;
}
crate::api::logging::blog_info(
"restore",
format!("trade_key_index resynced {current} -> {raised} from recovered trades"),
);
Ok(())
}
pub(crate) async fn ensure_trade_key_index_at_least(floor: u32) -> Result<()> {
let mut guard = identity_lock().write().await;
let state = guard.as_mut().ok_or_else(|| anyhow!("NoIdentity"))?;
let current = state.identity_info.trade_key_index;
let raised = current.max(floor);
if raised == current {
// Already ahead of (or level with) the recovered set — no-op, no write.
return Ok(());
}
state.identity_info.trade_key_index = raised;
if let Some(db) = crate::db::app_db::db() {
if let Err(e) = db.save_identity(&state.identity_info).await {
// Roll back — otherwise a retry with the same/lower floor sees
// `raised == current` and silently no-ops without retrying persist.
state.identity_info.trade_key_index = current;
return Err(anyhow!(
"StorageError: failed to persist resynced trade_key_index {raised}: {e}"
));
}
}
// Mirror to secure storage like `derive_trade_key` does — the DB is not
// the only durable record of this counter (issue `#249`).
publish_index(trade_key_index_tx(), raised);
crate::api::logging::blog_info(
"restore",
format!("trade_key_index resynced {current} -> {raised} from recovered trades"),
);
Ok(())
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/src/api/identity.rs` around lines 414 - 434, Update
ensure_trade_key_index_at_least to restore the in-memory index if save_identity
fails, allowing a retry with the same floor to persist it again. After a
successful persistence, publish the raised index through the existing trade-key
index channel, such as publish_index/trade_key_index_tx, and preserve the no-op
behavior when the current index already meets the floor. Extend the lifecycle
test to assert publication and add coverage for persistence failure followed by
a successful retry.

Comment thread rust/src/api/orders.rs
Comment on lines +3040 to +3051
Ok(Ok(DaemonReply::Restored(info))) => {
// #217: raise trade_key_index past every recovered trade before
// returning, so the next derive_trade_key() can't reuse a key a
// recovered trade already owns. Monotonic and idempotent. A persist
// failure fails the restore: an un-resynced counter reopens the
// key-reuse bug this closes, so silent success would be worse than
// a surfaced error the caller can retry.
if let Some(floor) = recovered_max_trade_index(&info) {
crate::api::identity::ensure_trade_key_index_at_least(floor).await?;
}
Ok(info)
}

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Depends on identity.rs fix.

This correctly derives the floor and fails the restore (?) if the resync can't be persisted — good, matches the documented "silent success would be worse" intent. However this relies on ensure_trade_key_index_at_least actually retrying the persist on a retried call, which it currently does not (see the identity.rs L414-434 comment) — a retried restore could return Ok(info) here while the DB counter was never durably raised.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@rust/src/api/orders.rs` around lines 3040 - 3051, The restore path in the
match arm handling DaemonReply::Restored depends on
ensure_trade_key_index_at_least retrying persistence when the stored counter is
already high enough. Update ensure_trade_key_index_at_least in identity.rs so
each call verifies or retries the durable counter update instead of returning
early solely because the in-memory/current value meets the floor, while
preserving monotonicity and propagating persistence failures to the restore
caller.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant