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