unc_primitives/
utils.rs

1use std::cmp::max;
2use std::convert::AsRef;
3use std::fmt;
4
5use chrono;
6use chrono::DateTime;
7use rand::distributions::Alphanumeric;
8use rand::{thread_rng, Rng};
9use serde;
10
11use crate::hash::{hash, CryptoHash};
12use crate::receipt::Receipt;
13use crate::transaction::SignedTransaction;
14use crate::types::{NumSeats, NumShards, ShardId};
15use crate::version::{
16    ProtocolVersion, CORRECT_RANDOM_VALUE_PROTOCOL_VERSION, CREATE_HASH_PROTOCOL_VERSION,
17    CREATE_RECEIPT_ID_SWITCH_TO_CURRENT_BLOCK_VERSION,
18};
19
20use unc_crypto::{ED25519PublicKey, Secp256K1PublicKey};
21use unc_primitives_core::account::id::{AccountId, AccountType};
22
23use std::mem::size_of;
24use std::ops::Deref;
25
26pub mod min_heap;
27
28/// Number of nano seconds in a second.
29const NS_IN_SECOND: u64 = 1_000_000_000;
30
31/// A data structure for tagging data as already being validated to prevent
32/// redundant work.
33///
34/// # Example
35///
36/// ```ignore
37/// struct Foo;
38/// struct Error;
39///
40/// /// Performs expensive validation of `foo`.
41/// fn validate_foo(foo: &Foo) -> Result<bool, Error>;
42///
43/// fn do_stuff(foo: Foo) {
44///     let foo = MaybeValidated::from(foo);
45///     do_stuff_with_foo(&foo);
46///     if foo.validate_with(validate_foo) {
47///         println!("^_^");
48///     }
49/// }
50///
51/// fn do_stuff_with_foo(foo: &MaybeValidated<Foo) {
52///     // …
53///     if maybe_do_something && foo.validate_with(validate_foo) {
54///         println!("@_@");
55///     }
56///     // …
57/// }
58/// ```
59#[derive(Clone)]
60pub struct MaybeValidated<T> {
61    validated: std::cell::Cell<bool>,
62    payload: T,
63}
64
65impl<T> MaybeValidated<T> {
66    /// Creates new MaybeValidated object marking payload as validated.  No
67    /// verification is performed; it’s caller’s responsibility to make sure the
68    /// payload has indeed been validated.
69    ///
70    /// # Example
71    ///
72    /// ```
73    /// use unc_primitives::utils::MaybeValidated;
74    ///
75    /// let value = MaybeValidated::from_validated(42);
76    /// assert!(value.is_validated());
77    /// assert_eq!(Ok(true), value.validate_with::<(), _>(|_| panic!()));
78    /// ```
79    pub fn from_validated(payload: T) -> Self {
80        Self { validated: std::cell::Cell::new(true), payload }
81    }
82
83    /// Validates payload with given `validator` function and returns result of
84    /// the validation.  If payload has already been validated returns
85    /// `Ok(true)`.  Note that this method changes the internal validated flag
86    /// so it’s probably incorrect to call it with different `validator`
87    /// functions.
88    ///
89    /// # Example
90    ///
91    /// ```
92    /// use unc_primitives::utils::MaybeValidated;
93    ///
94    /// let value = MaybeValidated::from(42);
95    /// assert_eq!(Err(()), value.validate_with(|_| Err(())));
96    /// assert_eq!(Ok(false), value.validate_with::<(), _>(|v| Ok(*v == 24)));
97    /// assert!(!value.is_validated());
98    /// assert_eq!(Ok(true), value.validate_with::<(), _>(|v| Ok(*v == 42)));
99    /// assert!(value.is_validated());
100    /// assert_eq!(Ok(true), value.validate_with::<(), _>(|_| panic!()));
101    /// ```
102    pub fn validate_with<E, F: FnOnce(&T) -> Result<bool, E>>(
103        &self,
104        validator: F,
105    ) -> Result<bool, E> {
106        if self.validated.get() {
107            Ok(true)
108        } else {
109            let res = validator(&self.payload);
110            self.validated.set(*res.as_ref().unwrap_or(&false));
111            res
112        }
113    }
114
115    /// Marks the payload as valid.  No verification is performed; it’s caller’s
116    /// responsibility to make sure the payload has indeed been validated.
117    pub fn mark_as_valid(&self) {
118        self.validated.set(true);
119    }
120
121    /// Applies function to the payload (whether it’s been validated or not) and
122    /// returns new object with result of the function as payload.  Validated
123    /// state is not changed.
124    ///
125    /// # Example
126    ///
127    /// ```
128    /// use unc_primitives::utils::MaybeValidated;
129    ///
130    /// let value = MaybeValidated::from(42);
131    /// assert_eq!("42", value.map(|v| v.to_string()).into_inner());
132    /// ```
133    pub fn map<U, F: FnOnce(T) -> U>(self, validator: F) -> MaybeValidated<U> {
134        MaybeValidated { validated: self.validated, payload: validator(self.payload) }
135    }
136
137    /// Returns a new object storing reference to this object’s payload.  Note
138    /// that the two objects do not share the validated state so calling
139    /// `validate_with` on one of them does not affect the other.
140    ///
141    /// # Example
142    ///
143    /// ```
144    /// use unc_primitives::utils::MaybeValidated;
145    ///
146    /// let value = MaybeValidated::from(42);
147    /// let value_as_ref = value.as_ref();
148    /// assert_eq!(Ok(true), value_as_ref.validate_with::<(), _>(|&&v| Ok(v == 42)));
149    /// assert!(value_as_ref.is_validated());
150    /// assert!(!value.is_validated());
151    /// ```
152    pub fn as_ref(&self) -> MaybeValidated<&T> {
153        MaybeValidated { validated: self.validated.clone(), payload: &self.payload }
154    }
155
156    /// Returns whether the payload has been validated.
157    pub fn is_validated(&self) -> bool {
158        self.validated.get()
159    }
160
161    /// Extracts the payload whether or not it’s been validated.
162    pub fn into_inner(self) -> T {
163        self.payload
164    }
165
166    /// Returns a reference to the payload
167    pub fn get_inner(&self) -> &T {
168        &self.payload
169    }
170}
171
172impl<T> From<T> for MaybeValidated<T> {
173    /// Creates new MaybeValidated object marking payload as not validated.
174    fn from(payload: T) -> Self {
175        Self { validated: std::cell::Cell::new(false), payload }
176    }
177}
178
179impl<T: Sized> Deref for MaybeValidated<T> {
180    type Target = T;
181
182    fn deref(&self) -> &Self::Target {
183        &self.payload
184    }
185}
186
187pub fn get_block_shard_id(block_hash: &CryptoHash, shard_id: ShardId) -> Vec<u8> {
188    let mut res = Vec::with_capacity(40);
189    res.extend_from_slice(block_hash.as_ref());
190    res.extend_from_slice(&shard_id.to_le_bytes());
191    res
192}
193
194pub fn get_block_shard_id_rev(
195    key: &[u8],
196) -> Result<(CryptoHash, ShardId), Box<dyn std::error::Error + Send + Sync>> {
197    if key.len() != 40 {
198        return Err(
199            std::io::Error::new(std::io::ErrorKind::InvalidInput, "Invalid key length").into()
200        );
201    }
202    let (block_hash_bytes, shard_id_bytes) = key.split_at(32);
203    let block_hash = CryptoHash::try_from(block_hash_bytes)?;
204    let shard_id = ShardId::from_le_bytes(shard_id_bytes.try_into()?);
205    Ok((block_hash, shard_id))
206}
207
208pub fn get_outcome_id_block_hash(outcome_id: &CryptoHash, block_hash: &CryptoHash) -> Vec<u8> {
209    let mut res = Vec::with_capacity(64);
210    res.extend_from_slice(outcome_id.as_ref());
211    res.extend_from_slice(block_hash.as_ref());
212    res
213}
214
215pub fn get_outcome_id_block_hash_rev(key: &[u8]) -> std::io::Result<(CryptoHash, CryptoHash)> {
216    if key.len() != 64 {
217        return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid key length"));
218    }
219    let outcome_id = CryptoHash::try_from(&key[..32]).unwrap();
220    let block_hash = CryptoHash::try_from(&key[32..]).unwrap();
221    Ok((outcome_id, block_hash))
222}
223
224/// Creates a new Receipt ID from a given signed transaction and a block hash.
225/// This method is backward compatible, so it takes the current protocol version.
226pub fn create_receipt_id_from_transaction(
227    protocol_version: ProtocolVersion,
228    signed_transaction: &SignedTransaction,
229    prev_block_hash: &CryptoHash,
230    block_hash: &CryptoHash,
231) -> CryptoHash {
232    create_hash_upgradable(
233        protocol_version,
234        &signed_transaction.get_hash(),
235        prev_block_hash,
236        block_hash,
237        0,
238    )
239}
240
241/// Creates a new Receipt ID from a given receipt, a block hash and a new receipt index.
242/// This method is backward compatible, so it takes the current protocol version.
243pub fn create_receipt_id_from_receipt(
244    protocol_version: ProtocolVersion,
245    receipt: &Receipt,
246    prev_block_hash: &CryptoHash,
247    block_hash: &CryptoHash,
248    receipt_index: usize,
249) -> CryptoHash {
250    create_hash_upgradable(
251        protocol_version,
252        &receipt.receipt_id,
253        prev_block_hash,
254        block_hash,
255        receipt_index as u64,
256    )
257}
258
259/// Creates a new action_hash from a given receipt, a block hash and an action index.
260/// This method is backward compatible, so it takes the current protocol version.
261pub fn create_action_hash(
262    protocol_version: ProtocolVersion,
263    receipt: &Receipt,
264    prev_block_hash: &CryptoHash,
265    block_hash: &CryptoHash,
266    action_index: usize,
267) -> CryptoHash {
268    // Action hash uses the same input as a new receipt ID, so to avoid hash conflicts we use the
269    // salt starting from the `u64` going backward.
270    let salt = u64::MAX.wrapping_sub(action_index as u64);
271    create_hash_upgradable(protocol_version, &receipt.receipt_id, prev_block_hash, block_hash, salt)
272}
273
274/// Creates a new `data_id` from a given action hash, a block hash and a data index.
275/// This method is backward compatible, so it takes the current protocol version.
276pub fn create_data_id(
277    protocol_version: ProtocolVersion,
278    action_hash: &CryptoHash,
279    prev_block_hash: &CryptoHash,
280    block_hash: &CryptoHash,
281    data_index: usize,
282) -> CryptoHash {
283    create_hash_upgradable(
284        protocol_version,
285        action_hash,
286        prev_block_hash,
287        block_hash,
288        data_index as u64,
289    )
290}
291
292/// Creates a unique random seed to be provided to `VMContext` from a give `action_hash` and
293/// a given `random_seed`.
294/// This method is backward compatible, so it takes the current protocol version.
295pub fn create_random_seed(
296    protocol_version: ProtocolVersion,
297    action_hash: CryptoHash,
298    random_seed: CryptoHash,
299) -> Vec<u8> {
300    let res = if protocol_version < CORRECT_RANDOM_VALUE_PROTOCOL_VERSION {
301        action_hash
302    } else if protocol_version < CREATE_HASH_PROTOCOL_VERSION {
303        random_seed
304    } else {
305        // Generates random seed from random_seed and action_hash.
306        // Since every action hash is unique, the seed will be unique per receipt and even
307        // per action within a receipt.
308        const BYTES_LEN: usize = size_of::<CryptoHash>() + size_of::<CryptoHash>();
309        let mut bytes: Vec<u8> = Vec::with_capacity(BYTES_LEN);
310        bytes.extend_from_slice(action_hash.as_ref());
311        bytes.extend_from_slice(random_seed.as_ref());
312        hash(&bytes)
313    };
314    res.as_ref().to_vec()
315}
316
317/// Creates a new CryptoHash ID based on the protocol version.
318/// Before `CREATE_HASH_PROTOCOL_VERSION` it uses `create_nonce_with_nonce` with
319/// just `base` and `salt`. But after `CREATE_HASH_PROTOCOL_VERSION` it uses
320/// `extra_hash` in addition to the `base` and `salt`.
321/// E.g. this `extra_hash` can be a block hash to distinguish receipts between forks.
322fn create_hash_upgradable(
323    protocol_version: ProtocolVersion,
324    base: &CryptoHash,
325    extra_hash_old: &CryptoHash,
326    extra_hash: &CryptoHash,
327    salt: u64,
328) -> CryptoHash {
329    if protocol_version < CREATE_HASH_PROTOCOL_VERSION {
330        create_nonce_with_nonce(base, salt)
331    } else {
332        const BYTES_LEN: usize =
333            size_of::<CryptoHash>() + size_of::<CryptoHash>() + size_of::<u64>();
334        let mut bytes: Vec<u8> = Vec::with_capacity(BYTES_LEN);
335        bytes.extend_from_slice(base.as_ref());
336        let extra_hash_used =
337            if protocol_version < CREATE_RECEIPT_ID_SWITCH_TO_CURRENT_BLOCK_VERSION {
338                extra_hash_old
339            } else {
340                extra_hash
341            };
342        bytes.extend_from_slice(extra_hash_used.as_ref());
343        bytes.extend(index_to_bytes(salt));
344        hash(&bytes)
345    }
346}
347
348/// Deprecated. Please use `create_hash_upgradable`
349fn create_nonce_with_nonce(base: &CryptoHash, salt: u64) -> CryptoHash {
350    let mut nonce: Vec<u8> = base.as_ref().to_owned();
351    nonce.extend(index_to_bytes(salt));
352    hash(&nonce)
353}
354
355pub fn index_to_bytes(index: u64) -> [u8; 8] {
356    index.to_le_bytes()
357}
358
359/// A wrapper around Option<T> that provides native Display trait.
360/// Simplifies propagating automatic Display trait on parent structs.
361pub struct DisplayOption<T>(pub Option<T>);
362
363impl<T: fmt::Display> fmt::Display for DisplayOption<T> {
364    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
365        match self.0 {
366            Some(ref v) => write!(f, "Some({})", v),
367            None => write!(f, "None"),
368        }
369    }
370}
371
372impl<T> DisplayOption<T> {
373    pub fn into(self) -> Option<T> {
374        self.0
375    }
376}
377
378impl<T> AsRef<Option<T>> for DisplayOption<T> {
379    fn as_ref(&self) -> &Option<T> {
380        &self.0
381    }
382}
383
384impl<T: fmt::Display> From<Option<T>> for DisplayOption<T> {
385    fn from(o: Option<T>) -> Self {
386        DisplayOption(o)
387    }
388}
389
390/// Macro to either return value if the result is Ok, or exit function logging error.
391#[macro_export]
392macro_rules! unwrap_or_return {
393    ($obj: expr, $ret: expr) => {
394        match $obj {
395            Ok(value) => value,
396            Err(err) => {
397                tracing::error!(target: "client", "Unwrap error: {}", err);
398                return $ret;
399            }
400        }
401    };
402    ($obj: expr) => {
403        match $obj {
404            Ok(value) => value,
405            Err(err) => {
406                tracing::error!(target: "client", "Unwrap error: {}", err);
407                return;
408            }
409        }
410    };
411}
412
413/// Converts timestamp in ns into DateTime UTC time.
414pub fn from_timestamp(timestamp: u64) -> DateTime<chrono::Utc> {
415    let secs = (timestamp / NS_IN_SECOND) as i64;
416    let nsecs = (timestamp % NS_IN_SECOND) as u32;
417    DateTime::from_timestamp(secs, nsecs).unwrap()
418}
419
420/// Converts DateTime UTC time into timestamp in ns.
421pub fn to_timestamp(time: DateTime<chrono::Utc>) -> u64 {
422    // The unwrap will be safe for all dates between 1678 and 2261.
423    time.timestamp_nanos_opt().unwrap() as u64
424}
425
426/// Compute number of seats per shard for given total number of seats and number of shards.
427pub fn get_num_seats_per_shard(num_shards: NumShards, num_seats: NumSeats) -> Vec<NumSeats> {
428    (0..num_shards)
429        .map(|shard_id| {
430            let remainder =
431                num_seats.checked_rem(num_shards).expect("num_shards ≠ 0 is guaranteed here");
432            let quotient =
433                num_seats.checked_div(num_shards).expect("num_shards ≠ 0 is guaranteed here");
434            let num = quotient
435                .checked_add(if shard_id < remainder { 1 } else { 0 })
436                .expect("overflow is impossible here");
437            max(num, 1)
438        })
439        .collect()
440}
441
442/// Generate random string of given length
443pub fn generate_random_string(len: usize) -> String {
444    let bytes = thread_rng().sample_iter(&Alphanumeric).take(len).collect();
445    String::from_utf8(bytes).unwrap()
446}
447
448pub struct Serializable<'a, T>(&'a T);
449
450impl<'a, T> fmt::Display for Serializable<'a, T>
451where
452    T: serde::Serialize,
453{
454    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
455        write!(f, "{:?}", serde_json::to_string(&self.0).unwrap())
456    }
457}
458
459/// Wrap an object that implements Serialize into another object
460/// that implements Display. When used display in this object
461/// it shows its json representation. It is used to display complex
462/// objects using tracing.
463///
464/// tracing::debug!(target: "diagnostic", value=%ser(&object));
465pub fn ser<T>(object: &T) -> Serializable<'_, T>
466where
467    T: serde::Serialize,
468{
469    Serializable(object)
470}
471
472/// From `unc-account-id` version `0.7.2`, `is_valid` returns true for ETH-implicit accounts.
473/// This function is a wrapper for `is_valid` method so that we can easily differentiate its behavior
474/// based on whether ETH-implicit accounts are enabled.
475pub fn account_is_valid(account_id: &AccountId, eth_accounts_enabled: bool) -> bool {
476    if eth_accounts_enabled {
477        account_id.get_account_type().is_valid()
478    } else {
479        account_id.get_account_type() == AccountType::UtilityAccount
480    }
481}
482
483/// Returns hex-encoded copy of the public key.
484/// This is a unc-implicit account ID which can be controlled by the corresponding ED25519 private key.
485pub fn derive_unc_account_id(public_key: &ED25519PublicKey) -> AccountId {
486    hex::encode(public_key).parse().unwrap()
487}
488
489/// Returns '0x' + keccak256(public_key)[12:32].hex().
490/// This is an ETH-implicit account ID which can be controlled by the corresponding Secp256K1 private key.
491pub fn derive_eth_implicit_account_id(public_key: &Secp256K1PublicKey) -> AccountId {
492    use sha3::Digest;
493    let pk_hash = sha3::Keccak256::digest(&public_key);
494    format!("0x{}", hex::encode(&pk_hash[12..32])).parse().unwrap()
495}
496
497#[cfg(test)]
498mod tests {
499    use super::*;
500    use unc_crypto::{KeyType, PublicKey};
501
502    #[test]
503    fn test_derive_unc_account_id() {
504        let public_key = PublicKey::from_seed(KeyType::ED25519, "test");
505        let expected: AccountId =
506            "bb4dc639b212e075a751685b26bdcea5920a504181ff2910e8549742127092a0".parse().unwrap();
507        let account_id = derive_unc_account_id(public_key.unwrap_as_ed25519());
508        assert_eq!(account_id, expected);
509    }
510
511    #[test]
512    fn test_derive_eth_implicit_account_id() {
513        let public_key = PublicKey::from_seed(KeyType::SECP256K1, "test");
514        let expected: AccountId = "0x96791e923f8cf697ad9c3290f2c9059f0231b24c".parse().unwrap();
515        let account_id = derive_eth_implicit_account_id(public_key.unwrap_as_secp256k1());
516        assert_eq!(account_id, expected);
517    }
518
519    #[test]
520    fn test_num_chunk_producers() {
521        for num_seats in 1..50 {
522            for num_shards in 1..50 {
523                let assignment = get_num_seats_per_shard(num_shards, num_seats);
524                assert_eq!(assignment.iter().sum::<u64>(), max(num_seats, num_shards));
525            }
526        }
527    }
528
529    #[test]
530    fn test_create_hash_upgradable() {
531        let base = hash(b"atata");
532        let extra_base = hash(b"hohoho");
533        let other_extra_base = hash(b"banana");
534        let salt = 3;
535        assert_eq!(
536            create_nonce_with_nonce(&base, salt),
537            create_hash_upgradable(
538                CREATE_HASH_PROTOCOL_VERSION - 1,
539                &base,
540                &extra_base,
541                &extra_base,
542                salt,
543            )
544        );
545        assert_ne!(
546            create_nonce_with_nonce(&base, salt),
547            create_hash_upgradable(
548                CREATE_HASH_PROTOCOL_VERSION,
549                &base,
550                &extra_base,
551                &extra_base,
552                salt,
553            )
554        );
555        assert_ne!(
556            create_hash_upgradable(
557                CREATE_HASH_PROTOCOL_VERSION,
558                &base,
559                &extra_base,
560                &extra_base,
561                salt,
562            ),
563            create_hash_upgradable(
564                CREATE_HASH_PROTOCOL_VERSION,
565                &base,
566                &other_extra_base,
567                &other_extra_base,
568                salt,
569            )
570        );
571        assert_ne!(
572            create_hash_upgradable(
573                CREATE_RECEIPT_ID_SWITCH_TO_CURRENT_BLOCK_VERSION - 1,
574                &base,
575                &extra_base,
576                &other_extra_base,
577                salt,
578            ),
579            create_hash_upgradable(
580                CREATE_RECEIPT_ID_SWITCH_TO_CURRENT_BLOCK_VERSION,
581                &base,
582                &extra_base,
583                &other_extra_base,
584                salt,
585            )
586        );
587        assert_eq!(
588            create_hash_upgradable(
589                CREATE_RECEIPT_ID_SWITCH_TO_CURRENT_BLOCK_VERSION,
590                &base,
591                &extra_base,
592                &other_extra_base,
593                salt,
594            ),
595            create_hash_upgradable(
596                CREATE_RECEIPT_ID_SWITCH_TO_CURRENT_BLOCK_VERSION,
597                &base,
598                &other_extra_base,
599                &other_extra_base,
600                salt
601            )
602        );
603    }
604}