Skip to main content

solana_core/consensus/
tower_storage.rs

1use {
2    crate::consensus::{
3        Result, Tower, TowerError, TowerVersions, tower1_7_14::SavedTower1_7_14,
4        tower1_14_11::Tower1_14_11,
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, StableAbi, StableAbiSample))]
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, StableAbi, StableAbiSample),
82    frozen_abi(
83        api_digest = "2Ne3NmHSeLpPfv38wn7ZRsuq4i56kqYzJeFLYmz6bw3Z",
84        abi_digest = "6U5RkdsuzLHyWpjGWKCHj9AzGFreginb6cXUiXodbgRv"
85    )
86)]
87#[derive(Default, Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
88pub struct SavedTower {
89    signature: Signature,
90    #[serde(with = "serde_bytes")]
91    data: Vec<u8>,
92    #[serde(skip)]
93    node_pubkey: Pubkey,
94}
95
96impl SavedTower {
97    pub fn new<T: Signer>(tower: &Tower, keypair: &T) -> Result<Self> {
98        let node_pubkey = keypair.pubkey();
99        if tower.node_pubkey != node_pubkey {
100            return Err(TowerError::WrongTower(format!(
101                "node_pubkey is {:?} but found tower for {:?}",
102                node_pubkey, tower.node_pubkey
103            )));
104        }
105
106        // SavedTower always stores its data in 1_14_11 format
107        let tower: Tower1_14_11 = tower.clone().into();
108
109        let data = bincode::serialize(&tower)?;
110        let signature = keypair.sign_message(&data);
111        Ok(Self {
112            signature,
113            data,
114            node_pubkey,
115        })
116    }
117}
118
119pub trait TowerStorage: Sync + Send {
120    fn load(&self, node_pubkey: &Pubkey) -> Result<Tower>;
121    fn store(&self, saved_tower: &SavedTowerVersions) -> Result<()>;
122}
123
124#[derive(Debug, Default, Clone, PartialEq, Eq)]
125pub struct NullTowerStorage {}
126
127impl TowerStorage for NullTowerStorage {
128    fn load(&self, _node_pubkey: &Pubkey) -> Result<Tower> {
129        Err(TowerError::IoError(io::Error::other(
130            "NullTowerStorage::load() not available",
131        )))
132    }
133
134    fn store(&self, _saved_tower: &SavedTowerVersions) -> Result<()> {
135        Ok(())
136    }
137}
138
139#[derive(Debug, Default, Clone, PartialEq, Eq)]
140pub struct FileTowerStorage {
141    pub tower_path: PathBuf,
142}
143
144impl FileTowerStorage {
145    pub fn new(tower_path: PathBuf) -> Self {
146        Self { tower_path }
147    }
148
149    // Old filename for towers pre 1.9 (VoteStateUpdate)
150    pub fn old_filename(&self, node_pubkey: &Pubkey) -> PathBuf {
151        self.tower_path
152            .join(format!("tower-{node_pubkey}"))
153            .with_extension("bin")
154    }
155
156    pub fn filename(&self, node_pubkey: &Pubkey) -> PathBuf {
157        self.tower_path
158            .join(format!("tower-1_9-{node_pubkey}"))
159            .with_extension("bin")
160    }
161
162    #[cfg(test)]
163    fn store_old(&self, saved_tower: &SavedTower1_7_14) -> Result<()> {
164        let pubkey = saved_tower.node_pubkey;
165        let filename = self.old_filename(&pubkey);
166        trace!("store: {}", filename.display());
167        let new_filename = filename.with_extension("bin.new");
168
169        {
170            // overwrite anything if exists
171            let file = File::create(&new_filename)?;
172            bincode::serialize_into(file, saved_tower)?;
173            // file.sync_all() hurts performance; pipeline sync-ing and submitting votes to the cluster!
174        }
175        fs::rename(&new_filename, &filename)?;
176        // self.path.parent().sync_all() hurts performance same as the above sync
177        Ok(())
178    }
179}
180
181impl TowerStorage for FileTowerStorage {
182    fn load(&self, node_pubkey: &Pubkey) -> Result<Tower> {
183        let filename = self.filename(node_pubkey);
184        trace!("load {}", filename.display());
185
186        // Ensure to create parent dir here, because restore() precedes save() always
187        fs::create_dir_all(filename.parent().unwrap())?;
188
189        if let Ok(file) = File::open(&filename) {
190            // New format
191            let mut stream = BufReader::new(file);
192
193            bincode::deserialize_from(&mut stream)
194                .map_err(|e| e.into())
195                .and_then(|t: SavedTowerVersions| t.try_into_tower(node_pubkey))
196        } else {
197            // Old format
198            let file = File::open(self.old_filename(node_pubkey))?;
199            let mut stream = BufReader::new(file);
200            bincode::deserialize_from(&mut stream)
201                .map_err(|e| e.into())
202                .and_then(|t: SavedTower1_7_14| {
203                    SavedTowerVersions::from(t).try_into_tower(node_pubkey)
204                })
205        }
206    }
207
208    fn store(&self, saved_tower: &SavedTowerVersions) -> Result<()> {
209        let pubkey = saved_tower.pubkey();
210        let filename = self.filename(&pubkey);
211        trace!("store: {}", filename.display());
212        let new_filename = filename.with_extension("bin.new");
213
214        {
215            // overwrite anything if exists
216            let mut file = File::create(&new_filename)?;
217            saved_tower.serialize_into(&mut file)?;
218            // file.sync_all() hurts performance; pipeline sync-ing and submitting votes to the cluster!
219        }
220        fs::rename(&new_filename, &filename)?;
221        // self.path.parent().sync_all() hurts performance same as the above sync
222        Ok(())
223    }
224}
225
226#[cfg(test)]
227pub mod test {
228    use {
229        super::*,
230        crate::consensus::{
231            BlockhashStatus, Tower,
232            tower1_7_14::{SavedTower1_7_14, Tower1_7_14},
233        },
234        solana_hash::Hash,
235        solana_keypair::Keypair,
236        solana_vote::vote_transaction::VoteTransaction,
237        solana_vote_program::vote_state::{
238            BlockTimestamp, Lockout, MAX_LOCKOUT_HISTORY, Vote, VoteState1_14_11,
239        },
240        tempfile::TempDir,
241    };
242
243    #[test]
244    fn test_tower_migration() {
245        let tower_path = TempDir::new().unwrap();
246        let identity_keypair = Keypair::new();
247        let node_pubkey = identity_keypair.pubkey();
248        let mut vote_state = VoteState1_14_11::default();
249        vote_state
250            .votes
251            .resize(MAX_LOCKOUT_HISTORY, Lockout::default());
252        vote_state.root_slot = Some(1);
253
254        let vote = Vote::new(vec![1, 2, 3, 4], Hash::default());
255        let tower_storage = FileTowerStorage::new(tower_path.path().to_path_buf());
256
257        let old_tower = Tower1_7_14 {
258            node_pubkey,
259            threshold_depth: 10,
260            threshold_size: 0.9,
261            vote_state,
262            last_vote: vote.clone(),
263            last_timestamp: BlockTimestamp::default(),
264            last_vote_tx_blockhash: BlockhashStatus::Uninitialized,
265            stray_restored_slot: Some(2),
266            last_switch_threshold_check: Option::default(),
267        };
268
269        {
270            let saved_tower = SavedTower1_7_14::new(&old_tower, &identity_keypair).unwrap();
271            tower_storage.store_old(&saved_tower).unwrap();
272        }
273
274        let loaded = Tower::restore(&tower_storage, &node_pubkey).unwrap();
275        assert_eq!(loaded.node_pubkey, old_tower.node_pubkey);
276        assert_eq!(loaded.last_vote(), VoteTransaction::from(vote));
277        assert_eq!(loaded.vote_state.root_slot, Some(1));
278        assert_eq!(loaded.stray_restored_slot(), None);
279    }
280}