api3_common/
beacon.rs

1use crate::abi::{decode, encode, encode_packed, keccak256, Int, ParamType, Token, Uint, U256};
2use crate::access::AccessControlRegistry;
3use crate::whitelist::Whitelist;
4use crate::{ensure, keccak_packed, median, Bytes, Bytes32, DataPoint, Error, StaticRole, Zero};
5
6const ONE_HOUR_IN_SECONDS: u32 = 3600;
7const FIFTEEN_MINUTES_IN_SECONDS: u32 = 900;
8
9/// Generic storage trait. Used for the common processing logic so that each chain could
10/// have their own implementation.
11pub trait Storage<T> {
12    fn get(&self, key: &Bytes32) -> Option<T>;
13    fn store(&mut self, key: Bytes32, t: T);
14}
15
16/// Public trait that handles signature verification across different chains
17pub trait SignatureManger {
18    /// Verifies the signature against the message and public key
19    /// Returns if the signature is valid
20    ///
21    /// # Arguments
22    ///
23    /// * `key` - The public key of the signer
24    /// * `message` - The message to verify
25    /// * `signature` - The signature to verify
26    fn verify(key: &[u8], message: &[u8], signature: &[u8]) -> bool;
27}
28
29/// Public trait that handles timestamp fetching across different chains
30pub trait TimestampChecker {
31    fn current_timestamp(&self) -> u32;
32
33    /// Returns if the timestamp used in the signature is valid
34    /// Returns `false` if the timestamp is not at most 1 hour old to
35    /// prevent replays. Returns `false` if the timestamp is not from the past,
36    /// with some leeway to accomodate for some benign time drift. These values
37    /// are appropriate in most cases, but you can adjust them if you are aware
38    /// of the implications.
39    ///
40    /// # Arguments
41    ///
42    /// * `timestamp` Timestamp used in the signature
43    fn is_valid(&self, timestamp: u32) -> bool {
44        let c = self.current_timestamp();
45        timestamp
46            .checked_add(ONE_HOUR_IN_SECONDS)
47            .expect("Invalid timestamp")
48            > c
49            && timestamp < c + FIFTEEN_MINUTES_IN_SECONDS
50    }
51}
52
53/// Reads the data point with ID
54/// Returns tuple containing (DataPoint.value, DataPoint.timestamp).
55///
56/// # Arguments
57///
58/// * `datapoint_id` Data point ID
59/// * `msg_sender` Address of who sent the transaction
60/// * `datapoint_storage` Data point storage that links `datapoint_id` to `Datapoint`
61/// * `access` The access control registry used
62/// * `whitelist` The whitelist implementation used
63pub fn read_with_data_point_id<
64    D: Storage<DataPoint>,
65    A: AccessControlRegistry,
66    W: Whitelist<Address = A::Address>,
67>(
68    datapoint_id: &Bytes32,
69    msg_sender: &A::Address,
70    datapoint_storage: &D,
71    access: &A,
72    whitelist: &W,
73) -> Result<(Int, u32), Error> {
74    ensure!(
75        reader_can_read_data_point(datapoint_id, msg_sender, access, whitelist),
76        Error::AccessDenied
77    )?;
78    let data_point = datapoint_storage
79        .get(datapoint_id)
80        .ok_or(Error::BeaconDataNotFound)?;
81    Ok((data_point.value, data_point.timestamp))
82}
83
84/// Reads the data point with name
85/// The read data point may belong to a Beacon or dAPI. The reader
86/// must be whitelisted for the hash of the data point name.
87/// Returns tuple containing (DataPoint.value, DataPoint.timestamp).
88///
89/// # Arguments
90///
91/// * `name` Data point name
92/// * `msg_sender` Address of who sent the transaction
93/// * `datapoint_storage` Data point storage that links `datapoint_id` to `Datapoint`
94/// * `name_storage` Name to Datapoint Id storage used
95/// * `access` The access control registry used
96/// * `whitelist` The whitelist implementation used
97pub fn read_with_name<
98    D: Storage<DataPoint>,
99    H: Storage<Bytes32>,
100    A: AccessControlRegistry,
101    W: Whitelist<Address = A::Address>,
102>(
103    name: Bytes32,
104    msg_sender: &A::Address,
105    datapoint_storage: &D,
106    name_storage: &H,
107    access: &A,
108    whitelist: &W,
109) -> Result<(Int, u32), Error> {
110    let name_hash = keccak_packed(&[Token::FixedBytes(name.to_vec())]);
111    ensure!(
112        reader_can_read_data_point(&name_hash, msg_sender, access, whitelist),
113        Error::AccessDenied
114    )?;
115    let key = name_storage
116        .get(&name_hash)
117        .ok_or(Error::NameHashNotFound)?;
118    let data_point = datapoint_storage
119        .get(&key)
120        .ok_or(Error::BeaconDataNotFound)?;
121    Ok((data_point.value, data_point.timestamp))
122}
123
124/// Returns if a reader can read the data point
125///
126/// # Arguments
127///
128/// * `data_point_id` Data point ID
129/// * `reader` The reader that is trying to read the datapoint
130/// * `access` The access control registry used
131/// * `whitelist` The whitelist implementation used
132pub fn reader_can_read_data_point<A: AccessControlRegistry, W: Whitelist<Address = A::Address>>(
133    data_point_id: &Bytes32,
134    reader: &A::Address,
135    access: &A,
136    whitelist: &W,
137) -> bool {
138    let role = access.find_static_role(StaticRole::UnlimitedReaderRole);
139    reader.is_zero()
140        || whitelist.user_is_whitelisted(data_point_id, reader)
141        || access.has_role(&role, reader)
142}
143
144/// Updates the dAPI that is specified by the beacon IDs.
145/// Returns the dAPI ID.
146///
147/// # Arguments
148///
149/// * `beacon_ids` is the list of beacon ids to perform aggregation
150pub fn update_dapi_with_beacons<D: Storage<DataPoint>>(
151    d: &mut D,
152    beacon_ids: &[Bytes32],
153) -> Result<Bytes32, Error> {
154    let beacon_count = beacon_ids.len();
155    ensure!(beacon_count > 1, Error::LessThanTwoBeacons)?;
156
157    let mut values = Vec::with_capacity(beacon_count);
158    let mut accumulated_timestamp = U256::from(0);
159
160    for beacon_id in beacon_ids {
161        let data_point = d.get(beacon_id).ok_or(Error::BeaconDataNotFound)?;
162        values.push(data_point.value);
163        accumulated_timestamp += U256::from(data_point.timestamp);
164    }
165
166    let dapi_id = derive_dapi_id(beacon_ids);
167    let dapi_datapoint = d.get(&dapi_id).ok_or(Error::BeaconDataNotFound)?;
168
169    let updated_timestamp = (accumulated_timestamp / beacon_count).as_u32();
170    ensure!(
171        updated_timestamp >= dapi_datapoint.timestamp,
172        Error::UpdatedValueOutdated
173    )?;
174    let updated_value = median(&values);
175    let datapoint = DataPoint::new(updated_value, updated_timestamp);
176
177    d.store(dapi_id, datapoint);
178    Ok(dapi_id)
179}
180
181/// Updates a dAPI using data signed by the respective Airnodes
182/// without requiring a request or subscription. The beacons for which the
183/// signature is omitted will be read from the storage.
184/// Returns the dAPI ID.
185///
186/// # Arguments
187///
188/// * `datapoint_storage` The datapoint storage trait implementation to use
189/// * `timestamp_checker` The timestamp checker/validator to use
190/// * `airnodes` Airnode addresses
191/// * `template_ids` Template IDs
192/// * `timestamps` Timestamps used in the signatures
193/// * `data` Response data (an `int256` encoded in contract ABI per Beacon)
194/// * `signatures` Template ID, a timestamp and the response data signed by the respective Airnode address per Beacon
195#[allow(clippy::too_many_arguments)]
196pub fn update_dapi_with_signed_data<
197    D: Storage<DataPoint>,
198    S: SignatureManger,
199    T: TimestampChecker,
200>(
201    datapoint_storage: &mut D,
202    timestamp_checker: &T,
203    airnodes: Vec<Bytes>,
204    template_ids: Vec<[u8; 32]>,
205    timestamps: Vec<[u8; 32]>,
206    data: Vec<Bytes>,
207    signatures: Vec<Bytes>,
208) -> Result<Bytes32, Error> {
209    let beacon_count = template_ids.len();
210
211    ensure!(
212        beacon_count == template_ids.len()
213            && beacon_count == timestamps.len()
214            && beacon_count == data.len()
215            && beacon_count == signatures.len(),
216        Error::ParameterLengthMismatch
217    )?;
218
219    ensure!(beacon_count > 1, Error::LessThanTwoBeacons)?;
220
221    let mut beacon_ids = Vec::with_capacity(beacon_count);
222    let mut values = Vec::with_capacity(beacon_count);
223    let mut accumulated_timestamp = U256::from(0);
224
225    for ind in 0..beacon_count {
226        if !signatures[ind].is_empty() {
227            let timestamp = U256::from_big_endian(&timestamps[ind]);
228            let timestamp_u32 = timestamp.as_u32();
229            ensure!(
230                timestamp_checker.is_valid(timestamp_u32),
231                Error::InvalidTimestamp
232            )?;
233
234            let message = keccak_packed(&[
235                Token::FixedBytes(template_ids[ind].clone().to_vec()),
236                Token::Uint(timestamp),
237                Token::Bytes(data[ind].clone()),
238            ]);
239            ensure!(
240                S::verify(&airnodes[ind], &message, &signatures[ind]),
241                Error::InvalidSignature
242            )?;
243
244            values.push(decode_fulfillment_data(&data[ind])?);
245
246            // Timestamp validity is already checked, which means it will
247            // be small enough to be typecast into `uint32`
248            accumulated_timestamp += timestamp;
249            beacon_ids.push(derive_beacon_id(
250                airnodes[ind].clone(),
251                template_ids[ind],
252            ));
253        } else {
254            let beacon_id = derive_beacon_id(airnodes[ind].clone(), template_ids[ind]);
255            let data_point = datapoint_storage
256                .get(&beacon_id)
257                .ok_or(Error::BeaconDataNotFound)?;
258            values.push(data_point.value);
259            accumulated_timestamp += U256::from(data_point.timestamp);
260            beacon_ids.push(beacon_id);
261        }
262    }
263    let dapi_id = derive_dapi_id(&beacon_ids);
264    let updated_timestamp = (accumulated_timestamp / beacon_count).as_u32();
265    let dapi_datapoint = datapoint_storage
266        .get(&dapi_id)
267        .ok_or(Error::BeaconDataNotFound)?;
268    ensure!(
269        updated_timestamp >= dapi_datapoint.timestamp,
270        Error::UpdatedValueOutdated
271    )?;
272    let updated_value = median(&values);
273    let datapoint = DataPoint::new(updated_value, updated_timestamp);
274    datapoint_storage.store(dapi_id, datapoint);
275    Ok(dapi_id)
276}
277
278/// Sets the data point ID the name points to
279/// While a data point ID refers to a specific Beacon or dAPI, names
280/// provide a more abstract interface for convenience. This means a name
281/// that was pointing at a Beacon can be pointed to a dAPI, then another
282/// dAPI, etc.
283///
284/// # Arguments
285///
286/// * `name` Human-readable name
287/// * `datapoint_id` Data point ID the name will point to
288/// * `msg_sender` Address of who sent the transaction
289/// * `access` Access control implementation to use
290/// * `storage` Storage implementation to use for linking name and datapoint_id
291pub fn set_name<D: Storage<Bytes32>, A: AccessControlRegistry>(
292    name: Bytes32,
293    datapoint_id: Bytes32,
294    msg_sender: &A::Address,
295    access: &A,
296    storage: &mut D,
297) -> Result<(), Error> {
298    ensure!(name != Bytes32::default(), Error::InvalidData)?;
299    ensure!(datapoint_id != Bytes32::default(), Error::InvalidData)?;
300    let role = access.find_static_role(StaticRole::NameSetterRole);
301    ensure!(access.has_role(&role, msg_sender), Error::AccessDenied)?;
302
303    storage.store(
304        keccak_packed(&[Token::FixedBytes(name.to_vec())]),
305        datapoint_id,
306    );
307
308    Ok(())
309}
310
311/// Derives the beacon id based on the `airnode` and `templated_id`
312/// Returns the beacon id
313///
314/// # Arguments
315///
316/// * `airnode` Airnode address
317/// * `template_id` Template ID
318pub fn derive_beacon_id(airnode: Bytes, template_id: Bytes32) -> Bytes32 {
319    ensure!(not_zero(&airnode), Error::AirnodeIdZero).unwrap();
320    ensure!(not_zero(&template_id), Error::TemplateIdZero).unwrap();
321    let (encoded, _) = encode_packed(&[
322        Token::Bytes(airnode),
323        Token::FixedBytes(template_id.to_vec()),
324    ]);
325    keccak256(&encoded)
326}
327
328/// Derives the dAPI ID from the beacon IDs
329/// Notice that `encode()` is used over `encode_packed()`
330/// Returns the derived dapi id
331///
332/// # Arguments
333///
334/// * `beacon_ids` Beacon IDs
335pub fn derive_dapi_id(beacon_ids: &[Bytes32]) -> Bytes32 {
336    let tokens: Vec<Token> = beacon_ids
337        .iter()
338        .map(|b| Token::FixedBytes(b.to_vec()))
339        .collect();
340    let encoded = encode(&tokens);
341    keccak256(&encoded)
342}
343
344/// Decode the encoded data to the respective data types.
345/// Returns the `Result` of decoding fulfillment data.
346///
347/// # Arguments
348///
349/// * `data` Fulfillment data (an `int256` encoded in contract ABI)
350pub fn decode_fulfillment_data(data: &Bytes) -> Result<Int, Error> {
351    ensure!(data.len() == 32, Error::InvalidDataLength)?;
352
353    let tokens = decode(&[ParamType::Int(0)], data)?;
354    ensure!(tokens.len() == 1, Error::InvalidDataLength)?;
355
356    if let Token::Int(i) = tokens[0] {
357        Ok(i)
358    } else {
359        Err(Error::InvalidDataType)
360    }
361}
362
363/// Called privately to process the Beacon update.
364/// Returns the updated Beacon value.
365///
366/// # Arguments
367///
368/// * `storage` The storage between `beacon_id` to `Datapoint`
369/// * `beacon_id` The Beacon ID
370/// * `timestamp` Timestamp used in the signature
371/// * `data` Fulfillment data (an `int256` encoded in contract ABI)
372pub fn process_beacon_update<D: Storage<DataPoint>>(
373    storage: &mut D,
374    beacon_id: Bytes32,
375    timestamp: Uint,
376    data: Bytes,
377) -> Result<(), Error> {
378    let updated_beacon_value = decode_fulfillment_data(&data)?;
379
380    let beacon = storage.get(&beacon_id).ok_or(Error::BeaconDataNotFound)?;
381    ensure!(
382        timestamp.as_u32() > beacon.timestamp,
383        Error::FulfillmentOlderThanBeacon
384    )?;
385
386    // Timestamp validity is already checked by `onlyValidTimestamp`, which
387    // means it will be small enough to be typecast into `uint32`
388
389    let datapoint = DataPoint::new(updated_beacon_value, timestamp.as_u32());
390    storage.store(beacon_id, datapoint);
391
392    Ok(())
393}
394
395fn not_zero(bytes: &[u8]) -> bool {
396    let mut count = 0;
397    for i in bytes {
398        if *i == 0u8 { count += 1; }
399    }
400    count != bytes.len()
401}
402
403#[cfg(test)]
404mod tests {
405    use crate::beacon::not_zero;
406    use crate::derive_beacon_id;
407
408    #[test]
409    fn not_zero_works() {
410        assert!(!not_zero(&[0;12]));
411        let mut v = [0;12];
412        v[2] = 1;
413        assert!(not_zero(&v));
414    }
415
416    #[test]
417    fn encode_packed_works() {
418        let raw_template_id =
419            hex::decode("0000000000000000000000000000000000000000000000000000000000000001")
420                .unwrap();
421        let airnode =
422            hex::decode("1d73899cc9fc3ad06a2c7f5bf26c8a4a76b42de905cb9b6ae96390355441a0ca")
423                .unwrap();
424        let mut template_id = [0; 32];
425        template_id.copy_from_slice(&raw_template_id);
426        let beacon_id = derive_beacon_id(airnode, template_id);
427        assert_eq!(
428            hex::encode(beacon_id),
429            "ad1b5c75a8b8e0d7dbc56c1e28aee9fabe285ad8fb61a256ddabd4523bfb284a"
430        );
431    }
432}