-
Notifications
You must be signed in to change notification settings - Fork 13
fix(dash-spv): persist masternode list #990
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,7 +14,9 @@ use tokio::sync::RwLock; | |
| use super::pipeline::MnListDiffPipeline; | ||
| use crate::error::{SyncError, SyncResult}; | ||
| use crate::network::RequestSender; | ||
| use crate::storage::BlockHeaderStorage; | ||
| use crate::storage::{ | ||
| BlockHeaderStorage, MasternodeStateStorage, PersistentMasternodeStateStorage, | ||
| }; | ||
| use crate::sync::{MasternodesProgress, SyncEvent, SyncManager, SyncState}; | ||
| use dashcore::network::message_qrinfo::QRInfo; | ||
| use dashcore::BlockHash; | ||
|
|
@@ -299,6 +301,8 @@ pub struct MasternodesManager<H: BlockHeaderStorage> { | |
| network: dashcore::Network, | ||
| /// Sync state tracking. | ||
| pub(super) sync_state: MasternodeSyncState, | ||
| /// `None` leaves the list in memory only. | ||
| pub(super) state_storage: Option<Arc<RwLock<PersistentMasternodeStateStorage>>>, | ||
| } | ||
|
|
||
| impl<H: BlockHeaderStorage> MasternodesManager<H> { | ||
|
|
@@ -307,6 +311,7 @@ impl<H: BlockHeaderStorage> MasternodesManager<H> { | |
| header_storage: Arc<RwLock<H>>, | ||
| engine: Arc<RwLock<MasternodeListEngine>>, | ||
| network: dashcore::Network, | ||
| state_storage: Option<Arc<RwLock<PersistentMasternodeStateStorage>>>, | ||
| ) -> Self { | ||
| // Recover sync state from the engine's stored masternode lists so that a | ||
| // restart can resume from where the previous run left off. | ||
|
|
@@ -337,6 +342,21 @@ impl<H: BlockHeaderStorage> MasternodesManager<H> { | |
| engine, | ||
| network, | ||
| sync_state, | ||
| state_storage, | ||
| } | ||
| } | ||
|
|
||
| /// Best effort: an unwritten list costs a rebuild next start, a failed sync | ||
| /// costs the list now. | ||
| pub(super) async fn persist_engine(&self, height: u32) { | ||
| let Some(storage) = &self.state_storage else { | ||
| return; | ||
| }; | ||
| let engine = self.engine.read().await; | ||
| if let Err(e) = storage.write().await.store_engine(&engine, height).await { | ||
| tracing::warn!("Could not persist masternode state at {height}: {e}"); | ||
| } else { | ||
| tracing::debug!("Persisted masternode state at height {height}"); | ||
| } | ||
| } | ||
|
Comment on lines
+349
to
361
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win Add an in-module test for All manager tests in this file pass As per coding guidelines, write unit tests for new functionality and comprehensive in-module tests under Also applies to: 724-724, 761-761, 993-993 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
|
|
||
|
|
@@ -559,6 +579,7 @@ impl<H: BlockHeaderStorage> MasternodesManager<H> { | |
|
|
||
| self.sync_state.last_synced_block_hash = Some(latest_block_hash); | ||
| self.progress.update_current_height(height); | ||
| self.persist_engine(height).await; | ||
| tracing::debug!("Incremental MnListDiff complete at height {}", height); | ||
| Ok(vec![SyncEvent::MasternodeStateUpdated { | ||
| height, | ||
|
|
@@ -662,6 +683,10 @@ impl<H: BlockHeaderStorage> MasternodesManager<H> { | |
|
|
||
| drop(engine); | ||
|
|
||
| if !events.is_empty() { | ||
| self.persist_engine(self.progress.current_height()).await; | ||
| } | ||
|
|
||
| if is_initial_sync { | ||
| self.set_state(SyncState::Synced); | ||
| tracing::info!("Masternode sync complete at height {}", self.progress.current_height()); | ||
|
|
@@ -696,7 +721,7 @@ mod tests { | |
| async fn create_test_manager_for(network: dashcore::Network) -> TestMasternodesManager { | ||
| let storage = DiskStorageManager::with_temp_dir().await.unwrap(); | ||
| let engine = Arc::new(RwLock::new(MasternodeListEngine::default_for_network(network))); | ||
| MasternodesManager::new(storage.block_headers(), engine, network).await | ||
| MasternodesManager::new(storage.block_headers(), engine, network, None).await | ||
| } | ||
|
|
||
| async fn create_test_manager() -> TestMasternodesManager { | ||
|
|
@@ -733,6 +758,7 @@ mod tests { | |
| block_headers, | ||
| Arc::new(RwLock::new(engine)), | ||
| dashcore::Network::Regtest, | ||
| None, | ||
| ) | ||
| .await; | ||
| manager.set_state(SyncState::Synced); | ||
|
|
@@ -964,6 +990,7 @@ mod tests { | |
| storage.block_headers(), | ||
| Arc::new(RwLock::new(engine)), | ||
| dashcore::Network::Testnet, | ||
| None, | ||
| ) | ||
| .await; | ||
|
|
||
|
|
||
There was a problem hiding this comment.
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
🔎 Supported by static analysis
🏁 Script executed:
Repository: dashpay/rust-dashcore
Length of output: 50378
🏁 Script executed:
Repository: dashpay/rust-dashcore
Length of output: 35171
🏁 Script executed:
Repository: dashpay/rust-dashcore
Length of output: 50378
🏁 Script executed:
Repository: dashpay/rust-dashcore
Length of output: 10712
🏁 Script executed:
Repository: dashpay/rust-dashcore
Length of output: 45018
Reject persisted engines for the wrong network.
PersistentMasternodeStateStoragestores all networks inmasternodestate/masternodestate.json. For an existing file,load_engine(network)returns the serialized engine without comparingengine.networkwithnetwork.MasternodesManager::newthen storesconfig.networkseparately while using the loaded engine. A shared path can therefore run a Testnet engine with a Mainnet manager and use incorrect network parameters.Scope storage by network, or reject a loaded engine when
engine.network != config.networkand usedefault_for_network(config.network).🤖 Prompt for AI Agents