penumbra_sdk_app/app_version/
component.rs1use std::fmt::Write as _;
2
3use anyhow::{anyhow, Context};
4use cnidarium::{StateDelta, Storage};
5use penumbra_sdk_proto::{StateReadProto, StateWriteProto};
6
7use super::APP_VERSION;
8
9fn version_to_software_version(version: u64) -> &'static str {
10 match version {
11 1 => "v0.70.x",
12 2 => "v0.73.x",
13 3 => "v0.74.x",
14 4 => "v0.75.x",
15 5 => "v0.76.x",
16 6 => "v0.77.x",
17 7 => "v0.79.x",
18 8 => "v0.80.x",
19 9 => "v0.81.x",
20 10 => "v1.4.x",
21 11 => "v2.0.x",
22 12 => "v2.1.x",
23 _ => "unknown",
24 }
25}
26
27#[derive(Debug, Clone, Copy)]
28enum CheckContext {
29 Running,
30 Migration,
31}
32
33#[tracing::instrument]
36fn check_version(ctx: CheckContext, expected: u64, found: Option<u64>) -> anyhow::Result<()> {
37 tracing::debug!("running version check");
38 let found = found.unwrap_or(expected);
39 if found == expected {
40 return Ok(());
41 }
42
43 match ctx {
44 CheckContext::Running => {
45 let expected_name = version_to_software_version(expected);
46 let found_name = version_to_software_version(found);
47 let mut error = String::new();
48 error.push_str("app version mismatch:\n");
49 write!(
50 &mut error,
51 " expected {} (penumbra {})\n",
52 expected, expected_name
53 )?;
54 write!(&mut error, " found {} (penumbra {})\n", found, found_name)?;
55 write!(&mut error, "Are you using the right node directory?\n")?;
56 if found == expected - 1 {
58 write!(&mut error, "Does a migration need to happen?\n")?;
59 write!(
60 &mut error,
61 "If so, then run `pd migrate` with version {}",
62 expected_name
63 )?;
64 } else {
65 write!(
66 &mut error,
67 "make sure you're running penumbra {}",
68 expected_name
69 )?;
70 }
71 Err(anyhow!(error))
72 }
73 CheckContext::Migration => {
74 let expected_name = version_to_software_version(expected);
75 let found_name = version_to_software_version(found);
76 let mut error = String::new();
77 if found == APP_VERSION {
78 write!(
79 &mut error,
80 "state already migrated to version {}",
81 APP_VERSION
82 )?;
83 anyhow::bail!(error);
84 }
85 error.push_str("app version mismatch:\n");
86 write!(
87 &mut error,
88 " expected {} (penumbra {})\n",
89 expected, expected_name
90 )?;
91 write!(&mut error, " found {} (penumbra {})\n", found, found_name)?;
92 write!(
93 &mut error,
94 "this migration should be run with penumbra {} instead",
95 version_to_software_version(expected + 1)
96 )?;
97 Err(anyhow!(error))
98 }
99 }
100}
101
102async fn read_app_version_safeguard<S: StateReadProto>(s: &S) -> anyhow::Result<Option<u64>> {
104 let out = s
105 .nonverifiable_get_proto(crate::app::state_key::app_version::safeguard().as_bytes())
106 .await
107 .context("while reading app version safeguard")?;
108 Ok(out)
109}
110
111fn write_app_version_safeguard<S: StateWriteProto>(s: &mut S, x: u64) {
113 s.nonverifiable_put_proto(
114 crate::app::state_key::app_version::safeguard()
115 .as_bytes()
116 .to_vec(),
117 x,
118 )
119}
120
121pub async fn check_and_update_app_version(s: Storage) -> anyhow::Result<()> {
133 if s.latest_version() == u64::MAX {
136 return Ok(());
137 }
138 let mut delta = StateDelta::new(s.latest_snapshot());
139
140 match read_app_version_safeguard(&delta).await? {
143 None => {
144 tracing::debug!(?APP_VERSION, "version safeguard not found, initializing");
145 write_app_version_safeguard(&mut delta, APP_VERSION);
146 s.commit_in_place(delta).await?;
147 }
148 Some(found) => check_version(CheckContext::Running, APP_VERSION, Some(found))?,
149 }
150 Ok(())
151}
152
153pub async fn migrate_app_version<S: StateWriteProto>(s: &mut S, to: u64) -> anyhow::Result<()> {
160 anyhow::ensure!(to > 1, "you can't migrate to the first penumbra version!");
161 let found = read_app_version_safeguard(s).await?;
162 check_version(CheckContext::Migration, to - 1, found)?;
163 write_app_version_safeguard(s, to);
164 Ok(())
165}
166
167#[cfg(test)]
168mod test {
169 use super::*;
170 #[test]
171 fn ensure_app_version_is_current_in_checks() -> anyhow::Result<()> {
175 let result = version_to_software_version(APP_VERSION);
176 assert!(
177 result != "unknown",
178 "APP_VERSION lacks a corresponding software version"
179 );
180 Ok(())
181 }
182}