diff --git a/crates/subspace-farmer/src/cluster/controller/farms.rs b/crates/subspace-farmer/src/cluster/controller/farms.rs index 4c725e3d49..98a2f491fe 100644 --- a/crates/subspace-farmer/src/cluster/controller/farms.rs +++ b/crates/subspace-farmer/src/cluster/controller/farms.rs @@ -5,19 +5,21 @@ //! automatically handles dynamic farm addition and removal, etc. use crate::cluster::controller::ClusterControllerFarmerIdentifyBroadcast; -use crate::cluster::farmer::{ClusterFarm, ClusterFarmerIdentifyFarmBroadcast}; +use crate::cluster::farmer::{ + ClusterFarm, ClusterFarmerFarmDetails, ClusterFarmerFarmDetailsRequest, + ClusterFarmerIdentifyBroadcast, +}; use crate::cluster::nats_client::NatsClient; use crate::farm::plotted_pieces::PlottedPieces; -use crate::farm::{Farm, FarmId, SectorPlottingDetails, SectorUpdate}; +use crate::farm::{Farm, FarmId, FarmerId, SectorPlottingDetails, SectorUpdate}; use anyhow::anyhow; use async_lock::RwLock as AsyncRwLock; use futures::channel::oneshot; use futures::future::FusedFuture; use futures::stream::FuturesUnordered; -use futures::{select, FutureExt, StreamExt}; +use futures::{select, FutureExt, Stream, StreamExt}; use parking_lot::Mutex; -use std::collections::hash_map::Entry; -use std::collections::{HashMap, VecDeque}; +use std::collections::{HashMap, HashSet, VecDeque}; use std::future::{ready, Future}; use std::mem; use std::pin::{pin, Pin}; @@ -27,7 +29,7 @@ use subspace_core_primitives::hashes::Blake3Hash; use subspace_core_primitives::sectors::SectorIndex; use tokio::task; use tokio::time::MissedTickBehavior; -use tracing::{error, info, trace, warn}; +use tracing::{debug, error, info, warn}; type AddRemoveFuture<'a> = Pin, ClusterFarm)>> + 'a>>; @@ -35,102 +37,253 @@ type AddRemoveFuture<'a> = /// Number of farms in a cluster is currently limited to 2^16 pub type FarmIndex = u16; +#[derive(Debug)] +struct KnownFarmer { + farmer_id: FarmerId, + fingerprint: Blake3Hash, + last_identification: Instant, + known_farms: HashMap, +} + #[derive(Debug)] struct KnownFarm { farm_id: FarmId, fingerprint: Blake3Hash, - last_identification: Instant, expired_sender: oneshot::Sender<()>, } -enum KnownFarmInsertResult { - Inserted { - farm_index: FarmIndex, - expired_receiver: oneshot::Receiver<()>, - }, +enum KnownFarmerInsertResult { + Inserted, FingerprintUpdated { - farm_index: FarmIndex, - expired_receiver: oneshot::Receiver<()>, + old_farms: HashMap, }, NotInserted, } +struct KnownFarmInsertResult { + farm_index: FarmIndex, + farm_id: FarmId, + total_sectors_count: u16, + expired_receiver: oneshot::Receiver<()>, + add: bool, + remove: bool, +} + #[derive(Debug)] -struct KnownFarms { +struct KnownFarmers { identification_broadcast_interval: Duration, - known_farms: HashMap, + known_farmers: Vec, } -impl KnownFarms { +impl KnownFarmers { fn new(identification_broadcast_interval: Duration) -> Self { Self { identification_broadcast_interval, - known_farms: HashMap::new(), + known_farmers: Vec::new(), } } - fn insert_or_update( + async fn insert_or_update_farmer( &mut self, - farm_id: FarmId, + farmer_id: FarmerId, fingerprint: Blake3Hash, - ) -> KnownFarmInsertResult { - if let Some(existing_result) = - self.known_farms - .iter_mut() - .find_map(|(&farm_index, known_farm)| { - if known_farm.farm_id == farm_id { - if known_farm.fingerprint == fingerprint { - known_farm.last_identification = Instant::now(); - Some(KnownFarmInsertResult::NotInserted) + farms_stream: Fut, + ) -> Vec + where + Fut: Future>, + S: Stream + Unpin, + { + let result = self + .known_farmers + .iter_mut() + .find_map(|known_farmer| { + let check_farmer_id = known_farmer.farmer_id == farmer_id; + let check_fingerprint = known_farmer.fingerprint == fingerprint; + match (check_farmer_id, check_fingerprint) { + (true, true) => { + debug!(%farmer_id,"Updating last identification for farmer"); + known_farmer.last_identification = Instant::now(); + Some(KnownFarmerInsertResult::NotInserted) + } + (true, false) => { + let old_farms = known_farmer + .known_farms + .drain() + .map(|(farm_index, know_farm)| { + (know_farm.farm_id, (farm_index, know_farm)) + }) + .collect(); + known_farmer.fingerprint = fingerprint; + known_farmer.last_identification = Instant::now(); + Some(KnownFarmerInsertResult::FingerprintUpdated { old_farms }) + } + (false, _) => None, + } + }) + .unwrap_or(KnownFarmerInsertResult::Inserted); + + if let KnownFarmerInsertResult::NotInserted = result { + return vec![]; + } + + let Some(farms_stream) = farms_stream.await else { + return vec![]; + }; + let farms = farms_stream.collect::>().await; + let farm_indices = self.pick_farmer_index(farms.len()); + + match result { + KnownFarmerInsertResult::Inserted => { + let mut known_farmer = KnownFarmer { + farmer_id, + fingerprint, + last_identification: Instant::now(), + known_farms: HashMap::new(), + }; + + let res = farm_indices + .into_iter() + .zip(farms) + .map(|(farm_index, farm_details)| { + let ClusterFarmerFarmDetails { + farm_id, + total_sectors_count, + fingerprint, + } = farm_details; + let (expired_sender, expired_receiver) = oneshot::channel(); + known_farmer.known_farms.insert( + farm_index, + KnownFarm { + farm_id, + fingerprint, + expired_sender, + }, + ); + info!(%farmer_id, %farm_id, %total_sectors_count, "Discovered new farm"); + KnownFarmInsertResult { + farm_index, + farm_id, + total_sectors_count, + expired_receiver, + add: true, + remove: false, + } + }) + .collect::>(); + self.known_farmers.push(known_farmer); + res + } + KnownFarmerInsertResult::FingerprintUpdated { mut old_farms } => { + farm_indices + .into_iter() + .zip(farms) + .filter_map(|(farm_index, farm_details)| { + let ClusterFarmerFarmDetails { + farm_id, + total_sectors_count, + fingerprint, + } = farm_details; + if let Some((farm_index, mut known_farm)) = old_farms.remove(&farm_id) { + if known_farm.farm_id == farm_id { + let known_farmer = self + .get_known_farmer(farmer_id) + .expect("Farmer should be available"); + if known_farm.fingerprint == fingerprint { + // Do nothing if farm is already known + known_farmer.known_farms.insert(farm_index, known_farm); + None + } else { + // Update fingerprint + let (expired_sender, expired_receiver) = oneshot::channel(); + known_farm.expired_sender = expired_sender; + known_farmer.known_farms.insert(farm_index, known_farm); + Some(KnownFarmInsertResult { + farm_index, + farm_id, + total_sectors_count, + expired_receiver, + add: true, + remove: true, + }) + } + } else { + None + } } else { + // Add new farm let (expired_sender, expired_receiver) = oneshot::channel(); - known_farm.fingerprint = fingerprint; - known_farm.expired_sender = expired_sender; - - Some(KnownFarmInsertResult::FingerprintUpdated { + self.get_known_farmer(farmer_id) + .expect("Farmer should be available") + .known_farms + .insert( + farm_index, + KnownFarm { + farm_id, + fingerprint, + expired_sender, + }, + ); + Some(KnownFarmInsertResult { farm_index, + farm_id, + total_sectors_count, expired_receiver, + add: true, + remove: false, }) } - } else { - None - } - }) - { - return existing_result; + }) + .collect::>() + } + KnownFarmerInsertResult::NotInserted => { + unreachable!("KnownFarmerInsertResult::NotInserted should be handled above") + } } + } - for farm_index in FarmIndex::MIN..=FarmIndex::MAX { - if let Entry::Vacant(entry) = self.known_farms.entry(farm_index) { - let (expired_sender, expired_receiver) = oneshot::channel(); + fn get_known_farmer(&mut self, farmer_id: FarmerId) -> Option<&mut KnownFarmer> { + self.known_farmers + .iter_mut() + .find(|known_farmer| known_farmer.farmer_id == farmer_id) + } - entry.insert(KnownFarm { - farm_id, - fingerprint, - last_identification: Instant::now(), - expired_sender, - }); + fn pick_farmer_index(&self, len: usize) -> Vec { + let used_indices = self + .known_farmers + .iter() + .flat_map(|known_farmer| known_farmer.known_farms.keys()) + .collect::>(); - return KnownFarmInsertResult::Inserted { - farm_index, - expired_receiver, - }; + let mut available_indices = Vec::with_capacity(len); + + for farm_index in FarmIndex::MIN..=FarmIndex::MAX { + if !used_indices.contains(&farm_index) { + if available_indices.len() < len { + available_indices.push(farm_index); + } else { + return available_indices; + } } } - warn!(%farm_id, max_supported_farm_index = %FarmIndex::MAX, "Too many farms, ignoring"); - KnownFarmInsertResult::NotInserted + warn!(max_supported_farm_index = %FarmIndex::MAX, "Too many farms"); + available_indices } fn remove_expired(&mut self) -> impl Iterator + '_ { - self.known_farms.extract_if(|_farm_index, known_farm| { - known_farm.last_identification.elapsed() > self.identification_broadcast_interval * 2 - }) + self.known_farmers + .extract_if(.., |known_farmer| { + known_farmer.last_identification.elapsed() + > self.identification_broadcast_interval * 2 + }) + .flat_map(|known_farmer| known_farmer.known_farms) } fn remove(&mut self, farm_index: FarmIndex) { - self.known_farms.remove(&farm_index); + self.known_farmers.iter_mut().for_each(|known_farmer| { + known_farmer.known_farms.remove(&farm_index); + }); } } @@ -141,7 +294,7 @@ pub async fn maintain_farms( plotted_pieces: &Arc>>, identification_broadcast_interval: Duration, ) -> anyhow::Result<()> { - let mut known_farms = KnownFarms::new(identification_broadcast_interval); + let mut known_farms = KnownFarmers::new(identification_broadcast_interval); // Futures that need to be processed sequentially in order to add/remove farms, if farm was // added, future will resolve with `Some`, `None` if removed @@ -152,11 +305,9 @@ pub async fn maintain_farms( let mut farms = FuturesUnordered::new(); let farmer_identify_subscription = pin!(nats_client - .subscribe_to_broadcasts::(None, None) + .subscribe_to_broadcasts::(None, None) .await - .map_err(|error| anyhow!( - "Failed to subscribe to farmer identify farm broadcast: {error}" - ))?); + .map_err(|error| anyhow!("Failed to subscribe to farmer identify broadcast: {error}"))?); // Request farmer to identify themselves if let Err(error) = nats_client @@ -213,14 +364,31 @@ pub async fn maintain_farms( let Some(identify_message) = maybe_identify_message else { return Err(anyhow!("Farmer identify stream ended")); }; + let farmer_id = identify_message.farmer_id; - process_farm_identify_message( + process_farmer_identify_message( identify_message, nats_client, &mut known_farms, &mut farms_to_add_remove, plotted_pieces, - ); + async { + nats_client + .stream_request( + &ClusterFarmerFarmDetailsRequest, + Some(&farmer_id.to_string()), + ) + .await + .inspect_err(|error| { + warn!( + %error, + %farmer_id, + "Failed to request farmer farm details" + ) + }) + .ok() + }, + ).await; } _ = farm_pruning_interval.tick().fuse() => { for (farm_index, removed_farm) in known_farms.remove_expired() { @@ -278,114 +446,107 @@ pub async fn maintain_farms( } } -fn process_farm_identify_message<'a>( - identify_message: ClusterFarmerIdentifyFarmBroadcast, +async fn process_farmer_identify_message<'a, Fut, S>( + identify_message: ClusterFarmerIdentifyBroadcast, nats_client: &'a NatsClient, - known_farms: &mut KnownFarms, + known_farms: &mut KnownFarmers, farms_to_add_remove: &mut VecDeque>, plotted_pieces: &'a Arc>>, -) { - let ClusterFarmerIdentifyFarmBroadcast { - farm_id, - total_sectors_count, + farms_stream: Fut, +) where + Fut: Future>, + S: Stream + Unpin, +{ + let ClusterFarmerIdentifyBroadcast { + farmer_id, fingerprint, } = identify_message; - let (farm_index, expired_receiver, add, remove) = - match known_farms.insert_or_update(farm_id, fingerprint) { - KnownFarmInsertResult::Inserted { - farm_index, - expired_receiver, - } => { - info!( - %farm_index, - %farm_id, - "Discovered new farm, initializing" - ); - - (farm_index, expired_receiver, true, false) - } - KnownFarmInsertResult::FingerprintUpdated { - farm_index, - expired_receiver, - } => { - info!( - %farm_index, - %farm_id, - "Farm fingerprint updated, re-initializing" - ); - - (farm_index, expired_receiver, true, true) - } - KnownFarmInsertResult::NotInserted => { - trace!( - %farm_id, - "Received identification for already known farm" - ); - // Nothing to do here - return; - } - }; - - if remove { - farms_to_add_remove.push_back(Box::pin(async move { - let plotted_pieces = Arc::clone(plotted_pieces); - - let delete_farm_fut = task::spawn_blocking(move || { - plotted_pieces.write_blocking().delete_farm(farm_index); - }); - if let Err(error) = delete_farm_fut.await { - error!( - %farm_index, - %farm_id, - %error, - "Failed to delete farm that was replaced" - ); - } - - None - })); - } - if add { - farms_to_add_remove.push_back(Box::pin(async move { - match initialize_farm( - farm_index, + for KnownFarmInsertResult { + farm_index, + farm_id, + total_sectors_count, + expired_receiver, + add, + remove, + } in known_farms + .insert_or_update_farmer(farmer_id, fingerprint, farms_stream) + .await + { + if remove { + remove_farm( farm_id, - total_sectors_count, - Arc::clone(plotted_pieces), - nats_client, + farm_index, + farms_to_add_remove, + plotted_pieces.clone(), ) - .await - { - Ok(farm) => { - if remove { - info!( - %farm_index, - %farm_id, - "Farm re-initialized successfully" - ); - } else { - info!( - %farm_index, - %farm_id, - "Farm initialized successfully" + .await; + } + + if add { + farms_to_add_remove.push_back(Box::pin(async move { + match initialize_farm( + farm_index, + farm_id, + total_sectors_count, + plotted_pieces.clone(), + nats_client, + ) + .await + { + Ok(farm) => { + if remove { + info!( + %farm_index, + %farm_id, + "Farm re-initialized successfully" + ); + } else { + info!( + %farm_index, + %farm_id, + "Farm initialized successfully" + ); + } + + Some((farm_index, expired_receiver, farm)) + } + Err(error) => { + warn!( + %error, + "Failed to initialize farm {farm_id}" ); + None } - - Some((farm_index, expired_receiver, farm)) } - Err(error) => { - warn!( - %error, - "Failed to initialize farm {farm_id}" - ); - None - } - } - })); + })); + } } } +async fn remove_farm( + farm_id: FarmId, + farm_index: FarmIndex, + farms_to_add_remove: &mut VecDeque>, + plotted_pieces: Arc>>, +) { + farms_to_add_remove.push_back(Box::pin(async move { + let delete_farm_fut = task::spawn_blocking(move || { + plotted_pieces.write_blocking().delete_farm(farm_index); + }); + if let Err(error) = delete_farm_fut.await { + error!( + %farm_index, + %farm_id, + %error, + "Failed to delete farm that was replaced", + ); + } + + None + })); +} + async fn initialize_farm( farm_index: FarmIndex, farm_id: FarmId, diff --git a/crates/subspace-farmer/src/cluster/farmer.rs b/crates/subspace-farmer/src/cluster/farmer.rs index d6afd67b74..d3c732a864 100644 --- a/crates/subspace-farmer/src/cluster/farmer.rs +++ b/crates/subspace-farmer/src/cluster/farmer.rs @@ -12,7 +12,7 @@ use crate::cluster::nats_client::{ GenericBroadcast, GenericRequest, GenericStreamRequest, NatsClient, }; use crate::farm::{ - Farm, FarmError, FarmId, FarmingNotification, HandlerFn, HandlerId, PieceReader, + Farm, FarmError, FarmId, FarmerId, FarmingNotification, HandlerFn, HandlerId, PieceReader, PlottedSectors, SectorUpdate, }; use crate::utils::AsyncJoinOnDrop; @@ -40,9 +40,31 @@ const MIN_FARMER_IDENTIFICATION_INTERVAL: Duration = Duration::from_secs(1); type Handler = Bag, A>; -/// Broadcast with identification details by farmers +/// Broadcast with farmer id for identification #[derive(Debug, Clone, Encode, Decode)] -pub struct ClusterFarmerIdentifyFarmBroadcast { +pub struct ClusterFarmerIdentifyBroadcast { + /// Farmer ID + pub farmer_id: FarmerId, + /// Farmer fingerprint changes when something about internal farm changes (like allocated space) + pub fingerprint: Blake3Hash, +} + +impl GenericBroadcast for ClusterFarmerIdentifyBroadcast { + const SUBJECT: &'static str = "subspace.farmer.*.farmer-identify"; +} + +/// Request farm details from farmer +#[derive(Debug, Clone, Encode, Decode)] +pub struct ClusterFarmerFarmDetailsRequest; + +impl GenericStreamRequest for ClusterFarmerFarmDetailsRequest { + const SUBJECT: &'static str = "subspace.farmer.*.farm.details"; + type Response = ClusterFarmerFarmDetails; +} + +/// Farm details +#[derive(Debug, Clone, Encode, Decode)] +pub struct ClusterFarmerFarmDetails { /// Farm ID pub farm_id: FarmId, /// Total number of sectors in the farm @@ -51,10 +73,6 @@ pub struct ClusterFarmerIdentifyFarmBroadcast { pub fingerprint: Blake3Hash, } -impl GenericBroadcast for ClusterFarmerIdentifyFarmBroadcast { - const SUBJECT: &'static str = "subspace.farmer.*.identify"; -} - /// Broadcast with sector updates by farmers #[derive(Debug, Clone, Encode, Decode)] struct ClusterFarmerSectorUpdateBroadcast { @@ -344,6 +362,23 @@ struct FarmDetails { _background_tasks: Option>, } +impl FarmDetails { + fn derive_identification(&self) -> ClusterFarmerFarmDetails { + ClusterFarmerFarmDetails { + farm_id: self.farm_id, + total_sectors_count: self.total_sectors_count, + fingerprint: self.derive_fingerprint(), + } + } + + fn derive_fingerprint(&self) -> Blake3Hash { + blake3_hash_list(&[ + &self.farm_id.encode(), + &self.total_sectors_count.to_le_bytes(), + ]) + } +} + /// Create farmer service for specified farms that will be processing incoming requests and send /// periodic identify notifications. /// @@ -358,6 +393,9 @@ pub fn farmer_service( where F: Farm, { + let farmer_id = FarmerId::new(); + let farmer_id_string = farmer_id.to_string(); + // For each farm start forwarding notifications as broadcast messages and create farm details // that can be used to respond to incoming requests let farms_details = farms @@ -479,7 +517,20 @@ where async move { if primary_instance { select! { - result = identify_responder(&nats_client, &farms_details, identification_broadcast_interval).fuse() => { + result = identify_responder( + &nats_client, + farmer_id, + &farmer_id_string, + &farms_details, + identification_broadcast_interval + ).fuse() => { + result + }, + result = farms_details_responder( + &nats_client, + &farmer_id_string, + &farms_details + ).fuse() => { result }, result = plotted_sectors_responder(&nats_client, &farms_details).fuse() => { @@ -506,6 +557,8 @@ where /// broadcast in response, also send periodic notifications reminding that farm exists async fn identify_responder( nats_client: &NatsClient, + farmer_id: FarmerId, + farmer_id_string: &str, farms_details: &[FarmDetails], identification_broadcast_interval: Duration, ) -> anyhow::Result<()> { @@ -539,14 +592,14 @@ async fn identify_responder( } last_identification = Instant::now(); - send_identify_broadcast(nats_client, farms_details).await; + send_identify_broadcast(nats_client, farmer_id, farmer_id_string, farms_details).await; interval.reset(); } _ = interval.tick().fuse() => { last_identification = Instant::now(); trace!("Farmer self-identification"); - send_identify_broadcast(nats_client, farms_details).await; + send_identify_broadcast(nats_client, farmer_id, farmer_id_string, farms_details).await; } } } @@ -554,34 +607,66 @@ async fn identify_responder( Ok(()) } -async fn send_identify_broadcast(nats_client: &NatsClient, farms_details: &[FarmDetails]) { - farms_details +async fn send_identify_broadcast( + nats_client: &NatsClient, + farmer_id: FarmerId, + farmer_id_string: &str, + farms_details: &[FarmDetails], +) { + if farms_details.is_empty() { + warn!("No farm, skip sending farmer identify notification"); + return; + } + + if let Err(error) = nats_client + .broadcast( + &new_identify_message(farmer_id, farms_details), + farmer_id_string, + ) + .await + { + warn!(%farmer_id, %error, "Failed to send farmer identify notification"); + } +} + +fn new_identify_message( + farmer_id: FarmerId, + farms_details: &[FarmDetails], +) -> ClusterFarmerIdentifyBroadcast { + let farmer_id_bytes = farmer_id.encode(); + let farms_sectors_counts = farms_details .iter() - .map(|farm_details| async move { - if let Err(error) = nats_client - .broadcast( - &ClusterFarmerIdentifyFarmBroadcast { - farm_id: farm_details.farm_id, - total_sectors_count: farm_details.total_sectors_count, - fingerprint: blake3_hash_list(&[ - &farm_details.farm_id.encode(), - &farm_details.total_sectors_count.to_le_bytes(), - ]), - }, - &farm_details.farm_id_string, - ) - .await - { - warn!( - farm_id = %farm_details.farm_id, - %error, - "Failed to send farmer identify notification" - ); - } - }) - .collect::>() - .collect::>() - .await; + .map(|farm_details| farm_details.total_sectors_count.to_le_bytes()) + .collect::>(); + let mut farms_sectors_counts = farms_sectors_counts + .iter() + .map(AsRef::as_ref) + .collect::>(); + farms_sectors_counts.push(farmer_id_bytes.as_slice()); + let fingerprint = blake3_hash_list(farms_sectors_counts.as_slice()); + + ClusterFarmerIdentifyBroadcast { + farmer_id, + fingerprint, + } +} + +async fn farms_details_responder( + nats_client: &NatsClient, + farmer_id_string: &str, + farms_details: &[FarmDetails], +) -> anyhow::Result<()> { + nats_client + .stream_request_responder( + Some(farmer_id_string), + Some(farmer_id_string.to_string()), + |_request: ClusterFarmerFarmDetailsRequest| async { + Some(stream::iter( + farms_details.iter().map(FarmDetails::derive_identification), + )) + }, + ) + .await } async fn plotted_sectors_responder( diff --git a/crates/subspace-farmer/src/farm.rs b/crates/subspace-farmer/src/farm.rs index 32d506549e..28377d8c70 100644 --- a/crates/subspace-farmer/src/farm.rs +++ b/crates/subspace-farmer/src/farm.rs @@ -522,6 +522,62 @@ impl HandlerId for event_listener_primitives::HandlerId { } } +/// An identifier for a farmer, can be used for in logs, thread names, etc. +#[derive( + Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize, Display, From, +)] +#[serde(untagged)] +pub enum FarmerId { + /// Farmer ID + Ulid(Ulid), +} + +impl Encode for FarmerId { + #[inline] + fn size_hint(&self) -> usize { + 1_usize + + match self { + FarmerId::Ulid(ulid) => 0_usize.saturating_add(Encode::size_hint(&ulid.0)), + } + } + + #[inline] + fn encode_to(&self, output: &mut O) { + match self { + FarmerId::Ulid(ulid) => { + output.push_byte(0); + Encode::encode_to(&ulid.0, output); + } + } + } +} + +impl EncodeLike for FarmerId {} + +impl Decode for FarmerId { + #[inline] + fn decode(input: &mut I) -> Result { + match input + .read_byte() + .map_err(|e| e.chain("Could not decode `FarmerId`, failed to read variant byte"))? + { + 0 => u128::decode(input) + .map(|ulid| FarmerId::Ulid(Ulid(ulid))) + .map_err(|e| e.chain("Could not decode `FarmerId::Ulid.0`")), + _ => Err("Could not decode `FarmerId`, variant doesn't exist".into()), + } + } +} + +#[allow(clippy::new_without_default)] +impl FarmerId { + /// Creates new ID + #[inline] + pub fn new() -> Self { + Self::Ulid(Ulid::new()) + } +} + /// An identifier for a farm, can be used for in logs, thread names, etc. #[derive( Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize, Display, From,