feat(key-wallet): add keep_txs_in_memory Cargo feature#709
feat(key-wallet): add keep_txs_in_memory Cargo feature#709QuantumExplorer wants to merge 1 commit intov0.42-devfrom
Conversation
|
Warning Rate limit exceeded
To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (12)
📝 WalkthroughWalkthroughPer-account in-memory transaction retention for ManagedCoreAccount is now controlled by a new Cargo feature ChangesIn-memory transaction feature gating (single cohort)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 👉 Get your free trial and get 200 agent minutes per Slack user (a $50 value). 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. Comment |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## v0.42-dev #709 +/- ##
=============================================
- Coverage 71.17% 71.06% -0.12%
=============================================
Files 320 320
Lines 68610 68647 +37
=============================================
- Hits 48836 48785 -51
- Misses 19774 19862 +88
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
key-wallet/src/tests/keep_txs_in_memory_tests.rs (1)
52-75: ⚡ Quick winAdd a re-confirmation idempotency test to lock in correct behavior.
The existing test verifies single-pass behavior well, but there's no coverage for calling
confirm_transaction(or the equivalent through the wallet pipeline) a second time for the same transaction whenkeep_txs_in_memoryisfalse. Such a test would catch the deduplication regression identified above and serve as a regression guard once it's fixed.📝 Suggested additional test
#[tokio::test] async fn confirm_transaction_is_idempotent_when_disabled() { let mut ctx = TestWalletContext::new_random(); ctx.managed_wallet .first_bip44_managed_account_mut() .expect("Should have BIP44 account") .set_keep_txs_in_memory(false); let tx = Transaction::dummy(&ctx.receive_address, 0..1, &[200_000]); // First pass: mempool let r1 = ctx.check_transaction(&tx, TransactionContext::Mempool).await; assert!(r1.is_relevant); // Second pass: block confirmation of the same tx let r2 = ctx.check_transaction(&tx, TransactionContext::InBlock(/* block info */)).await; let account = ctx.bip44_account(); // UTXO count must not double assert_eq!(account.utxos.len(), 1); // Balance must remain the same assert_eq!(account.balance.spendable(), 200_000); // monitor_revision must not be bumped on the second pass if no UTXO actually changed // (once the deduplication bug is fixed) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@key-wallet/src/tests/keep_txs_in_memory_tests.rs` around lines 52 - 75, Add an idempotency test that confirms calling ctx.check_transaction twice for the same Transaction (first with TransactionContext::Mempool, then with TransactionContext::InBlock) does not double-count UTXOs or change balance when keep_txs_in_memory is false: create a TestWalletContext, call .first_bip44_managed_account_mut().set_keep_txs_in_memory(false), build the same Transaction::dummy, call ctx.check_transaction(&tx, TransactionContext::Mempool).await then ctx.check_transaction(&tx, TransactionContext::InBlock(...)).await, then assert on bip44_account().utxos.len() == 1 and bip44_account().balance.spendable() == 200_000 and (if available) that bip44_account().monitor_revision was not incremented on the second pass; name the test confirm_transaction_is_idempotent_when_disabled and add it to keep_txs_in_memory_tests.rs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@key-wallet/src/managed_account/mod.rs`:
- Around line 594-596: The deduplication bug occurs because confirm_transaction
currently checks self.transactions.contains_key(&tx.txid()) which is empty when
keep_txs_in_memory is false; add a new field processed_txids: HashSet<Txid>
(annotated like spent_outpoints with serde(skip_serializing)) to track seen
txids independent of stored transactions, always insert tx.txid() into
processed_txids inside record_transaction (even when keep_txs_in_memory is
false), change confirm_transaction to use processed_txids.contains(&tx.txid())
as the early-return guard, initialize processed_txids = HashSet::new() in new()
and reconstruct it from helper.transactions during Deserialize (same place
transactions and spent_outpoints are rebuilt) so behavior is consistent after
load.
---
Nitpick comments:
In `@key-wallet/src/tests/keep_txs_in_memory_tests.rs`:
- Around line 52-75: Add an idempotency test that confirms calling
ctx.check_transaction twice for the same Transaction (first with
TransactionContext::Mempool, then with TransactionContext::InBlock) does not
double-count UTXOs or change balance when keep_txs_in_memory is false: create a
TestWalletContext, call
.first_bip44_managed_account_mut().set_keep_txs_in_memory(false), build the same
Transaction::dummy, call ctx.check_transaction(&tx,
TransactionContext::Mempool).await then ctx.check_transaction(&tx,
TransactionContext::InBlock(...)).await, then assert on
bip44_account().utxos.len() == 1 and bip44_account().balance.spendable() ==
200_000 and (if available) that bip44_account().monitor_revision was not
incremented on the second pass; name the test
confirm_transaction_is_idempotent_when_disabled and add it to
keep_txs_in_memory_tests.rs.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 31ffa800-a57f-42b1-886b-975dd1a4bcff
📒 Files selected for processing (3)
key-wallet/src/managed_account/mod.rskey-wallet/src/tests/keep_txs_in_memory_tests.rskey-wallet/src/tests/mod.rs
There was a problem hiding this comment.
♻️ Duplicate comments (1)
key-wallet/src/managed_account/mod.rs (1)
103-117:⚠️ Potential issue | 🟠 Major | 🏗️ Heavy liftDeduplication is still broken when
keep_txs_in_memoryis disabled.Line 114-Line 115 makes
has_transactionalwaysfalse, and Line 506-Line 507 makesconfirm_transactionalways re-record and report a change. The same tx replay (e.g., mempool → block, rescans) is treated as new repeatedly, which can inflate wallet-level state-change signaling and transaction counters downstream.🔧 Fix direction (dedupe independent of tx record retention)
pub struct ManagedCoreAccount { + /// Txids processed by this account regardless of record retention policy. + #[cfg_attr(feature = "serde", serde(skip_serializing))] + processed_txids: HashSet<Txid>, #[cfg(feature = "keep_txs_in_memory")] pub transactions: BTreeMap<Txid, TransactionRecord>, ... } impl ManagedCoreAccount { pub fn has_transaction(&self, txid: &Txid) -> bool { - #[cfg(feature = "keep_txs_in_memory")] - { self.transactions.contains_key(txid) } - #[cfg(not(feature = "keep_txs_in_memory"))] - { let _ = txid; false } + self.processed_txids.contains(txid) } pub(crate) fn confirm_transaction(...) -> bool { - #[cfg(feature = "keep_txs_in_memory")] - { - if !self.transactions.contains_key(&tx.txid()) { ... } - ... - } - #[cfg(not(feature = "keep_txs_in_memory"))] - { - self.record_transaction(...); - true - } + if !self.processed_txids.contains(&tx.txid()) { + self.record_transaction(...); + return true; + } + // existing context/utxo update logic } pub(crate) fn record_transaction(...) -> TransactionRecord { + self.processed_txids.insert(tx.txid()); #[cfg(feature = "keep_txs_in_memory")] self.transactions.insert(tx.txid(), tx_record); ... } }Also applies to: 466-508
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@key-wallet/src/managed_account/mod.rs` around lines 103 - 117, has_transaction currently returns false when keep_txs_in_memory is disabled, causing confirm_transaction to always treat repeats as new; add a lightweight persistent "seen txid" pathway so deduplication works regardless of tx record retention: introduce a HashSet<FieldName like seen_txids> on ManagedAccount (or reuse an existing persistent index/map), update has_transaction to check seen_txids under the #[cfg(not(feature = "keep_txs_in_memory"))] branch, and modify confirm_transaction to consult seen_txids before re-recording/reporting a change and to insert the txid into seen_txids when first processed so subsequent mempool→block or rescan events are deduplicated.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@key-wallet/src/managed_account/mod.rs`:
- Around line 103-117: has_transaction currently returns false when
keep_txs_in_memory is disabled, causing confirm_transaction to always treat
repeats as new; add a lightweight persistent "seen txid" pathway so
deduplication works regardless of tx record retention: introduce a
HashSet<FieldName like seen_txids> on ManagedAccount (or reuse an existing
persistent index/map), update has_transaction to check seen_txids under the
#[cfg(not(feature = "keep_txs_in_memory"))] branch, and modify
confirm_transaction to consult seen_txids before re-recording/reporting a change
and to insert the txid into seen_txids when first processed so subsequent
mempool→block or rescan events are deduplicated.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 5c09014e-2a07-44d5-85c9-24a384785a1b
📒 Files selected for processing (9)
key-wallet/Cargo.tomlkey-wallet/src/managed_account/managed_account_trait.rskey-wallet/src/managed_account/mod.rskey-wallet/src/test_utils/wallet.rskey-wallet/src/tests/keep_txs_in_memory_tests.rskey-wallet/src/tests/mod.rskey-wallet/src/transaction_checking/transaction_router/tests/mod.rskey-wallet/src/transaction_checking/wallet_checker.rskey-wallet/src/wallet/managed_wallet_info/wallet_info_interface.rs
🚧 Files skipped from review as they are similar to previous changes (1)
- key-wallet/src/tests/keep_txs_in_memory_tests.rs
|
This PR has merge conflicts with the base branch. Please rebase or merge the base branch into your branch to resolve them. |
2d38624 to
ce2ba4b
Compare
|
This PR has merge conflicts with the base branch. Please rebase or merge the base branch into your branch to resolve them. |
Adds an opt-in `keep_txs_in_memory` Cargo feature (NOT in default) that gates the `ManagedCoreKeysAccount::transactions` field at compile time. With the feature off, processed transactions update UTXOs and balance but no per-tx history is retained, saving memory for callers that only need balance/UTXO state. Always-present `processed_txids: HashSet<Txid>` keeps `confirm_transaction`'s dedup guard working in either configuration so re-events (mempool→block, late IS-lock arrivals) aren't replayed as new every time. Also adds `has_transaction(&Txid)` / `get_transaction(&Txid)` helpers — the former backed by `processed_txids` works in both configs, the latter is gated. `key-wallet-manager` and `key-wallet-ffi` enable the feature explicitly to keep their existing surface (event matured-coinbase, FFI tx accessors) unchanged. Wallet-level history queries (`transaction_history`, `immature_transactions`, `matured_coinbase_records`) return empty `Vec`s when the feature is off so downstream callers compile cleanly. Test files that exercise per-tx history are gated under the feature, new module `keep_txs_in_memory_tests` covers both configurations (including a regression test for confirm-transaction idempotency). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ce2ba4b to
ed46023
Compare
|
Closing because I prefer a new approach I made in #733 |
By default, records of chainlocked transactions are now dropped from each managed account's in-memory `transactions` map; only their txids are retained (in a new `finalized_txids` set on `ManagedCoreKeysAccount`) to keep dedup, `has_transaction`, and finality queries working. The opt-in `keep-finalized-transactions` Cargo feature reverts to the old behavior — every processed transaction stays in the map for the wallet's lifetime. The drop is driven off `TransactionContext::is_finalized_in_block` (chainlock only), not `is_finalized` (chainlock or IS-lock), so IS-locked records survive long enough to absorb the surrounding block-confirmation event (xdustinface review on #709). `confirm_transaction` now returns `Option<TransactionRecord>` so callers always observe the record even when it's about to be dropped. The feature is forwarded through `key-wallet-manager` and `key-wallet-ffi`. Three FFI accessors that walk the full `transactions` map (`managed_core_account_get_transaction_count`, `managed_core_account_get_transactions`, `managed_core_account_free_transactions`) are gated to the feature because they would otherwise return a partial history. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
By default, records of chainlocked transactions are now dropped from each managed account's in-memory `transactions` map; only their txids are retained (in a new `finalized_txids` set on `ManagedCoreKeysAccount`) to keep dedup, `has_transaction`, and finality queries working. The opt-in `keep-finalized-transactions` Cargo feature reverts to the old behavior — every processed transaction stays in the map for the wallet's lifetime. The drop is driven off `TransactionContext::is_finalized_in_block` (chainlock only), not `is_finalized` (chainlock or IS-lock), so IS-locked records survive long enough to absorb the surrounding block-confirmation event (xdustinface review on #709). `confirm_transaction` now returns `Option<TransactionRecord>` so callers always observe the record even when it's about to be dropped. The feature is forwarded through `key-wallet-manager` and `key-wallet-ffi`. Three FFI accessors that walk the full `transactions` map (`managed_core_account_get_transaction_count`, `managed_core_account_get_transactions`, `managed_core_account_free_transactions`) are gated to the feature because they would otherwise return a partial history. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(key-wallet): add `keep-finalized-transactions` feature By default, records of chainlocked transactions are now dropped from each managed account's in-memory `transactions` map; only their txids are retained (in a new `finalized_txids` set on `ManagedCoreKeysAccount`) to keep dedup, `has_transaction`, and finality queries working. The opt-in `keep-finalized-transactions` Cargo feature reverts to the old behavior — every processed transaction stays in the map for the wallet's lifetime. The drop is driven off `TransactionContext::is_finalized_in_block` (chainlock only), not `is_finalized` (chainlock or IS-lock), so IS-locked records survive long enough to absorb the surrounding block-confirmation event (xdustinface review on #709). `confirm_transaction` now returns `Option<TransactionRecord>` so callers always observe the record even when it's about to be dropped. The feature is forwarded through `key-wallet-manager` and `key-wallet-ffi`. Three FFI accessors that walk the full `transactions` map (`managed_core_account_get_transaction_count`, `managed_core_account_get_transactions`, `managed_core_account_free_transactions`) are gated to the feature because they would otherwise return a partial history. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(dash-spv-ffi): enable `keep-finalized-transactions` for tests Integration tests under `dash-spv-ffi/tests/dashd_sync/` walk the full per-account transaction history (`managed_core_account_get_transactions`, `managed_core_account_get_transaction_count`, `managed_core_account_free_transactions`) to verify end-to-end wallet sync against a regtest dashd. These accessors are gated behind the `keep-finalized-transactions` feature on `key-wallet-ffi`, so under the default feature set they aren't compiled in and the test build fails to resolve the imports. Add `key-wallet-ffi` as a dev-dependency with the feature enabled. Cargo unifies features across the build graph at test time, which makes the gated accessors available in the test binaries without expanding the lib crate's default feature surface. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(key-wallet): align finalization with chainlock-only semantics Address xdustinface review feedback on PR #733: - Drop `TransactionContext::is_finalized()` (the soft IS-or-chainlock check). The wallet now treats only a chainlock as finality. - Rename `TransactionContext::is_finalized_in_block()` to `is_chain_locked()` so the predicate name describes the variant rather than overloading "finalized" — same naming style as the existing `is_instant_send()` helper. - Drop `ManagedAccountTrait::transaction_is_finalized()` (the unused soft-finality variant) and rename `transaction_is_finalized_in_block()` to `transaction_is_finalized()`. Now that there's a single finalization concept, the trait method name aligns with the feature name. - Replace runtime `if cfg!(...) { ... } else { ... }` branches in `ManagedCoreKeysAccount::has_transaction` and `transaction_is_finalized` with two `#[cfg]`-gated function bodies — one per feature configuration. Same for the chainlock-driven drop call in `confirm_transaction` / `record_transaction`: the `let drop_now = …` binding now only exists when the feature is off, removing the `#[allow(unused_variables)]` workaround. - Rewrite the doc on `has_transaction` so it's clear what it actually reports (live record OR finalized-txid marker) and how callers use it (distinguish brand-new sightings from re-processings) instead of the misleading "dedup signal in `confirm_transaction`" wording. Drop logic still triggers on the strict `is_chain_locked()` check — IS-locked-but-not-yet-chainlocked records still survive so the surrounding block-confirmation event can populate height / block hash before the chainlock catches up. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(key-wallet): fix stale `transaction_is_finalized_in_block` mention The previous commit renamed the trait method to `transaction_is_finalized` but missed this Cargo.toml feature-doc reference. Caught by CodeRabbit on PR #733. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
keep_txs_in_memory(NOT enabled by default) that gates theManagedCoreAccount::transactionsfield at compile time. With the feature off, the field doesn't exist and processing transactions only updates UTXOs and balance.key-wallet-ffienables the feature explicitly to keep its FFI surface unchanged. Other crates can opt in the same way.ManagedCoreAccount::has_transaction(&Txid)/get_transaction(&Txid)helpers that work in both configurations (returningfalse/Nonewhen the feature is off) so call sites stay readable.wallet_checkerandwallet_info_interfacethrough the helpers or#[cfg]blocks. Wallet-level history queries (transaction_history,immature_transactions,matured_coinbase_records) return emptyVecs when the feature is off.ManagedAccountTrait::transactions()/transactions_mut()are also feature-gated.key-walletdev-dep self-reference asdefault-features = falseso the feature can actually be disabled when running tests.Test plan
cargo build --workspace(default features)cargo test --workspace --no-runbuilds all tests across the workspacecargo test -p key-wallet --lib(feature off) — 434 passedcargo test -p key-wallet --features keep_txs_in_memory --lib— 464 passedcargo test -p key-wallet --all-features --lib— 469 passedcargo clippy -p key-wallet --testsand--features keep_txs_in_memory --tests— cleancargo clippy --workspace --tests— clean🤖 Generated with Claude Code
Summary by CodeRabbit