lexe-common
Advanced tools
| //! Timeout constants, organized into nested modules named after the API trait, | ||
| //! service, or struct whose timeouts it holds. | ||
| //! | ||
| //! `const_assert!`s are used to enforce our timeout hierarchies; e.g. each | ||
| //! timeout along a request chain must be shorter than the one enclosing it. | ||
| //! | ||
| //! Principles for tuning: | ||
| //! | ||
| //! - When a timeout increase is needed: prefer to raise the timeout for a | ||
| //! specific server or client method that needs it, rather than the global | ||
| //! default. | ||
| //! - Be careful when decreasing timeouts baked into enclave programs - if it | ||
| //! cannot be overridden via CLI, there is no way to backtrack if the decrease | ||
| //! was too aggressive. Wire through a CLI arg before decreasing the timeout. | ||
| use std::time::Duration; | ||
| use lexe_std::const_assert; | ||
| /// Defaults for Lexe API servers. | ||
| pub mod server { | ||
| use super::*; | ||
| /// The grace period passed to `axum_server::Handle::graceful_shutdown` | ||
| /// during which new connections are refused and we wait for existing | ||
| /// connections to terminate before initiating a hard shutdown. | ||
| pub const SHUTDOWN_GRACE_PERIOD: Duration = Duration::from_secs(3); | ||
| /// The maximum time we'll wait for a server to complete shutdown. | ||
| pub const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5); | ||
| /// The default maximum time a server can spend handling a request. | ||
| pub const DEFAULT_HANDLER_TIMEOUT: Duration = Duration::from_secs(25); | ||
| const_assert!(SHUTDOWN_TIMEOUT.as_secs() > SHUTDOWN_GRACE_PERIOD.as_secs()); | ||
| } | ||
| /// Defaults for Lexe `RestClient`s. | ||
| pub mod client { | ||
| use super::*; | ||
| /// The default request timeout for Lexe `RestClient`s. | ||
| pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30); | ||
| const_assert!( | ||
| DEFAULT_TIMEOUT.as_secs() > server::DEFAULT_HANDLER_TIMEOUT.as_secs() | ||
| ); | ||
| } | ||
| /// Timeouts for `UserNode`s. | ||
| pub mod usernode { | ||
| use super::*; | ||
| /// Default sync timeout for user nodes (BDK/LDK sync). | ||
| // | ||
| // (2026-07-24): 30s -> 15s. The const was created at 30s despite 15s being | ||
| // the documented default; prod doesn't override it. | ||
| pub const DEFAULT_SYNC_TIMEOUT: Duration = Duration::from_secs(15); | ||
| /// The amount of time user node tasks have to finish after a graceful | ||
| /// shutdown signal is received before the task is forced to exit. | ||
| pub const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(25); | ||
| // The meganode's `run_user` handler (bounded by the default | ||
| // `server::DEFAULT_HANDLER_TIMEOUT`) blocks on the usernode's boot sync | ||
| // before responding. | ||
| const_assert!( | ||
| DEFAULT_SYNC_TIMEOUT.as_secs() | ||
| < server::DEFAULT_HANDLER_TIMEOUT.as_secs() | ||
| ); | ||
| const_assert!( | ||
| SHUTDOWN_TIMEOUT.as_secs() > server::SHUTDOWN_TIMEOUT.as_secs() | ||
| ); | ||
| } | ||
| /// Timeouts for in-enclave `UserRunner`. | ||
| pub mod userrunner { | ||
| use super::*; | ||
| /// The amount of time the user runner has to finish after a graceful | ||
| /// shutdown signal is received before the program is forced to exit. | ||
| pub const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(27); | ||
| const_assert!( | ||
| SHUTDOWN_TIMEOUT.as_secs() > usernode::SHUTDOWN_TIMEOUT.as_secs() | ||
| ); | ||
| } | ||
| /// Timeouts for `UserNodeProvisionApi` (User -> Node). | ||
| pub mod user_node_provision_api { | ||
| use super::*; | ||
| /// `NodeClient`'s request timeout for provisioning. Generous because the | ||
| /// provision handler does the Google Drive OAuth exchange and GVFS setup. | ||
| pub const CLIENT_TIMEOUT: Duration = Duration::from_secs(30); | ||
| const_assert!( | ||
| CLIENT_TIMEOUT.as_secs() > server::DEFAULT_HANDLER_TIMEOUT.as_secs() | ||
| ); | ||
| } | ||
| /// Timeouts for `UserNodeRunApi` (User -> Node). | ||
| pub mod user_node_run_api { | ||
| use super::*; | ||
| /// Handling timeout for the node's user-facing server. Generous because | ||
| /// `[preflight_]pay_invoice` may compute `max_flow`, which takes ~30s at | ||
| /// 10 iterations and ~50s at 17 iterations. | ||
| /// See `compute_max_flow_to_recipient` for more details. | ||
| pub const SERVER_HANDLER_TIMEOUT: Duration = Duration::from_secs(60); | ||
| /// Timeouts for endpoints which may compute `max_flow`. | ||
| pub mod max_flow { | ||
| use super::*; | ||
| /// Client timeout for endpoints which may compute `max_flow`. | ||
| pub const CLIENT_TIMEOUT: Duration = Duration::from_secs(62); | ||
| const_assert!( | ||
| CLIENT_TIMEOUT.as_secs() | ||
| > user_node_run_api::SERVER_HANDLER_TIMEOUT.as_secs() | ||
| ); | ||
| } | ||
| } |
| { | ||
| "git": { | ||
| "sha1": "0ac597ead266a8e8782d0b883b6648ec042e8fd8" | ||
| "sha1": "40779429fba3e515594ded5c256151a5611fabf6" | ||
| }, | ||
| "path_in_vcs": "public/lexe-common" | ||
| } |
+15
-15
@@ -505,5 +505,5 @@ # This file is automatically @generated by Cargo. | ||
| name = "lexe-byte-array" | ||
| version = "0.1.18" | ||
| version = "0.1.19" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "5e23344f7a31dca16335bde556bb7ac159e8104fbccc9c6c567464d4b7d96702" | ||
| checksum = "8befecbf125294fa28825cff970aa592a1d3f3e44126d45fa49ac1a057de94e6" | ||
| dependencies = [ | ||
@@ -515,3 +515,3 @@ "lexe-hex", | ||
| name = "lexe-common" | ||
| version = "0.1.18" | ||
| version = "0.1.19" | ||
| dependencies = [ | ||
@@ -564,5 +564,5 @@ "anyhow", | ||
| name = "lexe-crypto" | ||
| version = "0.1.18" | ||
| version = "0.1.19" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "05e6974fab400e95d1793b3bdff27caf075c32c388c7ddccfbd5db83bf852e83" | ||
| checksum = "b65c4cf9e3b997fe1d9efc0b7877505a23edb04201dac31dcf97558e1da7fad9" | ||
| dependencies = [ | ||
@@ -585,5 +585,5 @@ "bcs", | ||
| name = "lexe-enclave" | ||
| version = "0.1.18" | ||
| version = "0.1.19" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "584a0dec4f92eb083e0a62cc4c833e6046bfcecb1a10e6179df05aa130516cbc" | ||
| checksum = "1aaf2020098e4c7e0541f2c966a3e843204210b3659e88deb7f561ad42bcc9b2" | ||
| dependencies = [ | ||
@@ -605,11 +605,11 @@ "cfg-if", | ||
| name = "lexe-hex" | ||
| version = "0.1.18" | ||
| version = "0.1.19" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "1ee60f4bb9eacd99d119d1831248778d0f4ee7650ef476507af2c9c11a683f46" | ||
| checksum = "acdf65b70dc7524aef1fa3829d208bfc01434dab1fb06835bdbe2af3f3ae43bd" | ||
| [[package]] | ||
| name = "lexe-serde" | ||
| version = "0.1.18" | ||
| version = "0.1.19" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "3d4197b1db7c0a2df29931bce84e1d17fa0009a6a861ae9295ea1b4fa5c31aae" | ||
| checksum = "e3ad016a88e3e7a650d3a6885f975cb8ef2f54b0fe00ef5d73dd06551aad928c" | ||
| dependencies = [ | ||
@@ -623,5 +623,5 @@ "base64", | ||
| name = "lexe-sha256" | ||
| version = "0.1.18" | ||
| version = "0.1.19" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "4db865c7a10553dc27b108e4bab8a0dc9d5e9b2edd1c98d08159e505296044ae" | ||
| checksum = "0fb2bd039f02fa7400ed8e63a28386054c45b60c245746147174c74b404f7fc4" | ||
| dependencies = [ | ||
@@ -636,5 +636,5 @@ "lexe-byte-array", | ||
| name = "lexe-std" | ||
| version = "0.1.18" | ||
| version = "0.1.19" | ||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| checksum = "b5179ab46c980244c8a537322ee4a34fb1a3466d84e10ee88b352ef137bab4d1" | ||
| checksum = "b2dca82b723d3ca96ce17f62ea75459e940b362d102a36e70db74d1fc1a8553c" | ||
| dependencies = [ | ||
@@ -641,0 +641,0 @@ "ref-cast", |
+10
-10
@@ -16,3 +16,3 @@ # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO | ||
| name = "lexe-common" | ||
| version = "0.1.18" | ||
| version = "0.1.19" | ||
| authors = [ | ||
@@ -105,21 +105,21 @@ "Max Fang <max@lexe.app>", | ||
| [dependencies.lexe-byte-array] | ||
| version = "0.1.18" | ||
| version = "0.1.19" | ||
| [dependencies.lexe-crypto] | ||
| version = "0.1.18" | ||
| version = "0.1.19" | ||
| [dependencies.lexe-enclave] | ||
| version = "0.1.18" | ||
| version = "0.1.19" | ||
| [dependencies.lexe-hex] | ||
| version = "0.1.18" | ||
| version = "0.1.19" | ||
| [dependencies.lexe-serde] | ||
| version = "0.1.18" | ||
| version = "0.1.19" | ||
| [dependencies.lexe-sha256] | ||
| version = "0.1.18" | ||
| version = "0.1.19" | ||
| [dependencies.lexe-std] | ||
| version = "0.1.18" | ||
| version = "0.1.19" | ||
| features = ["ref-cast"] | ||
@@ -213,7 +213,7 @@ | ||
| [dev-dependencies.lexe-crypto] | ||
| version = "0.1.18" | ||
| version = "0.1.19" | ||
| features = ["test-utils"] | ||
| [dev-dependencies.lexe-enclave] | ||
| version = "0.1.18" | ||
| version = "0.1.19" | ||
| features = ["test-utils"] | ||
@@ -220,0 +220,0 @@ |
+5
-24
@@ -1,8 +0,11 @@ | ||
| use std::{include_bytes, time::Duration}; | ||
| use std::include_bytes; | ||
| use lexe_enclave::enclave::{Measurement, MrShort}; | ||
| use lexe_std::{const_assert, const_concat_str}; | ||
| use lexe_std::const_concat_str; | ||
| use crate::{ppm, ppm::Ppm}; | ||
| /// Timeout constants and hierarchies. | ||
| pub mod timeout; | ||
| // --- General --- // | ||
@@ -38,24 +41,2 @@ | ||
| /// The amount of time user node tasks have to finish after a graceful shutdown | ||
| /// signal is received before the task is forced to exit. | ||
| pub const USER_NODE_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(25); | ||
| /// The amount of time user the user runner has to finish after a graceful | ||
| /// shutdown signal is received before the program is forced to exit. | ||
| pub const USER_RUNNER_SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(27); | ||
| const_assert!( | ||
| USER_NODE_SHUTDOWN_TIMEOUT.as_secs() | ||
| < USER_RUNNER_SHUTDOWN_TIMEOUT.as_secs() | ||
| ); | ||
| /// Default sync timeout for user nodes (BDK/LDK sync). | ||
| pub const DEFAULT_USERNODE_SYNC_TIMEOUT: Duration = Duration::from_secs(30); | ||
| /// Computing `max_flow` takes ~30s at 10 iterations and ~50s at 17 iterations. | ||
| /// Set `LayerConfig::handling_timeout` and `reqwest::RequestBuilder::timeout` | ||
| /// to this value to ensure that callers can get a response. | ||
| /// See `compute_max_flow_to_recipient` for more details. | ||
| pub const MAX_FLOW_TIMEOUT: Duration = Duration::from_secs(60); | ||
| /// This is both: | ||
@@ -62,0 +43,0 @@ /// |
+122
-0
@@ -183,2 +183,62 @@ use std::{ | ||
| /// Formats this timestamp into a human-readable string relative to `now`, | ||
| /// e.g. "just now", "5 minutes ago", "in 3 hours", | ||
| /// "in 2 years and 5 weeks". | ||
| /// | ||
| /// Picks the largest whole unit that fits; weeks and years also carry the | ||
| /// remainder in the next unit down. Sub-second differences in either | ||
| /// direction read as "just now". | ||
| pub fn to_relative_string(self, now: Self) -> String { | ||
| const MINUTE: u64 = 60; | ||
| const HOUR: u64 = 60 * MINUTE; | ||
| const DAY: u64 = 24 * HOUR; | ||
| const WEEK: u64 = 7 * DAY; | ||
| const YEAR: u64 = 365 * DAY; | ||
| /// e.g. "1 week", "3 weeks" | ||
| fn fmt_1_unit(count: u64, unit: &str) -> String { | ||
| let plural = if count == 1 { "" } else { "s" }; | ||
| format!("{count} {unit}{plural}") | ||
| } | ||
| /// A count plus its remainder in the next unit down, e.g. | ||
| /// "3 weeks and 2 days". The remainder is omitted when zero. | ||
| fn fmt_2_units( | ||
| count: u64, | ||
| unit: &str, | ||
| rem: u64, | ||
| rem_unit: &str, | ||
| ) -> String { | ||
| let major = fmt_1_unit(count, unit); | ||
| match rem { | ||
| 0 => major, | ||
| _ => format!("{major} and {}", fmt_1_unit(rem, rem_unit)), | ||
| } | ||
| } | ||
| let elapsed = now.checked_duration_since(self); | ||
| let secs = match elapsed { | ||
| Some(elapsed) => elapsed.as_secs(), | ||
| None => self.saturating_duration_since(now).as_secs(), | ||
| }; | ||
| // Largest whole unit that fits. Weeks and years carry a remainder, | ||
| // since "51 weeks" and "3 years" alone lose too much precision. | ||
| let magnitude = match secs { | ||
| 0 => return "just now".to_owned(), | ||
| s if s < MINUTE => fmt_1_unit(s, "second"), | ||
| s if s < HOUR => fmt_1_unit(s / MINUTE, "minute"), | ||
| s if s < DAY => fmt_1_unit(s / HOUR, "hour"), | ||
| s if s < WEEK => fmt_1_unit(s / DAY, "day"), | ||
| s if s < YEAR => | ||
| fmt_2_units(s / WEEK, "week", (s % WEEK) / DAY, "day"), | ||
| s => fmt_2_units(s / YEAR, "year", (s % YEAR) / WEEK, "week"), | ||
| }; | ||
| match elapsed { | ||
| Some(_) => format!("{magnitude} ago"), | ||
| None => format!("in {magnitude}"), | ||
| } | ||
| } | ||
| /// Floors the timestamp to the most recent second. | ||
@@ -412,2 +472,64 @@ #[cfg(test)] | ||
| } | ||
| /// Sanity check the relative formatting at every granularity. | ||
| #[test] | ||
| fn relative_string_sanity() { | ||
| const MINUTE: u64 = 60; | ||
| const HOUR: u64 = 60 * MINUTE; | ||
| const DAY: u64 = 24 * HOUR; | ||
| const WEEK: u64 = 7 * DAY; | ||
| const YEAR: u64 = 365 * DAY; | ||
| // (secs away from `now`, past rendering, future rendering) | ||
| let cases = [ | ||
| (0, "just now", "just now"), | ||
| (1, "1 second ago", "in 1 second"), | ||
| (59, "59 seconds ago", "in 59 seconds"), | ||
| (MINUTE, "1 minute ago", "in 1 minute"), | ||
| (5 * MINUTE + 30, "5 minutes ago", "in 5 minutes"), | ||
| (HOUR, "1 hour ago", "in 1 hour"), | ||
| (3 * HOUR + 59 * MINUTE, "3 hours ago", "in 3 hours"), | ||
| (DAY + 12 * HOUR, "1 day ago", "in 1 day"), | ||
| (6 * DAY, "6 days ago", "in 6 days"), | ||
| (WEEK, "1 week ago", "in 1 week"), | ||
| (WEEK + DAY, "1 week and 1 day ago", "in 1 week and 1 day"), | ||
| ( | ||
| 3 * WEEK + 2 * DAY, | ||
| "3 weeks and 2 days ago", | ||
| "in 3 weeks and 2 days", | ||
| ), | ||
| ( | ||
| 51 * WEEK + 6 * DAY, | ||
| "51 weeks and 6 days ago", | ||
| "in 51 weeks and 6 days", | ||
| ), | ||
| (YEAR, "1 year ago", "in 1 year"), | ||
| ( | ||
| YEAR + 5 * WEEK, | ||
| "1 year and 5 weeks ago", | ||
| "in 1 year and 5 weeks", | ||
| ), | ||
| // Remainders below the next unit down are dropped, not rounded. | ||
| ( | ||
| 2 * YEAR + WEEK + 3 * DAY, | ||
| "2 years and 1 week ago", | ||
| "in 2 years and 1 week", | ||
| ), | ||
| ( | ||
| 143 * WEEK, | ||
| "2 years and 38 weeks ago", | ||
| "in 2 years and 38 weeks", | ||
| ), | ||
| ]; | ||
| let now = TimestampMs::from_secs(100 * YEAR).unwrap(); | ||
| for (secs, past, future) in cases { | ||
| let secs = Duration::from_secs(secs); | ||
| let past_ts = now.saturating_sub(secs); | ||
| let future_ts = now.saturating_add(secs); | ||
| assert_eq!(past_ts.to_relative_string(now), past); | ||
| assert_eq!(future_ts.to_relative_string(now), future); | ||
| } | ||
| } | ||
| } |
Sorry, the diff of this file is not supported yet