safety_rules/
persistent_safety_storage.rs

1// Copyright (c) The Diem Core Contributors
2// SPDX-License-Identifier: Apache-2.0
3
4// Copyright 2021 Conflux Foundation. All rights reserved.
5// Conflux is free software and distributed under GNU General Public License.
6// See http://www.gnu.org/licenses/
7
8use crate::Error;
9use consensus_types::{common::Author, safety_data::SafetyData};
10use diem_crypto::{
11    hash::CryptoHash, PrivateKey, SigningKey, ValidCryptoMaterial,
12};
13use diem_global_constants::{
14    CONSENSUS_KEY, EXECUTION_KEY, OWNER_ACCOUNT, SAFETY_DATA,
15};
16use diem_logger::prelude::*;
17use diem_secure_storage::{CryptoStorage, KVStorage, OnDiskStorage};
18use diem_types::validator_config::{
19    ConsensusPrivateKey, ConsensusPublicKey, ConsensusSignature,
20};
21use serde::Serialize;
22use std::{convert::TryFrom, fs};
23
24/// Wraps `OnDiskStorage` with the safety-rules-specific schema
25/// (`SAFETY_DATA`, `OWNER_ACCOUNT`) and a single-key in-memory cache over the
26/// hot `SAFETY_DATA` read.
27///
28/// `cached_safety_data` is a local copy of the `SafetyData` entry. Every vote
29/// reads it; caching at this layer keeps the per-vote path off of disk. On
30/// writes, both the cache and the on-disk file are updated; on write error
31/// the cache is invalidated so the next read goes back through to disk.
32pub struct PersistentSafetyStorage {
33    enable_cached_safety_data: bool,
34    cached_safety_data: Option<SafetyData>,
35    internal_store: OnDiskStorage,
36    private_key: ConsensusPrivateKey,
37}
38
39impl PersistentSafetyStorage {
40    /// Use this to instantiate a PersistentStorage for a new data store, one
41    /// that has no SafetyRules values set.
42    pub fn initialize(
43        mut internal_store: OnDiskStorage, author: Author,
44        private_key: ConsensusPrivateKey, enable_cached_safety_data: bool,
45    ) -> Self {
46        let geneisis_safety_data = SafetyData::new(1, 0, 0, None);
47        let safety_data = Self::initialize_(
48            &mut internal_store,
49            geneisis_safety_data,
50            author,
51        )
52        .expect("Unable to initialize backend storage");
53
54        Self {
55            enable_cached_safety_data,
56            cached_safety_data: Some(safety_data),
57            internal_store,
58            private_key,
59        }
60    }
61
62    pub fn replace_with_suffix(
63        &mut self, new_storage_suffix: &str,
64    ) -> Result<(), Error> {
65        let current_path = self.internal_store.file_path().clone();
66        let new_path = current_path.with_extension(new_storage_suffix);
67        if !new_path.exists() {
68            return Err(Error::SecureStorageUnexpectedError(format!(
69                "new secure storage path incorrect: {:?}",
70                new_path
71            )));
72        }
73        let new_disk_storage = OnDiskStorage::new(new_path.clone());
74        let old_account: Author = self.internal_store.get(OWNER_ACCOUNT)?.value;
75        let new_account: Author = new_disk_storage.get(OWNER_ACCOUNT)?.value;
76        if old_account != new_account {
77            return Err(Error::SecureStorageUnexpectedError(format!(
78                "current: {}, new: {}",
79                old_account, new_account
80            )));
81        }
82        fs::rename(&new_path, &current_path)
83            .map_err(|e| Error::InternalError(e.to_string()))?;
84        self.internal_store = OnDiskStorage::new(current_path);
85        self.cached_safety_data = self.internal_store.get(SAFETY_DATA)?.value;
86        Ok(())
87    }
88
89    pub fn save_to_suffix(
90        &mut self, new_storage_suffix: &str,
91    ) -> Result<(), Error> {
92        let current_path = self.internal_store.file_path();
93        let new_path = current_path.with_extension(new_storage_suffix);
94        fs::rename(current_path, &new_path)
95            .map_err(|e| Error::InternalError(e.to_string()))?;
96        Ok(())
97    }
98
99    fn initialize_(
100        internal_store: &mut OnDiskStorage, safety_data: SafetyData,
101        author: Author,
102    ) -> Result<SafetyData, Error> {
103        // Attempting to re-initialize existing storage. This can happen in
104        // environments like cluster test. Rather than be rigid here,
105        // leave it up to the developer to detect inconsistencies or why
106        // they did not reset storage between rounds. Do not repeat the
107        // checks again below, because it is just too strange to have a
108        // partially configured storage.
109        // NOTE: If the key exists, `OnDiskStorage` does not return error
110        // when we `set` the value, so we need to `get` first here.
111        if let Ok(safety_data) = internal_store.get::<SafetyData>(SAFETY_DATA) {
112            diem_warn!("Attempted to re-initialize existing storage");
113            return Ok(safety_data.value);
114        }
115
116        internal_store.set(SAFETY_DATA, safety_data.clone())?;
117        internal_store.set(OWNER_ACCOUNT, author)?;
118        Ok(safety_data)
119    }
120
121    pub fn author(&self) -> Result<Author, Error> {
122        Ok(self.internal_store.get(OWNER_ACCOUNT).map(|v| v.value)?)
123    }
124
125    pub fn consensus_key_for_version(
126        &self, version: ConsensusPublicKey,
127    ) -> Result<ConsensusPrivateKey, Error> {
128        if self.private_key.public_key() == version {
129            let serialized: &[u8] = &(self.private_key.to_bytes());
130            let cloned = ConsensusPrivateKey::try_from(serialized).unwrap();
131            Ok(cloned)
132        } else {
133            Ok(self
134                .internal_store
135                .export_private_key_for_version(CONSENSUS_KEY, version)?)
136        }
137    }
138
139    pub fn sign<T: Serialize + CryptoHash>(
140        &self, key_name: String, key_version: ConsensusPublicKey, message: &T,
141    ) -> Result<ConsensusSignature, Error> {
142        if key_name == CONSENSUS_KEY || key_name == EXECUTION_KEY {
143            Ok(self.private_key.sign(message))
144        } else {
145            Ok(self.internal_store.sign_using_version(
146                &key_name,
147                key_version,
148                message,
149            )?)
150        }
151    }
152
153    pub fn safety_data(&mut self) -> Result<SafetyData, Error> {
154        if !self.enable_cached_safety_data {
155            return self.internal_store.get(SAFETY_DATA).map(|v| v.value)?;
156        }
157
158        if let Some(cached_safety_data) = self.cached_safety_data.clone() {
159            Ok(cached_safety_data)
160        } else {
161            let safety_data: SafetyData =
162                self.internal_store.get(SAFETY_DATA).map(|v| v.value)?;
163            self.cached_safety_data = Some(safety_data.clone());
164            Ok(safety_data)
165        }
166    }
167
168    pub fn set_safety_data(&mut self, data: SafetyData) -> Result<(), Error> {
169        match self.internal_store.set(SAFETY_DATA, data.clone()) {
170            Ok(_) => {
171                self.cached_safety_data = Some(data);
172                Ok(())
173            }
174            Err(error) => {
175                self.cached_safety_data = None;
176                Err(Error::SecureStorageUnexpectedError(error.to_string()))
177            }
178        }
179    }
180
181    #[cfg(any(test, feature = "testing"))]
182    pub fn internal_store(&mut self) -> &mut OnDiskStorage {
183        &mut self.internal_store
184    }
185}
186
187#[cfg(test)]
188mod tests {
189    use super::*;
190    use crate::test_utils::test_storage;
191    use diem_types::validator_signer::ValidatorSigner;
192
193    #[test]
194    fn test() {
195        let signer = ValidatorSigner::from_int(0);
196        let mut safety_storage = test_storage(&signer);
197
198        let safety_data = safety_storage.safety_data().unwrap();
199        assert_eq!(safety_data.epoch, 1);
200        assert_eq!(safety_data.last_voted_round, 0);
201        assert_eq!(safety_data.preferred_round, 0);
202
203        safety_storage
204            .set_safety_data(SafetyData::new(9, 8, 1, None))
205            .unwrap();
206
207        let safety_data = safety_storage.safety_data().unwrap();
208        assert_eq!(safety_data.epoch, 9);
209        assert_eq!(safety_data.last_voted_round, 8);
210        assert_eq!(safety_data.preferred_round, 1);
211    }
212}