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
28const NS_IN_SECOND: u64 = 1_000_000_000;
30
31#[derive(Clone)]
60pub struct MaybeValidated<T> {
61 validated: std::cell::Cell<bool>,
62 payload: T,
63}
64
65impl<T> MaybeValidated<T> {
66 pub fn from_validated(payload: T) -> Self {
80 Self { validated: std::cell::Cell::new(true), payload }
81 }
82
83 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 pub fn mark_as_valid(&self) {
118 self.validated.set(true);
119 }
120
121 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 pub fn as_ref(&self) -> MaybeValidated<&T> {
153 MaybeValidated { validated: self.validated.clone(), payload: &self.payload }
154 }
155
156 pub fn is_validated(&self) -> bool {
158 self.validated.get()
159 }
160
161 pub fn into_inner(self) -> T {
163 self.payload
164 }
165
166 pub fn get_inner(&self) -> &T {
168 &self.payload
169 }
170}
171
172impl<T> From<T> for MaybeValidated<T> {
173 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
224pub 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
241pub 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
259pub 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 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
274pub 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
292pub 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 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
317fn 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
348fn 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
359pub 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_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
413pub 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
420pub fn to_timestamp(time: DateTime<chrono::Utc>) -> u64 {
422 time.timestamp_nanos_opt().unwrap() as u64
424}
425
426pub 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
442pub 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
459pub fn ser<T>(object: &T) -> Serializable<'_, T>
466where
467 T: serde::Serialize,
468{
469 Serializable(object)
470}
471
472pub 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
483pub fn derive_unc_account_id(public_key: &ED25519PublicKey) -> AccountId {
486 hex::encode(public_key).parse().unwrap()
487}
488
489pub 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}