From 3ede44a7d8e9f0d542efbc70fc447292f0158418 Mon Sep 17 00:00:00 2001 From: MasterPtato Date: Thu, 30 Oct 2025 12:38:09 -0700 Subject: [PATCH] fix: add version rollback check --- Cargo.lock | 6 ++- Cargo.toml | 1 + engine/packages/config/src/config/mod.rs | 4 ++ engine/packages/engine/Cargo.toml | 8 ++-- engine/packages/engine/src/commands/start.rs | 50 +++++++++++++++++++- engine/packages/engine/src/keys.rs | 47 ++++++++++++++++++ engine/packages/engine/src/lib.rs | 1 + 7 files changed, 110 insertions(+), 7 deletions(-) create mode 100644 engine/packages/engine/src/keys.rs diff --git a/Cargo.lock b/Cargo.lock index 52293b92a2..7b12025d5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4243,6 +4243,7 @@ dependencies = [ "gasoline", "hex", "include_dir", + "indoc", "lz4_flex", "namespace", "pegboard", @@ -4271,6 +4272,7 @@ dependencies = [ "rivet-workflow-worker", "rstest", "rustyline", + "semver", "serde", "serde_json", "serde_yaml", @@ -5057,9 +5059,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "sentry" diff --git a/Cargo.toml b/Cargo.toml index 406c4e7199..021aea5859 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,7 @@ rstest = "0.26.1" rustls-pemfile = "2.2.0" rustyline = "15.0.0" scc = "3.3.2" +semver = "1.0.27" serde_bare = "0.5.0" serde_html_form = "0.2.7" serde_yaml = "0.9.34" diff --git a/engine/packages/config/src/config/mod.rs b/engine/packages/config/src/config/mod.rs index f6a873fcdd..73ee6aa315 100644 --- a/engine/packages/config/src/config/mod.rs +++ b/engine/packages/config/src/config/mod.rs @@ -95,6 +95,9 @@ pub struct Root { #[serde(default)] pub telemetry: Telemetry, + + #[serde(default)] + pub allow_version_rollback: bool, } impl Default for Root { @@ -112,6 +115,7 @@ impl Default for Root { clickhouse: None, vector_http: None, telemetry: Default::default(), + allow_version_rollback: false, } } } diff --git a/engine/packages/engine/Cargo.toml b/engine/packages/engine/Cargo.toml index 7ec983317d..efda95749f 100644 --- a/engine/packages/engine/Cargo.toml +++ b/engine/packages/engine/Cargo.toml @@ -18,26 +18,28 @@ futures-util.workspace = true gas.workspace = true hex.workspace = true include_dir.workspace = true +indoc.workspace = true lz4_flex.workspace = true -pegboard-serverless.workspace = true pegboard-runner.workspace = true +pegboard-serverless.workspace = true reqwest.workspace = true rivet-api-peer.workspace = true rivet-bootstrap.workspace = true -rivet-cache.workspace = true rivet-cache-purge.workspace = true +rivet-cache.workspace = true rivet-config.workspace = true rivet-guard.workspace = true -rivet-tracing-reconfigure.workspace = true rivet-logs.workspace = true rivet-pools.workspace = true rivet-runtime.workspace = true rivet-service-manager.workspace = true rivet-telemetry.workspace = true rivet-term.workspace = true +rivet-tracing-reconfigure.workspace = true rivet-util.workspace = true rivet-workflow-worker.workspace = true rustyline.workspace = true +semver.workspace = true serde_json.workspace = true serde_yaml.workspace = true serde.workspace = true diff --git a/engine/packages/engine/src/commands/start.rs b/engine/packages/engine/src/commands/start.rs index 5316072da8..dd6bda16f9 100644 --- a/engine/packages/engine/src/commands/start.rs +++ b/engine/packages/engine/src/commands/start.rs @@ -1,8 +1,12 @@ use std::time::Duration; -use anyhow::*; +use anyhow::{Context, Result, anyhow}; use clap::Parser; +use indoc::formatdoc; use rivet_service_manager::{CronConfig, RunConfig}; +use universaldb::utils::IsolationLevel::*; + +use crate::keys; // 7 day logs retention const LOGS_RETENTION: Duration = Duration::from_secs(7 * 24 * 60 * 60); @@ -88,10 +92,52 @@ impl Opts { .collect::>() }; - // Start server let pools = rivet_pools::Pools::new(config.clone()).await?; + + verify_engine_version(&config, &pools).await?; + + // Start server rivet_service_manager::start(config, pools, services).await?; Ok(()) } } + +/// Verifies that no rollback has occurred (if allowing rollback is disabled). +async fn verify_engine_version( + config: &rivet_config::Config, + pools: &rivet_pools::Pools, +) -> Result<()> { + if config.allow_version_rollback { + return Ok(()); + } + + pools + .udb()? + .run(|tx| async move { + let current_version = semver::Version::parse(env!("CARGO_PKG_VERSION")) + .context("failed to parse cargo pkg version as semver")?; + + if let Some(existing_version) = + tx.read_opt(&keys::EngineVersionKey {}, Serializable).await? + { + if current_version < existing_version { + return Ok(Err(anyhow!("{}", formatdoc!( + " + Rivet Engine has been rolled back to a previous version: + - Last Used Version: {existing_version} + - Current Version: {current_version} + Cannot proceed without potential data corruption. + + (If you know what you're doing, this error can be disabled in the Rivet config via `allow_version_rollback: true`) + " + )))); + } + } + + tx.write(&keys::EngineVersionKey {}, current_version)?; + + Ok(Ok(())) + }) + .await? +} diff --git a/engine/packages/engine/src/keys.rs b/engine/packages/engine/src/keys.rs new file mode 100644 index 0000000000..cca8fe84f2 --- /dev/null +++ b/engine/packages/engine/src/keys.rs @@ -0,0 +1,47 @@ +use anyhow::Result; +use universaldb::prelude::*; + +#[derive(Debug)] +pub struct EngineVersionKey {} + +impl EngineVersionKey { + pub fn new() -> Self { + EngineVersionKey {} + } +} + +impl FormalKey for EngineVersionKey { + type Value = semver::Version; + + fn deserialize(&self, raw: &[u8]) -> Result { + semver::Version::parse(str::from_utf8(raw)?).map_err(Into::into) + } + + fn serialize(&self, value: Self::Value) -> Result> { + Ok(value.to_string().into_bytes()) + } +} + +impl TuplePack for EngineVersionKey { + fn pack( + &self, + w: &mut W, + tuple_depth: TupleDepth, + ) -> std::io::Result { + let t = (RIVET, VERSION); + t.pack(w, tuple_depth) + } +} + +impl<'de> TupleUnpack<'de> for EngineVersionKey { + fn unpack(input: &[u8], tuple_depth: TupleDepth) -> PackResult<(&[u8], Self)> { + let (input, (_, data)) = <(usize, usize)>::unpack(input, tuple_depth)?; + if data != VERSION { + return Err(PackError::Message("expected VERSION data".into())); + } + + let v = EngineVersionKey {}; + + Ok((input, v)) + } +} diff --git a/engine/packages/engine/src/lib.rs b/engine/packages/engine/src/lib.rs index 21423c2d70..86bd286bee 100644 --- a/engine/packages/engine/src/lib.rs +++ b/engine/packages/engine/src/lib.rs @@ -4,6 +4,7 @@ use commands::*; use rivet_service_manager::RunConfig; pub mod commands; +pub mod keys; pub mod run_config; pub mod util;