//! Persistent state for the IPT manager //! //! Records of our IPTs. //! Does *not* include private keys - those are in the `KeyMgr`. use super::*; use crate::time_store; /// Handle for a suitable persistent storage manager pub(crate) type IptStorageHandle = tor_persist::state_dir::StorageHandle; //---------- On disk data structures, done with serde ---------- /// Record of intro point establisher state, as stored on disk #[derive(Serialize, Deserialize, Debug)] pub(crate) struct StateRecord { /// Relays ipt_relays: Vec, /// Reference time stored: time_store::Reference, } /// Record of a selected intro point relay, as stored on disk #[derive(Serialize, Deserialize, Debug)] struct RelayRecord { /// Which relay? relay: RelayIds, /// When do we plan to retire it? planned_retirement: time_store::FutureTimestamp, /// The IPTs, including the current one and any still-wanted old ones ipts: Vec, } /// Record of a single intro point, as stored on disk #[derive(Serialize, Deserialize, Debug)] struct IptRecord { /// Used to find the cryptographic keys, amongst other things lid: IptLocalId, /// Is this IPT current, or are we just keeping it because of old descriptors #[serde(default, skip_serializing_if = "<&bool as std::ops::Not>::not")] is_current: bool, } //---------- Storing ---------- /// Store the IPTs in the persistent state pub(super) fn store>( imm: &Immutable, state: &mut State, ) -> Result<(), IptStoreError> { let tstoring = time_store::Storing::start(&imm.runtime); // Convert the IPT relays (to the on-disk format) let ipt_relays = state .irelays .iter() .map(|irelay| { // Convert one IPT relay, with its IPTs, to the on-disk format let relay = irelay.relay.clone(); let planned_retirement = tstoring.store_future(irelay.planned_retirement); let ipts = irelay .ipts .iter() .map(|ipt| { // Convert one IPT - at least, the parts we store here IptRecord { lid: ipt.lid, is_current: ipt.is_current.is_some(), } }) .collect_vec(); RelayRecord { relay, planned_retirement, ipts, } }) .collect_vec(); let on_disk = StateRecord { ipt_relays, stored: tstoring.store_ref(), }; state.storage.store(&on_disk)?; Ok(()) } //---------- Loading ---------- /// Load the IPTs from the persistent state /// /// `publish_set` should already have been loaded from its persistent state. pub(super) fn load>( imm: &Immutable, storage: &IptStorageHandle, config: &watch::Receiver>, mockable: &mut M, publish_set: &PublishIptSet, ) -> Result, StartupError> { let on_disk = storage.load().map_err(StartupError::LoadState)?; let Some(on_disk) = on_disk else { return Ok(vec![]); }; // Throughout, we use exhaustive struct patterns on the data we got from disk, // so we avoid missing any of the data. let StateRecord { ipt_relays, stored } = on_disk; let tloading = time_store::Loading::start(&imm.runtime, stored); // Load the IPT relays (from the on-disk to the in-memory format) let mut ipt_relays: Vec<_> = ipt_relays .into_iter() .map(|rrelay| { // Load one IPT relay let RelayRecord { relay, planned_retirement, ipts, } = rrelay; let planned_retirement = tloading.load_future(planned_retirement); // Load the IPTs at this relay, restarting their establishers, etc. let ipts = ipts .into_iter() .map(|ipt| ipt.load_restart(imm, config, mockable, &relay)) .try_collect()?; Ok::<_, StartupError>(IptRelay { relay, planned_retirement, ipts, }) }) .try_collect()?; IptManager::::import_new_expiry_times(&mut ipt_relays, publish_set); Ok(ipt_relays) } impl IptRecord { /// Recreate (load) one IPT fn load_restart>( self, imm: &Immutable, new_configs: &watch::Receiver>, mockable: &mut M, relay: &RelayIds, ) -> Result { let IptRecord { lid, is_current } = self; let ipt = Ipt::start_establisher( imm, new_configs, mockable, relay, lid, is_current.then_some(IsCurrent), Some(IptExpectExistingKeys), // last_descriptor_expiry_including_slop // is restored by the `import_new_expiry_times` call in `load` PromiseLastDescriptorExpiryNoneIsGood {}, ) .map_err(|e| match e { CreateIptError::Fatal(e) => e.into(), // During startup we're trying to *read* the keystore; // if it goes wrong, we bail rather than continuing the startup attempt. CreateIptError::Keystore(cause) => StartupError::Keystore { action: "load IPT key(s)", cause, }, CreateIptError::OpenReplayLog { file, error } => { StartupError::StateDirectoryInaccessibleIo { source: error, action: "opening", path: file, } } })?; // We don't record whether this IPT was published, so we should assume it was. mockable.start_accepting(&*ipt.establisher); Ok(ipt) } }