1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 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 138 139 140 141 142 143 144 145 146 147 148 149 150 151 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 177 178 179 180 181 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
use super::*;
/// Quantum time used in the standard topology
pub const STANDARD_QUANTUM_TIME: Duration = Duration::from_secs(60 * 5);
/// Topology defines the structure of spacetime, in particular how space and
/// time are quantized.
///
/// Any calculation which requires converting from absolute coordinates to
/// quantized coordinates must refer to the topology. Therefore, this type is
/// ubiquitous! More functions than not take it as a parameter. This may seem
/// cumbersome, but there are a few reasons why this is helpful:
/// - We currently use a "standard" quantization for all networks, but we may
/// find it beneficial in the future to let each network specify its own
/// quantization levels, based on its own traffic and longevity needs.
/// - It is confusing to be working with three different coordinate systems in
/// this codebase, and the presence of a `&topo` param in a function is a
/// helpful reminder to be extra mindful about the unit conversions that are
/// happening
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Topology {
/// The quantization of space
pub space: Dimension,
/// The quantization of time
pub time: Dimension,
/// The origin of time, meaning the 0th quantum contains this Timestamp.
pub time_origin: Timestamp,
/// Ignore any data which lies after `Timestamp::now() - time_cutoff`.
/// This is so that historical quantized gossip does not overlap with
/// recent gossip.
pub time_cutoff: Duration,
}
impl Topology {
/// Unit dimensions with the given time origin
#[cfg(feature = "test_utils")]
pub fn unit(time_origin: Timestamp) -> Self {
Self {
space: Dimension::unit(),
time: Dimension::unit(),
time_origin,
time_cutoff: Duration::ZERO,
}
}
/// Unit dimensions with a zero time origin
#[cfg(feature = "test_utils")]
pub fn unit_zero() -> Self {
Self {
space: Dimension::unit(),
time: Dimension::unit(),
time_origin: Timestamp::from_micros(0),
time_cutoff: Duration::ZERO,
}
}
/// Standard dimensions with the given time origin
pub fn standard(time_origin: Timestamp, time_cutoff: Duration) -> Self {
Self {
space: Dimension::standard_space(),
time: Dimension::standard_time(),
time_origin,
time_cutoff,
}
}
/// Standard dimensions with the [`HOLOCHAIN_EPOCH`](Timestamp::HOLOCHAIN_EPOCH) as the time origin
pub fn standard_epoch(time_cutoff: Duration) -> Self {
Self::standard(Timestamp::HOLOCHAIN_EPOCH, time_cutoff)
}
/// Standard dimensions with the [`HOLOCHAIN_EPOCH`](Timestamp::HOLOCHAIN_EPOCH) as the time origin
pub fn standard_epoch_full() -> Self {
Self::standard(Timestamp::HOLOCHAIN_EPOCH, Duration::ZERO)
}
/// Standard dimensions with a zero time origin
#[cfg(feature = "test_utils")]
pub fn standard_zero() -> Self {
Self::standard(Timestamp::ZERO, Duration::ZERO)
}
/// Returns the space quantum which contains this location
pub fn space_quantum(&self, x: Loc) -> SpaceQuantum {
(x.as_u32() / self.space.quantum).into()
}
/// Returns the time quantum which contains this timestamp
pub fn time_quantum(&self, t: Timestamp) -> TimeQuantum {
let t = (t.as_micros() - self.time_origin.as_micros()).max(0);
((t / self.time.quantum as i64) as u32).into()
}
/// Returns the time quantum which contains this timestamp
pub fn time_quantum_duration(&self, d: std::time::Duration) -> TimeQuantum {
((d.as_micros() as i64 / self.time.quantum as i64) as u32).into()
}
/// The minimum power to use in "exponentional coordinates".
pub fn min_space_power(&self) -> u8 {
// If space.quantum_power is 0, then min has to be at least 1, because
// in that case we can talk about 2^32 quanta at power 0, which would
// overflow a `u32`.
//
// If space.quantum_power is greater than 0 (the standard is 12), then
// the min power can be 0.
1u8.saturating_sub(self.space.quantum_power)
}
/// The maximum power to use in "exponentional coordinates".
/// This is 17 for standard space topology. (32 - 12 - 3)
pub fn max_space_power(&self, strat: &ArqStrat) -> u8 {
32 - self.space.quantum_power - strat.max_chunks_log2()
}
}
/// Defines the quantization of a dimension of spacetime.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Dimension {
/// The smallest possible length in this dimension.
/// Determines the interval represented by the leaf of a tree.
pub quantum: u32,
/// The smallest power of 2 which is larger than the quantum.
/// Needed for various calculations.
pub quantum_power: u8,
/// The log2 size of this dimension, so that 2^bit_depth is the number of
/// possible values that can be represented.
pub(super) bit_depth: u8,
}
impl Dimension {
/// No quantization.
/// Used for testing, making it easier to construct values without thinking
/// of unit conversions.
#[cfg(feature = "test_utils")]
pub fn unit() -> Self {
Dimension {
quantum: 1,
quantum_power: 0,
bit_depth: 32,
}
}
/// The standard space quantum size is 2^12
pub const fn standard_space() -> Self {
let quantum_power = 12;
Dimension {
// if a network has 1 million peers,
// the average spacing between them is ~4,300
// so at a target coverage of 100,
// each arc will be ~430,000 in length
// which divided by 16 (max chunks) is ~2700, which is about 2^15.
// So, we'll go down to 2^12 just to be extra safe.
// This means we only need 20 bits to represent any location.
quantum: 2u32.pow(quantum_power as u32),
quantum_power,
bit_depth: 32 - quantum_power,
}
}
/// The standard time quantum size is 5 minutes (300 million microseconds)
pub const fn standard_time() -> Self {
let quantum = STANDARD_QUANTUM_TIME.as_micros() as u32;
Dimension {
// 5 minutes in microseconds = 1mil * 60 * 5 = 300,000,000
// log2 of this is 28.16, FYI
quantum,
quantum_power: 29,
// 12 quanta = 1 hour.
// If we set the max lifetime for a network to ~100 years, which
// is 12 * 24 * 365 * 100 = 10,512,000 time quanta,
// the log2 of which is 23.32,
// then we can store any time coordinate in that range using 24 bits.
//
// BTW, the log2 of 100 years in microseconds is 54.81
bit_depth: 24,
}
}
/// Calculate from a quantum size
pub fn time(quantum_dur: Duration) -> Self {
let quantum = quantum_dur.as_micros() as u32;
let quantum_power = ((quantum as f64).log2().ceil() as u32).try_into().unwrap();
let quanta_per_100_years = 60 * 60 / quantum_dur.as_secs() * 24 * 365 * 100;
let bit_depth = ((quanta_per_100_years as f64).log2().ceil() as u32)
.try_into()
.unwrap();
Dimension {
quantum,
quantum_power,
bit_depth,
}
}
}
/// Node-specific parameters for gossip.
/// While the [`Topology`] must be the same for every node in a network, each
/// node is free to choose its own GossipParams.
///
/// Choosing smaller values for these offsets can lead to less resource usage,
/// at the expense of reducing opportunities to gossip with other nodes.
/// This is also largely dependent on the characteristcs of the network,
/// since if almost all nodes are operating with the same current timestamp
/// and Arq power level, there will be very little need for reconciliation.
///
/// In networks where nodes are offline for long periods of time, or latency
/// is very high (e.g. sneakernet), it could be helpful to increase these values.
#[derive(Copy, Clone, Debug, derive_more::Constructor)]
pub struct GossipParams {
/// What +/- coordinate offset will you accept for timestamps?
/// e.g. if the time quantum is 5 min,
/// a time buffer of 2 will allow +/- 10 min discrepancies with gossip partners.
pub max_time_offset: TimeQuantum,
/// What difference in power will you accept for other agents' Arqs?
/// e.g. if the power I use in my arq is 14, and this offset is 2,
/// I won't talk to anyone whose arq is expressed with a power lower
/// than 12 or greater than 16
pub max_space_power_offset: u8,
}
impl GossipParams {
/// Zero-tolerance gossip params
pub fn zero() -> Self {
Self {
max_time_offset: 0.into(),
max_space_power_offset: 0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn custom_quantum_time() {
assert_eq!(
Dimension::standard_time(),
Dimension::time(STANDARD_QUANTUM_TIME)
);
}
}