Commit 062e73c
authored
feat(dash-spv): filter re-match across reorg and safe auto-rebroadcast (#170)
* feat(key-wallet-manager): add `WalletEvent::TxRepeatedlyOrphaned` variant
New variant emitted by the SPV mempool manager when an outgoing
transaction demoted by reorg has exhausted its rebroadcast retry
budget. UI consumers surface this so the user can decide to abandon,
re-sign, or wait.
The `wallet_id` field is `Option<WalletId>` because the rebroadcast
loop may emit this for a txid that is no longer attributable to any
managed wallet. `WalletEvent::wallet_id()` is widened to
`Option<WalletId>` to accommodate.
* feat(dash-spv): drive wallet rewind and per-wallet effective floor on `ChainReorg`
`FiltersManager` now calls `WalletInterface::rewind_to_height` when it
observes a `ChainReorg` event, then resets its filter scan cursor to
the effective per-wallet floor (`min(fork_height, wallet.synced_height)`)
instead of always to `fork_height`. A wallet whose ingest tip is behind
the fork never saw the orphaned chain, so the filter pipeline does not
need to re-match the segment between its tip and the fork.
If the wallet refuses the rewind (e.g. chainlock floor, defense in depth since the SPV cascade already enforces this upstream), log and fall back to `fork_height` for the floor without touching wallet state.
Renames `reset_for_reorg`'s parameter to `effective_floor` to make the
contract explicit.
* feat(dash-spv): auto-rebroadcast reorg-demoted transactions with safety checks
`MempoolManager` now subscribes to `WalletEvent` broadcasts and drives
an auto-rebroadcast pipeline for transactions demoted by a chain reorg:
- `WalletEvent::Reorg` enqueues each `demoted_txid` after a
chainlock-floor defense check (the wallet already refuses to rewind
below its chainlock floor upstream, this branch is belt-and-braces).
- Block-height `nLockTime` is compared against the current chain tip;
unsatisfied locktimes defer the broadcast. Timestamp locktimes are
compared best-effort against wall clock, never against an MTP cache
the SPV side does not maintain.
- An input-conflict check evicts pending entries whose inputs collide
with any transaction confirming on the new chain (delivered via
`WalletEvent::BlockProcessed`).
- Each attempt advances an exponential backoff capped at one hour.
After `MAX_REORG_REBROADCAST_ATTEMPTS` (10) failures the entry is
dropped and a `WalletEvent::TxRepeatedlyOrphaned` is emitted so
the UI can surface it for user intervention.
The manager owns a forwarder `broadcast::channel` so it can both subscribe to wallet events (replaceable via `set_wallet_event_subscription`) and emit `TxRepeatedlyOrphaned` without changing the trait surface.
The constructor gains a `chainlock_height: Arc<AtomicU32>` parameter threaded through from `lifecycle.rs`. `MempoolManager` also picks up a `current_tip_height` field updated via `SyncManager::update_target_height`.
Tests cover the happy path, input-conflict eviction, locktime defer
followed by tip advance, and the retry-cap orphan event.
* chore(dash-spv-ffi): mark new `TxRepeatedlyOrphaned` variant as not-yet-bridged
Add the missing match arm in `FFIWalletEventCallbacks::dispatch`. The
variant is left without an FFI surface for now (matching the existing
`Reorg` arm) so the C ABI stays unchanged. Wiring a dedicated callback
is deferred to a follow-up that surfaces orphaned-tx state to the UI.
* chore: pr cleanup: fix FQ paths and remove what-comments
* test(dash-spv): cover rebroadcast backoff, chainlock floor, and channel-driven paths
Addresses four `manki-review` testing-coverage findings on PR #170:
- Extend `test_reorg_demoted_tx_rebroadcast_happy_path` with a second immediate `drive_reorg_rebroadcast` call to assert the exponential backoff suppresses a duplicate broadcast and does not bump `attempts`.
- Add `test_chainlock_floor_prevents_enqueue` exercising the chainlock-floor defense in `enqueue_demoted_for_rebroadcast` when `chainlock_height > current_tip_height`.
- Add `test_input_conflict_via_channel_evicts_rebroadcast` so the `WalletEvent::BlockProcessed` arm of `drain_wallet_events` is exercised through the channel, not by calling `evict_conflicting_rebroadcasts` directly.
- Extend `test_block_processed_removes_confirmed_txids` to seed `pending_rebroadcast` via `enqueue_demoted_for_rebroadcast` and assert the confirmed txid is removed from it, locking in the `BlockProcessed` handler's `pending_rebroadcast.remove(txid)` line.
Promotes `enqueue_demoted_for_rebroadcast` to `pub(super)` so sibling-module tests can drive the rebroadcast queue through the public API.
* ci: trigger workflows on feat/filter-rematch-and-rebroadcast
* test(dash-spv): narrow `enqueue_demoted_for_rebroadcast` visibility and clarify chainlock-floor test
Keep `enqueue_demoted_for_rebroadcast` private and expose a `#[cfg(test)] pub(super)` wrapper so sibling-module tests in `sync_manager.rs` can seed `pending_rebroadcast` without widening production visibility.
Drop the unused `wallet_event_rx` / `set_wallet_event_subscription` setup from `test_chainlock_floor_prevents_enqueue` (the test exercises the direct call, not the channel path) and document the ordering invariant the `transactions` postcondition relies on (the guard must short-circuit before the wallet fetch/insert path).1 parent ac05e6f commit 062e73c
8 files changed
Lines changed: 904 additions & 36 deletions
File tree
- dash-spv-ffi/src
- dash-spv/src
- client
- sync
- filters
- mempool
- key-wallet-manager/src
- test_utils
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1215 | 1215 | | |
1216 | 1216 | | |
1217 | 1217 | | |
| 1218 | + | |
| 1219 | + | |
| 1220 | + | |
| 1221 | + | |
| 1222 | + | |
| 1223 | + | |
| 1224 | + | |
| 1225 | + | |
1218 | 1226 | | |
1219 | 1227 | | |
1220 | 1228 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
163 | 163 | | |
164 | 164 | | |
165 | 165 | | |
166 | | - | |
| 166 | + | |
| 167 | + | |
167 | 168 | | |
168 | 169 | | |
169 | 170 | | |
170 | 171 | | |
171 | | - | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
172 | 176 | | |
173 | 177 | | |
174 | 178 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
149 | 149 | | |
150 | 150 | | |
151 | 151 | | |
152 | | - | |
153 | | - | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
154 | 177 | | |
155 | | - | |
156 | | - | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
157 | 187 | | |
158 | | - | |
159 | | - | |
160 | | - | |
161 | | - | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
162 | 192 | | |
163 | 193 | | |
164 | 194 | | |
| |||
2405 | 2435 | | |
2406 | 2436 | | |
2407 | 2437 | | |
| 2438 | + | |
| 2439 | + | |
| 2440 | + | |
| 2441 | + | |
| 2442 | + | |
| 2443 | + | |
| 2444 | + | |
| 2445 | + | |
| 2446 | + | |
| 2447 | + | |
| 2448 | + | |
| 2449 | + | |
| 2450 | + | |
| 2451 | + | |
| 2452 | + | |
| 2453 | + | |
| 2454 | + | |
| 2455 | + | |
| 2456 | + | |
| 2457 | + | |
| 2458 | + | |
| 2459 | + | |
| 2460 | + | |
| 2461 | + | |
| 2462 | + | |
| 2463 | + | |
| 2464 | + | |
| 2465 | + | |
| 2466 | + | |
| 2467 | + | |
| 2468 | + | |
2408 | 2469 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
190 | 190 | | |
191 | 191 | | |
192 | 192 | | |
193 | | - | |
| 193 | + | |
194 | 194 | | |
195 | 195 | | |
196 | | - | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
197 | 202 | | |
198 | 203 | | |
199 | 204 | | |
| |||
0 commit comments