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