avm_data_store/
lib.rs

1/*
2 * Copyright 2022 Fluence Labs Limited
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#![forbid(unsafe_code)]
18#![warn(rust_2018_idioms)]
19#![deny(
20    dead_code,
21    nonstandard_style,
22    unused_imports,
23    unused_mut,
24    unused_variables,
25    unused_unsafe,
26    unreachable_patterns
27)]
28
29use avm_interface::raw_outcome::RawAVMOutcome;
30
31use serde::Deserialize;
32use serde::Serialize;
33use std::borrow::Cow;
34use std::time::Duration;
35
36/// This trait is used for
37///   - persisting prev_data between successive calls of an interpreter
38///   - logging previous, current, and new data in case of spikes
39pub trait DataStore {
40    type Error;
41
42    fn initialize(&mut self) -> Result<(), Self::Error>;
43
44    fn store_data(
45        &mut self,
46        data: &[u8],
47        particle_id: &str,
48        current_peer_id: &str,
49    ) -> Result<(), Self::Error>;
50
51    fn read_data(
52        &mut self,
53        particle_id: &str,
54        current_peer_id: &str,
55    ) -> Result<Vec<u8>, Self::Error>;
56
57    /// Cleanup data that become obsolete.
58    fn cleanup_data(&mut self, particle_id: &str, current_peer_id: &str)
59        -> Result<(), Self::Error>;
60
61    /// Returns true if an anomaly happened and it's necessary to save execution data
62    /// for debugging purposes.
63    ///  execution_time - time taken by the interpreter to execute provided script
64    ///  memory_delta - count of bytes on which an interpreter heap has been extended
65    ///                 during execution of a particle
66    ///  outcome - a result of AquaVM invocation
67    fn detect_anomaly(
68        &self,
69        execution_time: Duration,
70        memory_delta: usize,
71        outcome: &RawAVMOutcome,
72    ) -> bool;
73
74    fn collect_anomaly_data(
75        &mut self,
76        particle_id: &str,
77        current_peer_id: &str,
78        anomaly_data: AnomalyData<'_>,
79    ) -> Result<(), Self::Error>;
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
83pub struct AnomalyData<'data> {
84    #[serde(borrow)]
85    pub air_script: Cow<'data, str>,
86    #[serde(borrow, with = "serde_bytes")]
87    pub particle: Cow<'data, [u8]>, // it's byte because of the restriction on trait objects methods
88    #[serde(borrow, with = "serde_bytes")]
89    pub prev_data: Cow<'data, [u8]>,
90    #[serde(borrow, with = "serde_bytes")]
91    pub current_data: Cow<'data, [u8]>,
92    #[serde(borrow, with = "serde_bytes")]
93    pub call_results: Cow<'data, [u8]>,
94    #[serde(borrow, with = "serde_bytes")]
95    pub avm_outcome: Cow<'data, [u8]>,
96    pub execution_time: Duration,
97    pub memory_delta: usize,
98}
99
100impl<'data> AnomalyData<'data> {
101    #[allow(clippy::too_many_arguments)]
102    pub fn new(
103        air_script: &'data str,
104        particle: &'data [u8],
105        prev_data: &'data [u8],
106        current_data: &'data [u8],
107        call_results: &'data [u8],
108        avm_outcome: &'data [u8],
109        execution_time: Duration,
110        memory_delta: usize,
111    ) -> Self {
112        Self {
113            air_script: air_script.into(),
114            particle: particle.into(),
115            prev_data: prev_data.into(),
116            current_data: current_data.into(),
117            call_results: call_results.into(),
118            avm_outcome: avm_outcome.into(),
119            execution_time,
120            memory_delta,
121        }
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128
129    fn anomaly_json(
130        air_script: &str,
131        particle: &[u8],
132        prev_data: &[u8],
133        current_data: &[u8],
134        call_results: &[u8],
135        avm_outcome: &[u8],
136    ) -> String {
137        format!(
138            concat!(
139                r#"{{"air_script":{air_script},"#,
140                r#""particle":{particle},"#,
141                r#""prev_data":{prev_data},"#,
142                r#""current_data":{current_data},"#,
143                r#""call_results":{call_results},"#,
144                r#""avm_outcome":{avm_outcome},"#,
145                r#""execution_time":{{"secs":42,"nanos":0}},"#,
146                r#""memory_delta":123"#,
147                r#"}}"#
148            ),
149            air_script = serde_json::to_string(air_script).unwrap(),
150            particle = serde_json::to_string(particle).unwrap(),
151            prev_data = serde_json::to_string(prev_data).unwrap(),
152            current_data = serde_json::to_string(current_data).unwrap(),
153            call_results = serde_json::to_string(call_results).unwrap(),
154            avm_outcome = serde_json::to_string(avm_outcome).unwrap(),
155        )
156    }
157    #[test]
158    fn anomaly_data_se() {
159        let anomaly = AnomalyData::new(
160            "(null)",
161            br#"{"data":"value"}"#,  // not real data
162            br#"{"trace":[]}"#,      // not real data
163            br#"{"trace":[1,2,3]}"#, // not real data
164            b"{}",                   // not real data
165            b"{}",
166            Duration::from_secs(42),
167            123,
168        );
169
170        let json_data = serde_json::to_string(&anomaly).expect("JSON serialize anomaly data");
171        let expected = anomaly_json(
172            &anomaly.air_script,
173            &anomaly.particle,
174            &anomaly.prev_data,
175            &anomaly.current_data,
176            &anomaly.call_results,
177            &anomaly.avm_outcome,
178        );
179        assert_eq!(json_data, expected);
180    }
181
182    #[test]
183    fn anomaly_data_de() {
184        let particle = br#"{"particle":"data"}"#;
185        let current_data = br#"{"data":"current"}"#;
186        let prev_data = br#"{"data":"prev"}"#;
187        let avm_outcome = br#"{"avm":[1,2,3]}"#;
188        let call_results = br#"{"call_results": "excellent result"}"#;
189        let json_data = anomaly_json(
190            "(null)",
191            &particle[..],
192            &prev_data[..],
193            &current_data[..],
194            &call_results[..],
195            &avm_outcome[..],
196        );
197
198        let anomaly: AnomalyData<'_> =
199            serde_json::from_str(&json_data).expect("deserialize JSON anomaly data");
200
201        assert_eq!(
202            anomaly,
203            AnomalyData::new(
204                "(null)",
205                &particle[..],
206                &prev_data[..],
207                &current_data[..],
208                &call_results[..],
209                &avm_outcome[..],
210                Duration::from_secs(42),
211                123,
212            )
213        )
214    }
215}