1use {
2 crate::consensus::{
3 tower1_14_11::Tower1_14_11, tower1_7_14::SavedTower1_7_14, Result, Tower, TowerError,
4 TowerVersions,
5 },
6 serde::{Deserialize, Serialize},
7 solana_pubkey::Pubkey,
8 solana_signature::Signature,
9 solana_signer::Signer,
10 std::{
11 fs::{self, File},
12 io::{self, BufReader},
13 path::PathBuf,
14 },
15};
16
17#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
18#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
19pub enum SavedTowerVersions {
20 V1_17_14(SavedTower1_7_14),
21 Current(SavedTower),
22}
23
24impl SavedTowerVersions {
25 fn try_into_tower(&self, node_pubkey: &Pubkey) -> Result<Tower> {
26 assert_eq!(self.pubkey(), Pubkey::default());
28
29 let tv = match self {
30 SavedTowerVersions::V1_17_14(t) => {
31 if !t.signature.verify(node_pubkey.as_ref(), &t.data) {
32 return Err(TowerError::InvalidSignature);
33 }
34 bincode::deserialize(&t.data).map(TowerVersions::V1_7_14)
35 }
36 SavedTowerVersions::Current(t) => {
37 if !t.signature.verify(node_pubkey.as_ref(), &t.data) {
38 return Err(TowerError::InvalidSignature);
39 }
40 bincode::deserialize(&t.data).map(TowerVersions::V1_14_11)
41 }
42 };
43 tv.map_err(|e| e.into()).and_then(|tv: TowerVersions| {
44 let tower = tv.convert_to_current();
45 if tower.node_pubkey != *node_pubkey {
46 return Err(TowerError::WrongTower(format!(
47 "node_pubkey is {:?} but found tower for {:?}",
48 node_pubkey, tower.node_pubkey
49 )));
50 }
51 Ok(tower)
52 })
53 }
54
55 fn serialize_into(&self, file: &mut File) -> Result<()> {
56 bincode::serialize_into(file, self).map_err(|e| e.into())
57 }
58
59 fn pubkey(&self) -> Pubkey {
60 match self {
61 SavedTowerVersions::V1_17_14(t) => t.node_pubkey,
62 SavedTowerVersions::Current(t) => t.node_pubkey,
63 }
64 }
65}
66
67impl From<SavedTower> for SavedTowerVersions {
68 fn from(tower: SavedTower) -> SavedTowerVersions {
69 SavedTowerVersions::Current(tower)
70 }
71}
72
73impl From<SavedTower1_7_14> for SavedTowerVersions {
74 fn from(tower: SavedTower1_7_14) -> SavedTowerVersions {
75 SavedTowerVersions::V1_17_14(tower)
76 }
77}
78
79#[cfg_attr(
80 feature = "frozen-abi",
81 derive(AbiExample),
82 frozen_abi(digest = "8T1GVMzNNWcHRQwyzPhFj5nErdazaBe3ZGKQdY7T89Zo")
83)]
84#[derive(Default, Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
85pub struct SavedTower {
86 signature: Signature,
87 #[serde(with = "serde_bytes")]
88 data: Vec<u8>,
89 #[serde(skip)]
90 node_pubkey: Pubkey,
91}
92
93impl SavedTower {
94 pub fn new<T: Signer>(tower: &Tower, keypair: &T) -> Result<Self> {
95 let node_pubkey = keypair.pubkey();
96 if tower.node_pubkey != node_pubkey {
97 return Err(TowerError::WrongTower(format!(
98 "node_pubkey is {:?} but found tower for {:?}",
99 node_pubkey, tower.node_pubkey
100 )));
101 }
102
103 let tower: Tower1_14_11 = tower.clone().into();
105
106 let data = bincode::serialize(&tower)?;
107 let signature = keypair.sign_message(&data);
108 Ok(Self {
109 signature,
110 data,
111 node_pubkey,
112 })
113 }
114}
115
116pub trait TowerStorage: Sync + Send {
117 fn load(&self, node_pubkey: &Pubkey) -> Result<Tower>;
118 fn store(&self, saved_tower: &SavedTowerVersions) -> Result<()>;
119}
120
121#[derive(Debug, Default, Clone, PartialEq, Eq)]
122pub struct NullTowerStorage {}
123
124impl TowerStorage for NullTowerStorage {
125 fn load(&self, _node_pubkey: &Pubkey) -> Result<Tower> {
126 Err(TowerError::IoError(io::Error::other(
127 "NullTowerStorage::load() not available",
128 )))
129 }
130
131 fn store(&self, _saved_tower: &SavedTowerVersions) -> Result<()> {
132 Ok(())
133 }
134}
135
136#[derive(Debug, Default, Clone, PartialEq, Eq)]
137pub struct FileTowerStorage {
138 pub tower_path: PathBuf,
139}
140
141impl FileTowerStorage {
142 pub fn new(tower_path: PathBuf) -> Self {
143 Self { tower_path }
144 }
145
146 pub fn old_filename(&self, node_pubkey: &Pubkey) -> PathBuf {
148 self.tower_path
149 .join(format!("tower-{node_pubkey}"))
150 .with_extension("bin")
151 }
152
153 pub fn filename(&self, node_pubkey: &Pubkey) -> PathBuf {
154 self.tower_path
155 .join(format!("tower-1_9-{node_pubkey}"))
156 .with_extension("bin")
157 }
158
159 #[cfg(test)]
160 fn store_old(&self, saved_tower: &SavedTower1_7_14) -> Result<()> {
161 let pubkey = saved_tower.node_pubkey;
162 let filename = self.old_filename(&pubkey);
163 trace!("store: {}", filename.display());
164 let new_filename = filename.with_extension("bin.new");
165
166 {
167 let file = File::create(&new_filename)?;
169 bincode::serialize_into(file, saved_tower)?;
170 }
172 fs::rename(&new_filename, &filename)?;
173 Ok(())
175 }
176}
177
178impl TowerStorage for FileTowerStorage {
179 fn load(&self, node_pubkey: &Pubkey) -> Result<Tower> {
180 let filename = self.filename(node_pubkey);
181 trace!("load {}", filename.display());
182
183 fs::create_dir_all(filename.parent().unwrap())?;
185
186 if let Ok(file) = File::open(&filename) {
187 let mut stream = BufReader::new(file);
189
190 bincode::deserialize_from(&mut stream)
191 .map_err(|e| e.into())
192 .and_then(|t: SavedTowerVersions| t.try_into_tower(node_pubkey))
193 } else {
194 let file = File::open(self.old_filename(node_pubkey))?;
196 let mut stream = BufReader::new(file);
197 bincode::deserialize_from(&mut stream)
198 .map_err(|e| e.into())
199 .and_then(|t: SavedTower1_7_14| {
200 SavedTowerVersions::from(t).try_into_tower(node_pubkey)
201 })
202 }
203 }
204
205 fn store(&self, saved_tower: &SavedTowerVersions) -> Result<()> {
206 let pubkey = saved_tower.pubkey();
207 let filename = self.filename(&pubkey);
208 trace!("store: {}", filename.display());
209 let new_filename = filename.with_extension("bin.new");
210
211 {
212 let mut file = File::create(&new_filename)?;
214 saved_tower.serialize_into(&mut file)?;
215 }
217 fs::rename(&new_filename, &filename)?;
218 Ok(())
220 }
221}
222
223#[cfg(test)]
224pub mod test {
225 use {
226 super::*,
227 crate::consensus::{
228 tower1_7_14::{SavedTower1_7_14, Tower1_7_14},
229 BlockhashStatus, Tower,
230 },
231 solana_hash::Hash,
232 solana_keypair::Keypair,
233 solana_vote::vote_transaction::VoteTransaction,
234 solana_vote_program::vote_state::{
235 BlockTimestamp, Lockout, Vote, VoteState1_14_11, MAX_LOCKOUT_HISTORY,
236 },
237 tempfile::TempDir,
238 };
239
240 #[test]
241 fn test_tower_migration() {
242 let tower_path = TempDir::new().unwrap();
243 let identity_keypair = Keypair::new();
244 let node_pubkey = identity_keypair.pubkey();
245 let mut vote_state = VoteState1_14_11::default();
246 vote_state
247 .votes
248 .resize(MAX_LOCKOUT_HISTORY, Lockout::default());
249 vote_state.root_slot = Some(1);
250
251 let vote = Vote::new(vec![1, 2, 3, 4], Hash::default());
252 let tower_storage = FileTowerStorage::new(tower_path.path().to_path_buf());
253
254 let old_tower = Tower1_7_14 {
255 node_pubkey,
256 threshold_depth: 10,
257 threshold_size: 0.9,
258 vote_state,
259 last_vote: vote.clone(),
260 last_timestamp: BlockTimestamp::default(),
261 last_vote_tx_blockhash: BlockhashStatus::Uninitialized,
262 stray_restored_slot: Some(2),
263 last_switch_threshold_check: Option::default(),
264 };
265
266 {
267 let saved_tower = SavedTower1_7_14::new(&old_tower, &identity_keypair).unwrap();
268 tower_storage.store_old(&saved_tower).unwrap();
269 }
270
271 let loaded = Tower::restore(&tower_storage, &node_pubkey).unwrap();
272 assert_eq!(loaded.node_pubkey, old_tower.node_pubkey);
273 assert_eq!(loaded.last_vote(), VoteTransaction::from(vote));
274 assert_eq!(loaded.vote_state.root_slot, Some(1));
275 assert_eq!(loaded.stray_restored_slot(), None);
276 }
277}