#[cfg(feature = "clock")]
use crate::block::BlockHeader;
use crate::hash::{CryptoHash, hash};
use crate::types::{ChunkExecutionResultHash, NumSeats, NumShards, ShardId};
use chrono;
use chrono::DateTime;
use near_crypto::{ED25519PublicKey, Secp256K1PublicKey};
use near_primitives_core::account::id::{AccountId, AccountType};
use near_primitives_core::deterministic_account_id::DeterministicAccountStateInit;
use near_primitives_core::types::BlockHeight;
use serde;
use std::cmp::max;
use std::convert::AsRef;
use std::fmt;
use std::mem::size_of;
use std::ops::Deref;
pub mod compression;
pub mod io;
pub mod min_heap;
const NS_IN_SECOND: u64 = 1_000_000_000;
#[derive(Clone)]
pub struct MaybeValidated<T> {
validated: std::cell::Cell<bool>,
payload: T,
}
impl<T> MaybeValidated<T> {
pub fn from_validated(payload: T) -> Self {
Self { validated: std::cell::Cell::new(true), payload }
}
pub fn validate_with<E, F: FnOnce(&T) -> Result<bool, E>>(
&self,
validator: F,
) -> Result<bool, E> {
if self.validated.get() {
Ok(true)
} else {
let res = validator(&self.payload);
self.validated.set(*res.as_ref().unwrap_or(&false));
res
}
}
pub fn map<U, F: FnOnce(T) -> U>(self, validator: F) -> MaybeValidated<U> {
MaybeValidated { validated: self.validated, payload: validator(self.payload) }
}
pub fn as_ref(&self) -> MaybeValidated<&T> {
MaybeValidated { validated: self.validated.clone(), payload: &self.payload }
}
pub fn is_validated(&self) -> bool {
self.validated.get()
}
pub fn into_inner(self) -> T {
self.payload
}
pub fn get_inner(&self) -> &T {
&self.payload
}
}
impl<T> From<T> for MaybeValidated<T> {
fn from(payload: T) -> Self {
Self { validated: std::cell::Cell::new(false), payload }
}
}
impl<T: Sized> Deref for MaybeValidated<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.payload
}
}
pub fn get_block_shard_id(block_hash: &CryptoHash, shard_id: ShardId) -> Vec<u8> {
let mut res = Vec::with_capacity(40);
res.extend_from_slice(block_hash.as_ref());
res.extend_from_slice(&shard_id.to_le_bytes());
res
}
pub fn get_witnesses_key(block_hash: &CryptoHash, shard_id: ShardId) -> Vec<u8> {
get_block_shard_id(block_hash, shard_id)
}
pub fn get_receipt_proof_key(
block_hash: &CryptoHash,
from_shard_id: ShardId,
to_shard_id: ShardId,
) -> Vec<u8> {
const BYTES_LEN: usize = size_of::<CryptoHash>() + size_of::<ShardId>() + size_of::<ShardId>();
let mut res = Vec::with_capacity(BYTES_LEN);
res.extend_from_slice(block_hash.as_ref());
res.extend_from_slice(&to_shard_id.to_le_bytes());
res.extend_from_slice(&from_shard_id.to_le_bytes());
res
}
pub fn get_receipt_proof_target_shard_prefix(
block_hash: &CryptoHash,
to_shard_id: ShardId,
) -> Vec<u8> {
const BYTES_LEN: usize = size_of::<CryptoHash>() + size_of::<ShardId>();
let mut res = Vec::with_capacity(BYTES_LEN);
res.extend_from_slice(block_hash.as_ref());
res.extend_from_slice(&to_shard_id.to_le_bytes());
res
}
pub fn get_endorsements_key_prefix(block_hash: &CryptoHash, shard_id: ShardId) -> Vec<u8> {
get_block_shard_id(block_hash, shard_id)
}
pub fn get_endorsements_key(
block_hash: &CryptoHash,
shard_id: ShardId,
account_id: &AccountId,
) -> Vec<u8> {
let account_id = account_id.as_bytes();
let length: usize = size_of::<CryptoHash>() + size_of::<ShardId>() + account_id.len();
let mut res = Vec::with_capacity(length);
res.extend_from_slice(block_hash.as_ref());
res.extend_from_slice(&shard_id.to_le_bytes());
res.extend_from_slice(account_id);
res
}
pub fn get_execution_results_key(block_hash: &CryptoHash, shard_id: ShardId) -> Vec<u8> {
get_block_shard_id(block_hash, shard_id)
}
pub fn get_uncertified_execution_results_key(hash: &ChunkExecutionResultHash) -> Vec<u8> {
hash.0.as_ref().to_vec()
}
pub fn get_block_shard_id_rev(
key: &[u8],
) -> Result<(CryptoHash, ShardId), Box<dyn std::error::Error + Send + Sync>> {
if key.len() != 40 {
return Err(
std::io::Error::new(std::io::ErrorKind::InvalidInput, "Invalid key length").into()
);
}
let (block_hash_bytes, shard_id_bytes) = key.split_at(32);
let block_hash = CryptoHash::try_from(block_hash_bytes)?;
let shard_id = ShardId::from_le_bytes(shard_id_bytes.try_into()?);
Ok((block_hash, shard_id))
}
pub fn get_outcome_id_block_hash(outcome_id: &CryptoHash, block_hash: &CryptoHash) -> Vec<u8> {
let mut res = Vec::with_capacity(64);
res.extend_from_slice(outcome_id.as_ref());
res.extend_from_slice(block_hash.as_ref());
res
}
pub fn get_outcome_id_block_hash_rev(key: &[u8]) -> std::io::Result<(CryptoHash, CryptoHash)> {
if key.len() != 64 {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "Invalid key length"));
}
let outcome_id = CryptoHash::try_from(&key[..32]).unwrap();
let block_hash = CryptoHash::try_from(&key[32..]).unwrap();
Ok((outcome_id, block_hash))
}
pub fn create_receipt_id_from_transaction(
tx_hash: &CryptoHash,
block_height: BlockHeight,
) -> CryptoHash {
create_hash_index(tx_hash, block_height, 0)
}
pub fn create_receipt_id_from_receipt_id(
receipt_id: &CryptoHash,
block_height: BlockHeight,
receipt_index: usize,
) -> CryptoHash {
create_hash_index(receipt_id, block_height, receipt_index as u64)
}
pub fn create_action_hash_from_receipt_id(
receipt_id: &CryptoHash,
block_height: BlockHeight,
action_index: usize,
) -> CryptoHash {
let salt = u64::MAX.wrapping_sub(action_index as u64);
create_hash_index(receipt_id, block_height, salt)
}
pub fn create_receipt_id_from_action_hash(
action_hash: &CryptoHash,
block_height: BlockHeight,
receipt_index: u64,
) -> CryptoHash {
create_hash_index(action_hash, block_height, receipt_index)
}
pub fn create_random_seed(action_hash: CryptoHash, random_seed: CryptoHash) -> Vec<u8> {
const BYTES_LEN: usize = size_of::<CryptoHash>() + size_of::<CryptoHash>();
let mut bytes: Vec<u8> = Vec::with_capacity(BYTES_LEN);
bytes.extend_from_slice(action_hash.as_ref());
bytes.extend_from_slice(random_seed.as_ref());
let res = hash(&bytes);
res.as_ref().to_vec()
}
fn create_hash_index(base: &CryptoHash, block_height: BlockHeight, salt: u64) -> CryptoHash {
const BYTES_LEN: usize = size_of::<CryptoHash>() + size_of::<CryptoHash>() + size_of::<u64>();
let mut bytes: Vec<u8> = Vec::with_capacity(BYTES_LEN);
bytes.extend_from_slice(base.as_ref());
bytes.extend_from_slice(block_height.to_le_bytes().as_ref());
bytes.extend(index_to_bytes(salt));
hash(&bytes)
}
pub fn index_to_bytes(index: u64) -> [u8; 8] {
index.to_le_bytes()
}
pub struct DisplayOption<T>(pub Option<T>);
impl<T: fmt::Display> fmt::Display for DisplayOption<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0 {
Some(ref v) => write!(f, "Some({})", v),
None => write!(f, "None"),
}
}
}
impl<T> DisplayOption<T> {
pub fn into(self) -> Option<T> {
self.0
}
}
impl<T> AsRef<Option<T>> for DisplayOption<T> {
fn as_ref(&self) -> &Option<T> {
&self.0
}
}
impl<T: fmt::Display> From<Option<T>> for DisplayOption<T> {
fn from(o: Option<T>) -> Self {
DisplayOption(o)
}
}
#[macro_export]
macro_rules! unwrap_or_return {
($obj: expr, $ret: expr) => {
match $obj {
Ok(value) => value,
Err(err) => {
tracing::error!(target: "near", ?err, "unwrap error");
return $ret;
}
}
};
($obj: expr) => {
match $obj {
Ok(value) => value,
Err(err) => {
tracing::error!(target: "near", ?err, "unwrap error");
return;
}
}
};
}
pub fn from_timestamp(timestamp: u64) -> DateTime<chrono::Utc> {
let secs = (timestamp / NS_IN_SECOND) as i64;
let nsecs = (timestamp % NS_IN_SECOND) as u32;
DateTime::from_timestamp(secs, nsecs).unwrap()
}
pub fn to_timestamp(time: DateTime<chrono::Utc>) -> u64 {
time.timestamp_nanos_opt().unwrap() as u64
}
pub fn get_num_seats_per_shard(num_shards: NumShards, num_seats: NumSeats) -> Vec<NumSeats> {
(0..num_shards)
.map(|shard_id| {
let remainder =
num_seats.checked_rem(num_shards).expect("num_shards ≠ 0 is guaranteed here");
let quotient =
num_seats.checked_div(num_shards).expect("num_shards ≠ 0 is guaranteed here");
let num = quotient
.checked_add(if shard_id < remainder { 1 } else { 0 })
.expect("overflow is impossible here");
max(num, 1)
})
.collect()
}
#[cfg(feature = "rand")]
pub fn generate_random_string(len: usize) -> String {
use rand::distributions::Alphanumeric;
use rand::{Rng, thread_rng};
let bytes = thread_rng().sample_iter(&Alphanumeric).take(len).collect();
String::from_utf8(bytes).unwrap()
}
pub struct Serializable<'a, T>(&'a T);
impl<'a, T> fmt::Display for Serializable<'a, T>
where
T: serde::Serialize,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", serde_json::to_string(&self.0).unwrap())
}
}
pub fn ser<T>(object: &T) -> Serializable<'_, T>
where
T: serde::Serialize,
{
Serializable(object)
}
pub fn account_is_implicit(account_id: &AccountId, eth_implicit_accounts_enabled: bool) -> bool {
if eth_implicit_accounts_enabled {
account_id.get_account_type().is_implicit()
} else {
account_id.get_account_type() == AccountType::NearImplicitAccount
}
}
pub fn derive_near_implicit_account_id(public_key: &ED25519PublicKey) -> AccountId {
hex::encode(public_key).parse().unwrap()
}
pub fn derive_eth_implicit_account_id(public_key: &Secp256K1PublicKey) -> AccountId {
use sha3::Digest;
let pk_hash = sha3::Keccak256::digest(&public_key);
format!("0x{}", hex::encode(&pk_hash[12..32])).parse().unwrap()
}
pub fn derive_near_deterministic_account_id(
state_init: &DeterministicAccountStateInit,
) -> AccountId {
use sha3::Digest;
let data = borsh::to_vec(&state_init).expect("borsh must not fail");
let hash = sha3::Keccak256::digest(&data);
format!("0s{}", hex::encode(&hash[12..32])).parse().unwrap()
}
#[cfg(feature = "clock")]
pub fn get_block_metadata(
prev_block_header: &BlockHeader,
signer: &crate::validator_signer::ValidatorSigner,
now: u64,
sandbox_delta_time: Option<near_time::Duration>,
) -> (u64, near_crypto::vrf::Value, near_crypto::vrf::Proof, CryptoHash) {
#[cfg(feature = "sandbox")]
let now = now + sandbox_delta_time.unwrap().whole_nanoseconds() as u64;
#[cfg(not(feature = "sandbox"))]
debug_assert!(sandbox_delta_time.is_none());
let time = if now <= prev_block_header.raw_timestamp() {
prev_block_header.raw_timestamp() + 1
} else {
now
};
let (vrf_value, vrf_proof) =
signer.compute_vrf_with_proof(prev_block_header.random_value().as_ref());
let random_value = hash(vrf_value.0.as_ref());
(time, vrf_value, vrf_proof, random_value)
}
#[cfg(test)]
mod tests {
use super::*;
use near_crypto::{KeyType, PublicKey};
#[test]
fn test_derive_near_implicit_account_id() {
let public_key = PublicKey::from_seed(KeyType::ED25519, "test");
let expected: AccountId =
"bb4dc639b212e075a751685b26bdcea5920a504181ff2910e8549742127092a0".parse().unwrap();
let account_id = derive_near_implicit_account_id(public_key.unwrap_as_ed25519());
assert_eq!(account_id, expected);
}
#[test]
fn test_derive_eth_implicit_account_id() {
let public_key = PublicKey::from_seed(KeyType::SECP256K1, "test");
let expected: AccountId = "0x96791e923f8cf697ad9c3290f2c9059f0231b24c".parse().unwrap();
let account_id = derive_eth_implicit_account_id(public_key.unwrap_as_secp256k1());
assert_eq!(account_id, expected);
}
#[test]
fn test_num_chunk_producers() {
for num_seats in 1..50 {
for num_shards in 1..50 {
let assignment = get_num_seats_per_shard(num_shards, num_seats);
assert_eq!(assignment.iter().sum::<u64>(), max(num_seats, num_shards));
}
}
}
#[test]
fn test_create_hash_index() {
let base = hash(b"atata");
let other_base = hash(b"banana");
let block_height: BlockHeight = 123_456_789;
let other_block_height: BlockHeight = 123_123_123;
let salt = 3;
assert_ne!(
create_hash_index(&base, block_height, salt),
create_hash_index(&other_base, block_height, salt)
);
assert_ne!(
create_hash_index(&base, block_height, salt),
create_hash_index(&base, other_block_height, salt)
);
assert_ne!(
create_hash_index(&base, block_height, salt),
create_hash_index(&base, block_height, salt + 1)
);
}
}