Commit b879fe1
authored
feat(dash-spv): masternode list and quorum rewind across reorg (#165)
* feat(dash): add `MasternodeListEngine::truncate_above` for reorg rewind
Adds a primitive that drops every piece of cached state whose anchor sits above a target height: `masternode_lists`, hash-keyed `known_snapshots` and `rotated_quorums_per_cycle` (via `block_container` lookup), and `quorum_statuses` height sets. Orphaned hashes are dropped conservatively. The container is trimmed last so hash lookups remain valid during cleanup. Idempotent under repeated invocation.
* feat(dash-spv): add `MasternodesManager::rewind_to_height`
Truncates the shared masternode engine, prunes the manager's height-tracked sync state, refreshes `last_synced_block_hash` from the surviving engine tip, clears straggler/dedup state, transitions to `Syncing`, and dispatches a fresh QRInfo for the new tip.
* feat(dash-spv): trigger masternode rewind on `ChainReorg`
`MasternodesManager::handle_sync_event` now intercepts `SyncEvent::ChainReorg` before any other arm and delegates to `rewind_to_height`, mirroring the cascade pattern already in `FilterHeadersManager` and `BlocksManager`.
* feat(dash-spv): hard-block chainlock validation across reorg
`ChainLockManager` listens for `SyncEvent::ChainReorg` and flips `masternode_ready` back to `false` while dropping any cached `pending_validation`. Subsequent chainlocks are cached until the next `MasternodeStateUpdated` flips readiness back on, which mirrors the pre-startup path.
* feat(dash-spv): drop pending instantlocks across reorg
`InstantSendManager` listens for `SyncEvent::ChainReorg` and clears `pending_instantlocks`. Pre-reorg IS locks were validated against a quorum view that no longer matches the new chain, so retrying them would either succeed under the wrong cycle or fail spuriously. Future IS locks re-received from the network are picked up via the existing `MasternodeStateUpdated` retry path. Also drops the dead `MasternodesManager::is_synced_for_height` query and its test.
* test(dash-spv): cover masternode-list rewind across DIP-3 reorg
Adds a regtest integration test that orchestrates a reorg via the existing controller node (`invalidateblock` + replacement chain), observes the SPV's `ChainReorg` event, verifies the engine truncates above the fork before refilling, and confirms a post-rewind `MasternodeStateUpdated` lands above the fork. Also adds `MasternodeTestContext::mine_reorg` and a `wait_for_chain_reorg_event` helper.
* test(dash-spv): cover qrinfo refresh across DIP-24 cycle reorg
Adds an integration test for the full DIP-24 rotation-cycle reorg path: mine a DKG cycle, capture its commitment hash, invalidate enough to roll back the commitment block, mine a replacement DKG cycle, and verify the engine drops the orphaned cycle from `rotated_quorums_per_cycle` and refills it under the replacement key.
The replacement DKG is flaky on the existing regtest masternode harness so the test is gated behind `#[ignore]` with a tracking note tied to #142. The body still compiles to catch refactors that break the helper surface.
* chore(dash-spv): apply rustfmt to reorg integration test scaffolding
* chore: pr cleanup — move inline imports to `mod tests` top, remove what-comments, import `Range`
* test(dash-spv): wait for full filter sync before triggering reorg in `test_masternode_list_rewind_across_dip3_reorg`
On macOS ARM, the filter pipeline had not yet downloaded filters for the
3 most recent blocks when `mine_reorg` was called. After the reorg those
block hashes were orphaned, so dashd disconnected every reconnect attempt
that included a `GetCFilters` for an orphaned stop hash, preventing the
`GetHeaders2` fork-detection request from ever getting a response and
causing the 60 s `wait_for_chain_reorg_event` timeout.
Ubuntu/Windows passed because the slower VMs gave the filter pipeline
enough time to finish before the reorg was triggered.
Fix: add `wait_for_full_sync` (waits for `SyncProgress::is_synced()`)
and call it after `wait_for_mn_state_event_above` and before
`mine_reorg`, ensuring no orphan-hash filter requests remain in flight.
* fix: merge engine lock acquisitions and add error recovery in `rewind_to_height`
Addresses manki-review comments on PR #165
#165 (comment)
#165 (comment)
#165 (comment)
* refactor: encapsulate `ChainLockManager` reorg state via `reset_for_reorg()`
Addresses manki-review comment on PR #165
#165 (comment)
* test: add optional fork-height filter to `wait_for_chain_reorg_event`
Addresses manki-review comment on PR #165
#165 (comment)
* fix(`ChainLockManager`): preserve `pending_validation` across peer disconnect
Adds `reset_for_disconnect()` that clears only `masternode_ready`, and
switches `on_disconnect` to use it instead of `reset_for_reorg()`.
A chainlock cached before masternode sync completes remains valid on the
same chain and must survive disconnect so `on_masternode_ready` can
re-validate it on the next reconnect.
Addresses manki-review comment on PR #165
#165 (comment)
* test(`MasternodesManager`): cover `rewind_to_height` send failure path
Adds `test_rewind_to_height_sets_wait_for_events_on_send_failure` to
assert that when `send_qrinfo_for_tip` errors (dropped channel), the
manager transitions to `WaitForEvents` and clears `qrinfo_in_flight`,
leaving it recoverable by the next `BlockHeaderSyncComplete` event.
Addresses manki-review comment on PR #165
#165 (comment)
* feat(`dash-spv`): extend buffered fork branches across multi-message reorg announcements
dashd announces reorgs as N separate `headers2` messages (one header each, because each `generatetoaddress` block produces a `headers` push). The first batch enters the fork buffer via `ingest_fork`. Subsequent batches whose `prev_blockhash` is the previous fork tip re-entered `ingest_fork`, which then failed chain-continuity against the *active-chain* ancestor and was swallowed with a "deferred to Phase 3" debug log. The branch was stuck at one header, never outweighed the active extension, and `ChainReorg` never fired.
Add `ForkBuffer::extend_branch` that looks up the existing branch by `(peer, prev_tip_hash)`, validates each continuation header (continuity off the buffered branch tip, PoW, MTP, DGW v3 against a rolling window of `history + buffered + new`), appends them, and re-keys the branch under the new tip. The caller receives a `BranchUpdate` with the new tip/height/work so it can refresh `fork_tip_index` and re-evaluate promotion. The per-header validation loop is factored out of `ingest` into `validate_chain` so both paths share identical rules.
`BlockHeadersManager` routes `fork_tip_index` hits through a new `extend_fork` helper that loads the same active-chain history as `ingest_fork`, calls `extend_branch`, refreshes the index, and runs `take_winning_candidate` so a branch that just outweighs the active chain promotes immediately.
Verified locally with `test_masternode_list_rewind_across_dip3_reorg` (previously timed out, now completes in ~2s).
Refs [#142](#142), [#165](#165).
* fix(`ChainLockManager`): document phase-ordering invariant in `reset_for_disconnect`, add behavioral tests
Addresses manki-review comments on PR #165:
- #165 (comment)
- #165 (comment)
- #165 (comment)
* test(`ChainLockManager`,`MasternodesManager`): strengthen round-4 manki assertions
- Assert return value and `progress.invalid()` in `test_pending_validation_survives_disconnect_and_consumed_on_reconnect` to cover the re-broadcast contract of `on_masternode_ready`
- Add `engine.masternode_lists.is_empty()` boundary assertion in `test_rewind_to_height_sets_wait_for_events_on_send_failure` to verify surviving entries, not just absent ones
- Add `test_reorg_prevents_stale_pending_validation_from_being_revalidated` as a companion test for the phase-ordering invariant documented on `reset_for_disconnect`
* test(`ChainLockManager`,`MasternodesManager`): address round-5 manki assertions
Assert `masternode_ready == true` after `on_masternode_ready()` in the reorg
test (the flag flips unconditionally, even when `pending_validation` is None).
Seed a surviving entry at height 30 in `test_rewind_to_height_sets_wait_for_events_on_send_failure`
and assert `contains_key(&30)` after truncation at fork_height=50, closing the
absence-only gap manki flagged.
The disconnect-test `progress.invalid()` assertion (thread 3) was already applied
in round 4.
Addresses manki-review comments on PR #165
#165 (comment)
#165 (comment)
#165 (comment)1 parent 374d7e9 commit b879fe1
12 files changed
Lines changed: 1287 additions & 68 deletions
File tree
- dash-spv
- src
- sync
- block_headers
- chainlock
- instantsend
- masternodes
- test_utils
- tests/dashd_masternode
- dash/src/sml/masternode_list_engine
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
44 | 44 | | |
45 | 45 | | |
46 | 46 | | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
47 | 55 | | |
48 | 56 | | |
49 | 57 | | |
| |||
89 | 97 | | |
90 | 98 | | |
91 | 99 | | |
92 | | - | |
93 | | - | |
94 | | - | |
95 | 100 | | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
96 | 138 | | |
97 | | - | |
| 139 | + | |
98 | 140 | | |
99 | 141 | | |
100 | 142 | | |
| |||
103 | 145 | | |
104 | 146 | | |
105 | 147 | | |
106 | | - | |
107 | 148 | | |
108 | 149 | | |
109 | 150 | | |
| |||
112 | 153 | | |
113 | 154 | | |
114 | 155 | | |
115 | | - | |
116 | | - | |
117 | | - | |
| 156 | + | |
118 | 157 | | |
119 | 158 | | |
120 | 159 | | |
121 | 160 | | |
122 | 161 | | |
123 | 162 | | |
124 | 163 | | |
125 | | - | |
126 | 164 | | |
127 | | - | |
| 165 | + | |
128 | 166 | | |
129 | 167 | | |
130 | 168 | | |
| |||
138 | 176 | | |
139 | 177 | | |
140 | 178 | | |
| 179 | + | |
| 180 | + | |
141 | 181 | | |
142 | | - | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
143 | 248 | | |
144 | | - | |
145 | 249 | | |
146 | | - | |
| 250 | + | |
147 | 251 | | |
148 | | - | |
149 | | - | |
150 | | - | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
151 | 255 | | |
152 | 256 | | |
153 | 257 | | |
154 | 258 | | |
155 | | - | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
156 | 264 | | |
157 | 265 | | |
158 | 266 | | |
| |||
588 | 696 | | |
589 | 697 | | |
590 | 698 | | |
| 699 | + | |
| 700 | + | |
| 701 | + | |
| 702 | + | |
| 703 | + | |
| 704 | + | |
| 705 | + | |
| 706 | + | |
| 707 | + | |
| 708 | + | |
| 709 | + | |
| 710 | + | |
| 711 | + | |
| 712 | + | |
| 713 | + | |
| 714 | + | |
| 715 | + | |
| 716 | + | |
| 717 | + | |
| 718 | + | |
| 719 | + | |
| 720 | + | |
| 721 | + | |
| 722 | + | |
| 723 | + | |
| 724 | + | |
| 725 | + | |
| 726 | + | |
| 727 | + | |
| 728 | + | |
| 729 | + | |
| 730 | + | |
| 731 | + | |
| 732 | + | |
| 733 | + | |
| 734 | + | |
| 735 | + | |
| 736 | + | |
| 737 | + | |
| 738 | + | |
| 739 | + | |
| 740 | + | |
| 741 | + | |
| 742 | + | |
| 743 | + | |
| 744 | + | |
| 745 | + | |
| 746 | + | |
| 747 | + | |
| 748 | + | |
| 749 | + | |
| 750 | + | |
| 751 | + | |
| 752 | + | |
| 753 | + | |
| 754 | + | |
| 755 | + | |
| 756 | + | |
| 757 | + | |
| 758 | + | |
| 759 | + | |
| 760 | + | |
| 761 | + | |
| 762 | + | |
| 763 | + | |
| 764 | + | |
| 765 | + | |
| 766 | + | |
| 767 | + | |
| 768 | + | |
| 769 | + | |
| 770 | + | |
| 771 | + | |
| 772 | + | |
| 773 | + | |
| 774 | + | |
| 775 | + | |
| 776 | + | |
| 777 | + | |
| 778 | + | |
| 779 | + | |
| 780 | + | |
| 781 | + | |
| 782 | + | |
| 783 | + | |
| 784 | + | |
| 785 | + | |
| 786 | + | |
| 787 | + | |
| 788 | + | |
| 789 | + | |
| 790 | + | |
| 791 | + | |
| 792 | + | |
| 793 | + | |
| 794 | + | |
| 795 | + | |
| 796 | + | |
| 797 | + | |
| 798 | + | |
| 799 | + | |
| 800 | + | |
| 801 | + | |
| 802 | + | |
| 803 | + | |
| 804 | + | |
| 805 | + | |
| 806 | + | |
| 807 | + | |
| 808 | + | |
| 809 | + | |
| 810 | + | |
| 811 | + | |
| 812 | + | |
| 813 | + | |
| 814 | + | |
| 815 | + | |
| 816 | + | |
| 817 | + | |
| 818 | + | |
| 819 | + | |
| 820 | + | |
| 821 | + | |
| 822 | + | |
| 823 | + | |
| 824 | + | |
| 825 | + | |
| 826 | + | |
| 827 | + | |
591 | 828 | | |
0 commit comments