solana_core/consensus/
tower_storage.rs

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        // This method assumes that `self` was just deserialized
27        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        // SavedTower always stores its data in 1_14_11 format
104        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    // Old filename for towers pre 1.9 (VoteStateUpdate)
147    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            // overwrite anything if exists
168            let file = File::create(&new_filename)?;
169            bincode::serialize_into(file, saved_tower)?;
170            // file.sync_all() hurts performance; pipeline sync-ing and submitting votes to the cluster!
171        }
172        fs::rename(&new_filename, &filename)?;
173        // self.path.parent().sync_all() hurts performance same as the above sync
174        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        // Ensure to create parent dir here, because restore() precedes save() always
184        fs::create_dir_all(filename.parent().unwrap())?;
185
186        if let Ok(file) = File::open(&filename) {
187            // New format
188            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            // Old format
195            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            // overwrite anything if exists
213            let mut file = File::create(&new_filename)?;
214            saved_tower.serialize_into(&mut file)?;
215            // file.sync_all() hurts performance; pipeline sync-ing and submitting votes to the cluster!
216        }
217        fs::rename(&new_filename, &filename)?;
218        // self.path.parent().sync_all() hurts performance same as the above sync
219        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}