diff --git a/Cargo.lock b/Cargo.lock index 67efe3cba66..9cfd0352b1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2363,7 +2363,7 @@ dependencies = [ [[package]] name = "near-runtime-fees" -version = "0.8.0" +version = "0.9.0" dependencies = [ "num-rational", "serde", @@ -2371,7 +2371,7 @@ dependencies = [ [[package]] name = "near-runtime-standalone" -version = "0.1.0" +version = "0.9.0" dependencies = [ "near-crypto", "near-pool", @@ -2417,7 +2417,7 @@ dependencies = [ [[package]] name = "near-vm-errors" -version = "0.8.0" +version = "0.9.0" dependencies = [ "borsh 0.6.1", "near-rpc-error-macro", @@ -2426,7 +2426,7 @@ dependencies = [ [[package]] name = "near-vm-logic" -version = "0.8.0" +version = "0.9.0" dependencies = [ "base64", "bs58", @@ -2441,7 +2441,7 @@ dependencies = [ [[package]] name = "near-vm-runner" -version = "0.8.0" +version = "0.9.0" dependencies = [ "assert_matches", "bencher", @@ -2458,7 +2458,7 @@ dependencies = [ [[package]] name = "near-vm-runner-standalone" -version = "0.8.0" +version = "0.9.0" dependencies = [ "base64", "clap", @@ -2560,7 +2560,7 @@ dependencies = [ [[package]] name = "node-runtime" -version = "0.0.1" +version = "0.9.0" dependencies = [ "assert_matches", "base64", @@ -3243,7 +3243,7 @@ dependencies = [ [[package]] name = "runtime-params-estimator" -version = "0.1.0" +version = "0.9.0" dependencies = [ "borsh 0.6.1", "clap", diff --git a/chain/chain/src/error.rs b/chain/chain/src/error.rs index d4cc887c063..b94ddbaf5fd 100644 --- a/chain/chain/src/error.rs +++ b/chain/chain/src/error.rs @@ -5,8 +5,9 @@ use chrono::{DateTime, Utc}; use failure::{Backtrace, Context, Fail}; use near_primitives::challenge::{ChunkProofs, ChunkState}; -use near_primitives::errors::StorageError; +use near_primitives::errors::{EpochError, StorageError}; use near_primitives::hash::CryptoHash; +use near_primitives::serialize::to_base; use near_primitives::sharding::{ChunkHash, ShardChunkHeader}; use near_primitives::types::ShardId; @@ -284,3 +285,14 @@ impl From for Error { } impl std::error::Error for Error {} + +impl From for Error { + fn from(error: EpochError) -> Self { + match error { + EpochError::EpochOutOfBounds => ErrorKind::EpochOutOfBounds, + EpochError::MissingBlock(h) => ErrorKind::DBNotFoundErr(to_base(&h)), + err => ErrorKind::ValidatorError(err.to_string()), + } + .into() + } +} diff --git a/chain/epoch_manager/src/lib.rs b/chain/epoch_manager/src/lib.rs index 17826e82d34..561baeed852 100644 --- a/chain/epoch_manager/src/lib.rs +++ b/chain/epoch_manager/src/lib.rs @@ -6,6 +6,7 @@ use cached::{Cached, SizedCache}; use log::{debug, warn}; use primitive_types::U256; +use near_primitives::errors::EpochError; use near_primitives::hash::CryptoHash; use near_primitives::types::{ AccountId, ApprovalStake, Balance, BlockChunkValidatorStats, BlockHeight, EpochId, ShardId, @@ -18,9 +19,8 @@ use near_store::{ColBlockInfo, ColEpochInfo, ColEpochStart, Store, StoreUpdate}; use crate::proposals::proposals_to_epoch_info; pub use crate::reward_calculator::RewardCalculator; -use crate::types::EpochError::EpochOutOfBounds; use crate::types::EpochSummary; -pub use crate::types::{BlockInfo, EpochConfig, EpochError, EpochInfo, RngSeed, SlashState}; +pub use crate::types::{BlockInfo, EpochConfig, EpochInfo, RngSeed, SlashState}; mod proposals; mod reward_calculator; @@ -310,8 +310,8 @@ impl EpochManager { minted_amount, ) { Ok(next_next_epoch_info) => next_next_epoch_info, - Err(EpochError::ThresholdError(amount, num_seats)) => { - warn!(target: "epoch_manager", "Not enough stake for required number of seats (all validators tried to unstake?): amount = {} for {}", amount, num_seats); + Err(EpochError::ThresholdError { stake_sum, num_seats }) => { + warn!(target: "epoch_manager", "Not enough stake for required number of seats (all validators tried to unstake?): amount = {} for {}", stake_sum, num_seats); let mut epoch_info = next_epoch_info.clone(); epoch_info.epoch_height += 1; epoch_info @@ -874,7 +874,7 @@ impl EpochManager { (Ok(index1), Ok(index2)) => Ok(index1.cmp(&index2)), (Ok(_), Err(_)) => self.get_epoch_info(other_epoch_id).map(|_| Ordering::Less), (Err(_), Ok(_)) => self.get_epoch_info(epoch_id).map(|_| Ordering::Greater), - (Err(_), Err(_)) => Err(EpochOutOfBounds), + (Err(_), Err(_)) => Err(EpochError::EpochOutOfBounds), } } } diff --git a/chain/epoch_manager/src/proposals.rs b/chain/epoch_manager/src/proposals.rs index db20cf884b0..709bbf6109f 100644 --- a/chain/epoch_manager/src/proposals.rs +++ b/chain/epoch_manager/src/proposals.rs @@ -1,19 +1,20 @@ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::iter; +use near_primitives::errors::EpochError; use near_primitives::types::{ AccountId, Balance, NumSeats, ValidatorId, ValidatorKickoutReason, ValidatorStake, }; -use crate::types::{EpochConfig, EpochError, EpochInfo, RngSeed}; +use crate::types::{EpochConfig, EpochInfo, RngSeed}; /// Find threshold of stake per seat, given provided stakes and required number of seats. fn find_threshold(stakes: &[Balance], num_seats: NumSeats) -> Result { - let stakes_sum: Balance = stakes.iter().sum(); - if stakes_sum < num_seats.into() { - return Err(EpochError::ThresholdError(stakes_sum, num_seats)); + let stake_sum: Balance = stakes.iter().sum(); + if stake_sum < num_seats.into() { + return Err(EpochError::ThresholdError { stake_sum, num_seats }); } - let (mut left, mut right): (Balance, Balance) = (1, stakes_sum + 1); + let (mut left, mut right): (Balance, Balance) = (1, stake_sum + 1); 'outer: loop { if left == right - 1 { break Ok(left); diff --git a/chain/epoch_manager/src/types.rs b/chain/epoch_manager/src/types.rs index 6111c9a7e3b..30e702d542c 100644 --- a/chain/epoch_manager/src/types.rs +++ b/chain/epoch_manager/src/types.rs @@ -1,5 +1,4 @@ use std::collections::{BTreeMap, HashMap}; -use std::{fmt, io}; use borsh::{BorshDeserialize, BorshSerialize}; use num_rational::Rational; @@ -7,7 +6,6 @@ use serde::Serialize; use near_primitives::challenge::SlashedValidator; use near_primitives::hash::CryptoHash; -use near_primitives::serialize::to_base; use near_primitives::types::{ AccountId, Balance, BlockChunkValidatorStats, BlockHeight, BlockHeightDelta, EpochHeight, EpochId, NumSeats, NumShards, ShardId, ValidatorId, ValidatorKickoutReason, ValidatorStake, @@ -190,76 +188,6 @@ impl BlockInfo { } } -#[derive(Eq, PartialEq)] -pub enum EpochError { - /// Error calculating threshold from given stakes for given number of seats. - /// Only should happened if calling code doesn't check for integer value of stake > number of seats. - ThresholdError(Balance, u64), - /// Requesting validators for an epoch that wasn't computed yet. - EpochOutOfBounds, - /// Missing block hash in the storage (means there is some structural issue). - MissingBlock(CryptoHash), - /// Error due to IO (DB read/write, serialization, etc.). - IOErr(String), - /// Given account ID is not a validator in the given epoch ID. - NotAValidator(AccountId, EpochId), -} - -impl std::error::Error for EpochError {} - -impl fmt::Debug for EpochError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - EpochError::ThresholdError(stakes_sum, num_seats) => write!( - f, - "Total stake {} must be higher than the number of seats {}", - stakes_sum, num_seats - ), - EpochError::EpochOutOfBounds => write!(f, "Epoch out of bounds"), - EpochError::MissingBlock(hash) => write!(f, "Missing block {}", hash), - EpochError::IOErr(err) => write!(f, "IO: {}", err), - EpochError::NotAValidator(account_id, epoch_id) => { - write!(f, "{} is not a validator in epoch {:?}", account_id, epoch_id) - } - } - } -} - -impl fmt::Display for EpochError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - EpochError::ThresholdError(stake, num_seats) => { - write!(f, "ThresholdError({}, {})", stake, num_seats) - } - EpochError::EpochOutOfBounds => write!(f, "EpochOutOfBounds"), - EpochError::MissingBlock(hash) => write!(f, "MissingBlock({})", hash), - EpochError::IOErr(err) => write!(f, "IOErr({})", err), - EpochError::NotAValidator(account_id, epoch_id) => { - write!(f, "NotAValidator({}, {:?})", account_id, epoch_id) - } - } - } -} - -impl From for EpochError { - fn from(error: io::Error) -> Self { - EpochError::IOErr(error.to_string()) - } -} - -impl From for near_chain::Error { - fn from(error: EpochError) -> Self { - match error { - EpochError::EpochOutOfBounds => near_chain::ErrorKind::EpochOutOfBounds, - EpochError::MissingBlock(h) => near_chain::ErrorKind::DBNotFoundErr(to_base(&h)), - EpochError::IOErr(err) => near_chain::ErrorKind::IOErr(err), - EpochError::NotAValidator(_, _) => near_chain::ErrorKind::NotAValidator, - err => near_chain::ErrorKind::ValidatorError(err.to_string()), - } - .into() - } -} - pub struct EpochSummary { pub prev_epoch_last_block_hash: CryptoHash, // Proposals from the epoch, only the latest one per account diff --git a/core/chain-configs/src/lib.rs b/core/chain-configs/src/lib.rs index 38e23f74fb4..6b963bf5e85 100644 --- a/core/chain-configs/src/lib.rs +++ b/core/chain-configs/src/lib.rs @@ -7,4 +7,4 @@ pub use genesis_config::{ }; /// Current latest version of the protocol -pub const PROTOCOL_VERSION: u32 = 15; +pub const PROTOCOL_VERSION: u32 = 16; diff --git a/core/primitives/src/errors.rs b/core/primitives/src/errors.rs index 07dd57334c2..eeee1b28c92 100644 --- a/core/primitives/src/errors.rs +++ b/core/primitives/src/errors.rs @@ -1,12 +1,13 @@ use crate::serialize::u128_dec_format; -use crate::types::{AccountId, Balance, Gas, Nonce}; +use crate::types::{AccountId, Balance, EpochId, Gas, Nonce}; use borsh::{BorshDeserialize, BorshSerialize}; use near_crypto::PublicKey; use serde::{Deserialize, Serialize}; -use std::fmt::Display; +use std::fmt::{Debug, Display}; +use crate::hash::CryptoHash; use near_rpc_error_macro::RpcError; -use near_vm_errors::FunctionCallError; +use near_vm_errors::{FunctionCallError, VMLogicError}; /// Error returned in the ExecutionOutcome in case of failure #[derive( @@ -55,6 +56,8 @@ pub enum RuntimeError { BalanceMismatchError(BalanceMismatchError), /// The incoming receipt didn't pass the validation, it's likely a malicious behaviour. ReceiptValidationError(ReceiptValidationError), + /// Error when accessing validator information. Happens inside epoch manager. + ValidatorError(EpochError), } /// Error used by `RuntimeExt`. This error has to be serializable, because it's transferred through @@ -64,6 +67,14 @@ pub enum ExternalError { /// Unexpected error which is typically related to the node storage corruption. /// It's possible the input state is invalid or malicious. StorageError(StorageError), + /// Error when accessing validator information. Happens inside epoch manager. + ValidatorError(EpochError), +} + +impl From for VMLogicError { + fn from(err: ExternalError) -> Self { + VMLogicError::ExternalError(err.try_to_vec().expect("Borsh serialize cannot fail")) + } } /// Internal @@ -368,7 +379,7 @@ impl Display for InvalidTxError { InvalidTxError::SignerDoesNotExist { signer_id } => { write!(f, "Signer {:?} does not exist", signer_id) } - InvalidTxError::InvalidAccessKeyError(access_key_error) => access_key_error.fmt(f), + InvalidTxError::InvalidAccessKeyError(access_key_error) => Display::fmt(&access_key_error, f), InvalidTxError::InvalidNonce { tx_nonce, ak_nonce } => write!( f, "Transaction nonce {} must be larger than nonce of the used access key {}", @@ -634,3 +645,60 @@ impl Display for ActionErrorKind { } } } + +#[derive(Eq, PartialEq, BorshSerialize, BorshDeserialize, Clone)] +pub enum EpochError { + /// Error calculating threshold from given stakes for given number of seats. + /// Only should happened if calling code doesn't check for integer value of stake > number of seats. + ThresholdError { stake_sum: Balance, num_seats: u64 }, + /// Requesting validators for an epoch that wasn't computed yet. + EpochOutOfBounds, + /// Missing block hash in the storage (means there is some structural issue). + MissingBlock(CryptoHash), + /// Error due to IO (DB read/write, serialization, etc.). + IOErr(String), + /// Given account ID is not a validator in the given epoch ID. + NotAValidator(AccountId, EpochId), +} + +impl std::error::Error for EpochError {} + +impl Display for EpochError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + EpochError::ThresholdError { stake_sum, num_seats } => write!( + f, + "Total stake {} must be higher than the number of seats {}", + stake_sum, num_seats + ), + EpochError::EpochOutOfBounds => write!(f, "Epoch out of bounds"), + EpochError::MissingBlock(hash) => write!(f, "Missing block {}", hash), + EpochError::IOErr(err) => write!(f, "IO: {}", err), + EpochError::NotAValidator(account_id, epoch_id) => { + write!(f, "{} is not a validator in epoch {:?}", account_id, epoch_id) + } + } + } +} + +impl Debug for EpochError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + EpochError::ThresholdError { stake_sum, num_seats } => { + write!(f, "ThresholdError({}, {})", stake_sum, num_seats) + } + EpochError::EpochOutOfBounds => write!(f, "EpochOutOfBounds"), + EpochError::MissingBlock(hash) => write!(f, "MissingBlock({})", hash), + EpochError::IOErr(err) => write!(f, "IOErr({})", err), + EpochError::NotAValidator(account_id, epoch_id) => { + write!(f, "NotAValidator({}, {:?})", account_id, epoch_id) + } + } + } +} + +impl From for EpochError { + fn from(error: std::io::Error) -> Self { + EpochError::IOErr(error.to_string()) + } +} diff --git a/core/primitives/src/test_utils.rs b/core/primitives/src/test_utils.rs index ebd006d902a..7cd8a044a4f 100644 --- a/core/primitives/src/test_utils.rs +++ b/core/primitives/src/test_utils.rs @@ -2,6 +2,7 @@ use near_crypto::{EmptySigner, PublicKey, Signature, Signer}; use crate::account::{AccessKey, AccessKeyPermission, Account}; use crate::block::Block; +use crate::errors::EpochError; use crate::hash::CryptoHash; use crate::merkle::PartialMerkleTree; use crate::transaction::{ @@ -9,9 +10,10 @@ use crate::transaction::{ DeployContractAction, FunctionCallAction, SignedTransaction, StakeAction, Transaction, TransferAction, }; -use crate::types::{AccountId, Balance, BlockHeight, EpochId, Gas, Nonce}; +use crate::types::{AccountId, Balance, BlockHeight, EpochId, EpochInfoProvider, Gas, Nonce}; use crate::validator_signer::ValidatorSigner; use num_rational::Rational; +use std::collections::HashMap; pub fn account_new(amount: Balance, code_hash: CryptoHash) -> Account { Account { amount, locked: 0, code_hash, storage_usage: std::mem::size_of::() as u64 } @@ -342,3 +344,33 @@ impl Block { ) } } + +#[derive(Default)] +pub struct MockEpochInfoProvider { + pub validators: HashMap, +} + +impl MockEpochInfoProvider { + pub fn new(validators: impl Iterator) -> Self { + MockEpochInfoProvider { validators: validators.collect() } + } +} + +impl EpochInfoProvider for MockEpochInfoProvider { + fn validator_stake( + &self, + _epoch_id: &EpochId, + _last_block_hash: &CryptoHash, + account_id: &AccountId, + ) -> Result, EpochError> { + Ok(self.validators.get(account_id).cloned()) + } + + fn validator_total_stake( + &self, + _epoch_id: &EpochId, + _last_block_hash: &CryptoHash, + ) -> Result { + Ok(self.validators.values().sum()) + } +} diff --git a/core/primitives/src/types.rs b/core/primitives/src/types.rs index 111d6173fba..e8676838ad9 100644 --- a/core/primitives/src/types.rs +++ b/core/primitives/src/types.rs @@ -6,6 +6,7 @@ use near_crypto::PublicKey; use crate::account::{AccessKey, Account}; use crate::challenge::ChallengesResult; +use crate::errors::EpochError; use crate::hash::CryptoHash; use crate::serialize::u128_dec_format; use crate::trie_key::TrieKey; @@ -537,3 +538,23 @@ pub enum ValidatorKickoutReason { #[derive(PartialEq, Eq, Clone, Debug, BorshSerialize, BorshDeserialize, Serialize)] pub struct StateHeaderKey(pub ShardId, pub CryptoHash); + +/// Provides information about current epoch validators. +/// Used to break dependency between epoch manager and runtime. +pub trait EpochInfoProvider { + /// Get current stake of a validator in the given epoch. + /// If the account is not a validator, returns `None`. + fn validator_stake( + &self, + epoch_id: &EpochId, + last_block_hash: &CryptoHash, + account_id: &AccountId, + ) -> Result, EpochError>; + + /// Get the total stake of the given epoch. + fn validator_total_stake( + &self, + epoch_id: &EpochId, + last_block_hash: &CryptoHash, + ) -> Result; +} diff --git a/neard/res/genesis_config.json b/neard/res/genesis_config.json index ca6a7e4ed4a..67c30033325 100644 --- a/neard/res/genesis_config.json +++ b/neard/res/genesis_config.json @@ -1,6 +1,6 @@ { "config_version": 1, - "protocol_version": 15, + "protocol_version": 16, "genesis_time": "1970-01-01T00:00:00.000000000Z", "chain_id": "sample", "genesis_height": 0, @@ -168,7 +168,9 @@ "touching_trie_node": 5764118571, "promise_and_base": 1473816795, "promise_and_per_promise": 5613432, - "promise_return": 558292404 + "promise_return": 558292404, + "validator_stake_base": 303944908800, + "validator_total_stake_base": 303944908800 }, "grow_mem_cost": 1, "regular_op_cost": 3856371, diff --git a/neard/src/runtime.rs b/neard/src/runtime.rs index ed4b9240587..a454bf53716 100644 --- a/neard/src/runtime.rs +++ b/neard/src/runtime.rs @@ -16,12 +16,12 @@ use near_chain::types::ApplyTransactionResult; use near_chain::{BlockHeader, Error, ErrorKind, RuntimeAdapter}; use near_chain_configs::Genesis; use near_crypto::{PublicKey, Signature}; -use near_epoch_manager::{BlockInfo, EpochConfig, EpochError, EpochManager, RewardCalculator}; +use near_epoch_manager::{BlockInfo, EpochConfig, EpochManager, RewardCalculator}; use near_pool::types::PoolIterator; use near_primitives::account::{AccessKey, Account}; use near_primitives::block::{Approval, ApprovalInner}; use near_primitives::challenge::{ChallengesResult, SlashedValidator}; -use near_primitives::errors::{InvalidTxError, RuntimeError}; +use near_primitives::errors::{EpochError, InvalidTxError, RuntimeError}; use near_primitives::hash::{hash, CryptoHash}; use near_primitives::receipt::Receipt; use near_primitives::sharding::ShardChunkHeader; @@ -29,8 +29,9 @@ use near_primitives::state_record::StateRecord; use near_primitives::transaction::SignedTransaction; use near_primitives::trie_key::trie_key_parsers; use near_primitives::types::{ - AccountId, ApprovalStake, Balance, BlockHeight, EpochHeight, EpochId, Gas, MerkleHash, - NumShards, ShardId, StateChangeCause, StateRoot, StateRootNode, ValidatorStake, ValidatorStats, + AccountId, ApprovalStake, Balance, BlockHeight, EpochHeight, EpochId, EpochInfoProvider, Gas, + MerkleHash, NumShards, ShardId, StateChangeCause, StateRoot, StateRootNode, ValidatorStake, + ValidatorStats, }; use near_primitives::views::{ AccessKeyInfoView, CallResult, EpochValidatorInfo, QueryError, QueryRequest, QueryResponse, @@ -48,6 +49,57 @@ const POISONED_LOCK_ERR: &str = "The lock was poisoned."; const STATE_DUMP_FILE: &str = "state_dump"; const GENESIS_ROOTS_FILE: &str = "genesis_roots"; +/// Wrapper type for epoch manager to get avoid implementing trait for foreign types. +pub struct SafeEpochManager(pub Arc>); + +impl AsRef> for SafeEpochManager { + fn as_ref(&self) -> &RwLock { + self.0.as_ref() + } +} + +impl EpochInfoProvider for SafeEpochManager { + fn validator_stake( + &self, + epoch_id: &EpochId, + last_block_hash: &CryptoHash, + account_id: &AccountId, + ) -> Result, EpochError> { + let mut epoch_manager = self.0.write().expect(POISONED_LOCK_ERR); + let slashed = epoch_manager.get_slashed_validators(last_block_hash)?; + if slashed.contains_key(account_id) { + return Ok(None); + } + let epoch_info = epoch_manager.get_epoch_info(&epoch_id)?; + if let Some(index) = epoch_info.validator_to_index.get(account_id) { + Ok(Some(epoch_info.validators[*index as usize].stake)) + } else { + Ok(None) + } + } + + fn validator_total_stake( + &self, + epoch_id: &EpochId, + last_block_hash: &CryptoHash, + ) -> Result { + let mut epoch_manager = self.0.write().expect(POISONED_LOCK_ERR); + let slashed = epoch_manager.get_slashed_validators(last_block_hash)?.clone(); + let epoch_info = epoch_manager.get_epoch_info(&epoch_id)?; + Ok(epoch_info + .validators + .iter() + .filter_map(|info| { + if slashed.contains_key(&info.account_id) { + None + } else { + Some(info.stake) + } + }) + .sum()) + } +} + /// Defines Nightshade state transition and validator rotation. /// TODO: this possibly should be merged with the runtime cargo or at least reconciled on the interfaces. pub struct NightshadeRuntime { @@ -58,7 +110,7 @@ pub struct NightshadeRuntime { pub tries: ShardTries, trie_viewer: TrieViewer, pub runtime: Runtime, - epoch_manager: Arc>, + epoch_manager: SafeEpochManager, shard_tracker: ShardTracker, } @@ -137,7 +189,7 @@ impl NightshadeRuntime { tries, runtime, trie_viewer, - epoch_manager, + epoch_manager: SafeEpochManager(epoch_manager), shard_tracker, } } @@ -146,7 +198,7 @@ impl NightshadeRuntime { &self, prev_block_hash: &CryptoHash, ) -> Result { - let mut epoch_manager = self.epoch_manager.write().expect(POISONED_LOCK_ERR); + let mut epoch_manager = self.epoch_manager.as_ref().write().expect(POISONED_LOCK_ERR); let epoch_id = epoch_manager.get_epoch_id_from_prev_block(prev_block_hash)?; epoch_manager.get_epoch_info(&epoch_id).map(|info| info.epoch_height).map_err(Error::from) } @@ -232,7 +284,7 @@ impl NightshadeRuntime { challenges_result: &ChallengesResult, ) -> Result { let validator_accounts_update = { - let mut epoch_manager = self.epoch_manager.write().expect(POISONED_LOCK_ERR); + let mut epoch_manager = self.epoch_manager.as_ref().write().expect(POISONED_LOCK_ERR); debug!(target: "runtime", "block height: {}, is next_block_epoch_start {}", block_height, @@ -298,10 +350,12 @@ impl NightshadeRuntime { }; let epoch_height = self.get_epoch_height_from_prev_block(prev_block_hash)?; + let epoch_id = self.get_epoch_id_from_prev_block(prev_block_hash)?; let apply_state = ApplyState { block_index: block_height, - epoch_length: self.genesis.config.epoch_length, + last_block_hash: *prev_block_hash, + epoch_id, epoch_height, gas_price, block_timestamp, @@ -317,18 +371,20 @@ impl NightshadeRuntime { &apply_state, &receipts, &transactions, + &self.epoch_manager, ) .map_err(|e| match e { - RuntimeError::InvalidTxError(_) => ErrorKind::InvalidTransactions, + RuntimeError::InvalidTxError(_) => Error::from(ErrorKind::InvalidTransactions), // TODO(#2152): process gracefully RuntimeError::BalanceMismatchError(e) => panic!("{}", e), // TODO(#2152): process gracefully RuntimeError::UnexpectedIntegerOverflow => { panic!("RuntimeError::UnexpectedIntegerOverflow") } - RuntimeError::StorageError(e) => ErrorKind::StorageError(e), + RuntimeError::StorageError(e) => Error::from(ErrorKind::StorageError(e)), // TODO(#2152): process gracefully RuntimeError::ReceiptValidationError(e) => panic!("{}", e), + RuntimeError::ValidatorError(e) => e.into(), })?; let total_gas_burnt = @@ -414,7 +470,7 @@ impl RuntimeAdapter for NightshadeRuntime { } fn verify_block_signature(&self, header: &BlockHeader) -> Result<(), Error> { - let mut epoch_manager = self.epoch_manager.write().expect(POISONED_LOCK_ERR); + let mut epoch_manager = self.epoch_manager.as_ref().write().expect(POISONED_LOCK_ERR); let validator = epoch_manager .get_block_producer_info(&header.inner_lite.epoch_id, header.inner_lite.height)?; if !header.verify_block_producer(&validator.public_key) { @@ -431,7 +487,7 @@ impl RuntimeAdapter for NightshadeRuntime { vrf_value: near_crypto::vrf::Value, vrf_proof: near_crypto::vrf::Proof, ) -> Result<(), Error> { - let mut epoch_manager = self.epoch_manager.write().expect(POISONED_LOCK_ERR); + let mut epoch_manager = self.epoch_manager.as_ref().write().expect(POISONED_LOCK_ERR); let validator = epoch_manager.get_block_producer_info(&epoch_id, block_height)?; let public_key = near_crypto::key_conversion::convert_public_key( validator.public_key.unwrap_as_ed25519(), @@ -571,7 +627,7 @@ impl RuntimeAdapter for NightshadeRuntime { } fn verify_header_signature(&self, header: &BlockHeader) -> Result { - let mut epoch_manager = self.epoch_manager.write().expect(POISONED_LOCK_ERR); + let mut epoch_manager = self.epoch_manager.as_ref().write().expect(POISONED_LOCK_ERR); let block_producer = epoch_manager .get_block_producer_info(&header.inner_lite.epoch_id, header.inner_lite.height)?; let slashed = match epoch_manager.get_slashed_validators(&header.prev_hash) { @@ -586,7 +642,7 @@ impl RuntimeAdapter for NightshadeRuntime { fn verify_chunk_header_signature(&self, header: &ShardChunkHeader) -> Result { let epoch_id = self.get_epoch_id_from_prev_block(&header.inner.prev_block_hash)?; - let mut epoch_manager = self.epoch_manager.write().expect(POISONED_LOCK_ERR); + let mut epoch_manager = self.epoch_manager.as_ref().write().expect(POISONED_LOCK_ERR); if let Ok(chunk_producer) = epoch_manager.get_chunk_producer_info( &epoch_id, header.inner.height_created, @@ -609,7 +665,7 @@ impl RuntimeAdapter for NightshadeRuntime { block_height: BlockHeight, approvals: &[Option], ) -> Result { - let mut epoch_manager = self.epoch_manager.write().expect(POISONED_LOCK_ERR); + let mut epoch_manager = self.epoch_manager.as_ref().write().expect(POISONED_LOCK_ERR); let info = epoch_manager.get_all_block_approvers_ordered(prev_block_hash).map_err(Error::from)?; if approvals.len() > info.len() { @@ -640,7 +696,7 @@ impl RuntimeAdapter for NightshadeRuntime { epoch_id: &EpochId, last_known_block_hash: &CryptoHash, ) -> Result, Error> { - let mut epoch_manager = self.epoch_manager.write().expect(POISONED_LOCK_ERR); + let mut epoch_manager = self.epoch_manager.as_ref().write().expect(POISONED_LOCK_ERR); epoch_manager .get_all_block_producers_ordered(epoch_id, last_known_block_hash) .map_err(Error::from) @@ -650,7 +706,7 @@ impl RuntimeAdapter for NightshadeRuntime { &self, parent_hash: &CryptoHash, ) -> Result, Error> { - let mut epoch_manager = self.epoch_manager.write().expect(POISONED_LOCK_ERR); + let mut epoch_manager = self.epoch_manager.as_ref().write().expect(POISONED_LOCK_ERR); epoch_manager.get_all_block_approvers_ordered(parent_hash).map_err(Error::from) } @@ -659,7 +715,7 @@ impl RuntimeAdapter for NightshadeRuntime { epoch_id: &EpochId, height: BlockHeight, ) -> Result { - let mut epoch_manager = self.epoch_manager.write().expect(POISONED_LOCK_ERR); + let mut epoch_manager = self.epoch_manager.as_ref().write().expect(POISONED_LOCK_ERR); Ok(epoch_manager.get_block_producer_info(epoch_id, height)?.account_id) } @@ -669,7 +725,7 @@ impl RuntimeAdapter for NightshadeRuntime { height: BlockHeight, shard_id: ShardId, ) -> Result { - let mut epoch_manager = self.epoch_manager.write().expect(POISONED_LOCK_ERR); + let mut epoch_manager = self.epoch_manager.as_ref().write().expect(POISONED_LOCK_ERR); Ok(epoch_manager.get_chunk_producer_info(epoch_id, height, shard_id)?.account_id) } @@ -679,7 +735,7 @@ impl RuntimeAdapter for NightshadeRuntime { last_known_block_hash: &CryptoHash, account_id: &AccountId, ) -> Result<(ValidatorStake, bool), Error> { - let mut epoch_manager = self.epoch_manager.write().expect(POISONED_LOCK_ERR); + let mut epoch_manager = self.epoch_manager.as_ref().write().expect(POISONED_LOCK_ERR); match epoch_manager.get_validator_by_account_id(epoch_id, account_id) { Ok(Some(validator)) => { let slashed = epoch_manager.get_slashed_validators(&last_known_block_hash)?; @@ -696,7 +752,7 @@ impl RuntimeAdapter for NightshadeRuntime { last_known_block_hash: &CryptoHash, account_id: &AccountId, ) -> Result<(ValidatorStake, bool), Error> { - let mut epoch_manager = self.epoch_manager.write().expect(POISONED_LOCK_ERR); + let mut epoch_manager = self.epoch_manager.as_ref().write().expect(POISONED_LOCK_ERR); match epoch_manager.get_fisherman_by_account_id(epoch_id, account_id) { Ok(Some(fisherman)) => { let slashed = epoch_manager.get_slashed_validators(&last_known_block_hash)?; @@ -713,7 +769,7 @@ impl RuntimeAdapter for NightshadeRuntime { last_known_block_hash: &CryptoHash, account_id: &AccountId, ) -> Result { - let mut epoch_manager = self.epoch_manager.write().expect(POISONED_LOCK_ERR); + let mut epoch_manager = self.epoch_manager.as_ref().write().expect(POISONED_LOCK_ERR); epoch_manager .get_num_validator_blocks(epoch_id, last_known_block_hash, account_id) .map_err(Error::from) @@ -747,7 +803,7 @@ impl RuntimeAdapter for NightshadeRuntime { } fn get_part_owner(&self, parent_hash: &CryptoHash, part_id: u64) -> Result { - let mut epoch_manager = self.epoch_manager.write().expect(POISONED_LOCK_ERR); + let mut epoch_manager = self.epoch_manager.as_ref().write().expect(POISONED_LOCK_ERR); let epoch_id = epoch_manager.get_epoch_id_from_prev_block(parent_hash)?; let settlement = epoch_manager.get_all_block_producers_settlement(&epoch_id, parent_hash)?; @@ -775,12 +831,12 @@ impl RuntimeAdapter for NightshadeRuntime { } fn is_next_block_epoch_start(&self, parent_hash: &CryptoHash) -> Result { - let mut epoch_manager = self.epoch_manager.write().expect(POISONED_LOCK_ERR); + let mut epoch_manager = self.epoch_manager.as_ref().write().expect(POISONED_LOCK_ERR); epoch_manager.is_next_block_epoch_start(parent_hash).map_err(Error::from) } fn get_epoch_id_from_prev_block(&self, parent_hash: &CryptoHash) -> Result { - let mut epoch_manager = self.epoch_manager.write().expect(POISONED_LOCK_ERR); + let mut epoch_manager = self.epoch_manager.as_ref().write().expect(POISONED_LOCK_ERR); epoch_manager.get_epoch_id_from_prev_block(parent_hash).map_err(Error::from) } @@ -788,17 +844,17 @@ impl RuntimeAdapter for NightshadeRuntime { &self, parent_hash: &CryptoHash, ) -> Result { - let mut epoch_manager = self.epoch_manager.write().expect(POISONED_LOCK_ERR); + let mut epoch_manager = self.epoch_manager.as_ref().write().expect(POISONED_LOCK_ERR); epoch_manager.get_next_epoch_id_from_prev_block(parent_hash).map_err(Error::from) } fn get_epoch_start_height(&self, block_hash: &CryptoHash) -> Result { - let mut epoch_manager = self.epoch_manager.write().expect(POISONED_LOCK_ERR); + let mut epoch_manager = self.epoch_manager.as_ref().write().expect(POISONED_LOCK_ERR); epoch_manager.get_epoch_start_height(block_hash).map_err(Error::from) } fn get_gc_stop_height(&self, block_hash: &CryptoHash) -> Result { - let mut epoch_manager = self.epoch_manager.write().expect(POISONED_LOCK_ERR); + let mut epoch_manager = self.epoch_manager.as_ref().write().expect(POISONED_LOCK_ERR); // an epoch must have a first block. let epoch_first_block = epoch_manager.get_block_info(block_hash)?.epoch_first_block; let epoch_first_block_info = epoch_manager.get_block_info(&epoch_first_block)?; @@ -819,12 +875,12 @@ impl RuntimeAdapter for NightshadeRuntime { } fn epoch_exists(&self, epoch_id: &EpochId) -> bool { - let mut epoch_manager = self.epoch_manager.write().expect(POISONED_LOCK_ERR); + let mut epoch_manager = self.epoch_manager.as_ref().write().expect(POISONED_LOCK_ERR); epoch_manager.get_epoch_info(epoch_id).is_ok() } fn get_epoch_minted_amount(&self, epoch_id: &EpochId) -> Result { - let mut epoch_manager = self.epoch_manager.write().expect(POISONED_LOCK_ERR); + let mut epoch_manager = self.epoch_manager.as_ref().write().expect(POISONED_LOCK_ERR); Ok(epoch_manager.get_epoch_info(epoch_id)?.minted_amount) } @@ -844,7 +900,7 @@ impl RuntimeAdapter for NightshadeRuntime { assert!(height > 0 || (proposals.is_empty() && slashed_validators.is_empty())); debug!(target: "runtime", "add validator proposals at block height {} {:?}", height, proposals); // Deal with validator proposals and epoch finishing. - let mut epoch_manager = self.epoch_manager.write().expect(POISONED_LOCK_ERR); + let mut epoch_manager = self.epoch_manager.as_ref().write().expect(POISONED_LOCK_ERR); let block_info = BlockInfo::new( height, last_finalized_height, @@ -962,18 +1018,24 @@ impl RuntimeAdapter for NightshadeRuntime { } QueryRequest::CallFunction { account_id, method_name, args } => { let mut logs = vec![]; - let mut epoch_manager = self.epoch_manager.write().expect(POISONED_LOCK_ERR); - let epoch_height = epoch_manager.get_epoch_info(&epoch_id)?.epoch_height; + let epoch_height = { + let mut epoch_manager = + self.epoch_manager.as_ref().write().expect(POISONED_LOCK_ERR); + epoch_manager.get_epoch_info(&epoch_id)?.epoch_height + }; match self.call_function( shard_id, *state_root, block_height, block_timestamp, + block_hash, epoch_height, + epoch_id, account_id, method_name, args.as_ref(), &mut logs, + &self.epoch_manager, ) { Ok(result) => Ok(QueryResponse { kind: QueryResponseKind::CallResult(CallResult { result, logs }), @@ -1050,7 +1112,7 @@ impl RuntimeAdapter for NightshadeRuntime { } fn get_validator_info(&self, block_hash: &CryptoHash) -> Result { - let mut epoch_manager = self.epoch_manager.write().expect(POISONED_LOCK_ERR); + let mut epoch_manager = self.epoch_manager.as_ref().write().expect(POISONED_LOCK_ERR); epoch_manager.get_validator_info(block_hash).map_err(|e| e.into()) } @@ -1158,7 +1220,7 @@ impl RuntimeAdapter for NightshadeRuntime { epoch_id: &EpochId, other_epoch_id: &EpochId, ) -> Result { - let mut epoch_manager = self.epoch_manager.write().expect(POISONED_LOCK_ERR); + let mut epoch_manager = self.epoch_manager.as_ref().write().expect(POISONED_LOCK_ERR); epoch_manager.compare_epoch_id(epoch_id, other_epoch_id).map_err(|e| e.into()) } } @@ -1180,22 +1242,28 @@ impl node_runtime::adapter::ViewRuntimeAdapter for NightshadeRuntime { state_root: MerkleHash, height: BlockHeight, block_timestamp: u64, + last_block_hash: &CryptoHash, epoch_height: EpochHeight, + epoch_id: &EpochId, contract_id: &AccountId, method_name: &str, args: &[u8], logs: &mut Vec, + epoch_info_provider: &dyn EpochInfoProvider, ) -> Result, Box> { let state_update = self.get_tries().new_trie_update(shard_id, state_root); self.trie_viewer.call_function( state_update, height, block_timestamp, + last_block_hash, epoch_height, + epoch_id, contract_id, method_name, args, logs, + epoch_info_provider, ) } diff --git a/neard/src/shard_tracker.rs b/neard/src/shard_tracker.rs index 2a6544477d3..72871442d34 100644 --- a/neard/src/shard_tracker.rs +++ b/neard/src/shard_tracker.rs @@ -5,7 +5,8 @@ use std::sync::{Arc, RwLock}; use byteorder::{LittleEndian, ReadBytesExt}; use log::info; -use near_epoch_manager::{EpochError, EpochManager}; +use near_epoch_manager::EpochManager; +use near_primitives::errors::EpochError; use near_primitives::hash::{hash, CryptoHash}; use near_primitives::types::{AccountId, EpochId, NumShards, ShardId}; diff --git a/runtime/near-runtime-fees/Cargo.toml b/runtime/near-runtime-fees/Cargo.toml index a6888e54c62..f79c1b2952d 100644 --- a/runtime/near-runtime-fees/Cargo.toml +++ b/runtime/near-runtime-fees/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "near-runtime-fees" -version = "0.8.0" +version = "0.9.0" authors = ["Near Inc "] edition = "2018" license = "Apache-2.0" diff --git a/runtime/near-vm-errors/Cargo.toml b/runtime/near-vm-errors/Cargo.toml index 4171d19b33d..6cd6a5f9756 100644 --- a/runtime/near-vm-errors/Cargo.toml +++ b/runtime/near-vm-errors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "near-vm-errors" -version = "0.8.0" +version = "0.9.0" authors = ["Near Inc "] edition = "2018" license = "Apache-2.0" diff --git a/runtime/near-vm-logic/Cargo.toml b/runtime/near-vm-logic/Cargo.toml index 4c4a90b07c5..81b55b2fc86 100644 --- a/runtime/near-vm-logic/Cargo.toml +++ b/runtime/near-vm-logic/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "near-vm-logic" -version = "0.8.0" +version = "0.9.0" authors = ["Near Inc "] edition = "2018" license = "Apache-2.0" @@ -20,8 +20,8 @@ serde = { version = "1", features = ["derive"] } sha2 = "0.8" sha3 = "0.8" -near-runtime-fees = { path = "../near-runtime-fees", version = "0.8.0" } -near-vm-errors = { path = "../near-vm-errors", version = "0.8.0" } +near-runtime-fees = { path = "../near-runtime-fees", version = "0.9.0" } +near-vm-errors = { path = "../near-vm-errors", version = "0.9.0" } [dev-dependencies] serde_json = {version= "1", features= ["preserve_order"]} diff --git a/runtime/near-vm-logic/src/config.rs b/runtime/near-vm-logic/src/config.rs index aaaef59eca4..df6ecb5834d 100644 --- a/runtime/near-vm-logic/src/config.rs +++ b/runtime/near-vm-logic/src/config.rs @@ -273,6 +273,14 @@ pub struct ExtCostsConfig { pub promise_and_per_promise: Gas, /// Cost for calling promise_return pub promise_return: Gas, + + // ############### + // # Validator API # + // ############### + /// Cost of calling `validator_stake`. + pub validator_stake_base: Gas, + /// Cost of calling `validator_total_stake`. + pub validator_total_stake_base: Gas, } // We multiply the actual computed costs by the fixed factor to ensure we @@ -327,6 +335,8 @@ impl Default for ExtCostsConfig { promise_and_base: SAFETY_MULTIPLIER * 491272265, promise_and_per_promise: SAFETY_MULTIPLIER * 1871144, promise_return: SAFETY_MULTIPLIER * 186097468, + validator_stake_base: SAFETY_MULTIPLIER * 9894040 * 1024 * 10, + validator_total_stake_base: SAFETY_MULTIPLIER * 9894040 * 1024 * 10, } } } @@ -379,6 +389,8 @@ impl ExtCostsConfig { promise_and_base: 0, promise_and_per_promise: 0, promise_return: 0, + validator_stake_base: 0, + validator_total_stake_base: 0, } } } @@ -432,6 +444,8 @@ pub enum ExtCosts { promise_and_base, promise_and_per_promise, promise_return, + validator_stake_base, + validator_total_stake_base, } impl ExtCosts { @@ -483,6 +497,8 @@ impl ExtCosts { promise_and_base => config.promise_and_base, promise_and_per_promise => config.promise_and_per_promise, promise_return => config.promise_return, + validator_stake_base => config.validator_stake_base, + validator_total_stake_base => config.validator_total_stake_base, } } } diff --git a/runtime/near-vm-logic/src/dependencies.rs b/runtime/near-vm-logic/src/dependencies.rs index 9a6258ac7b4..5961ba42752 100644 --- a/runtime/near-vm-logic/src/dependencies.rs +++ b/runtime/near-vm-logic/src/dependencies.rs @@ -514,4 +514,11 @@ pub trait External { /// Resets amount of touched trie nodes by storage operations fn reset_touched_nodes_counter(&mut self); + + /// Returns the validator stake for given account in the current epoch. + /// If the account is not a validator, returns `None`. + fn validator_stake(&self, account_id: &AccountId) -> Result>; + + /// Returns total stake of validators in the current epoch. + fn validator_total_stake(&self) -> Result; } diff --git a/runtime/near-vm-logic/src/logic.rs b/runtime/near-vm-logic/src/logic.rs index 2cb8e0d1b36..8b8b6b5e967 100644 --- a/runtime/near-vm-logic/src/logic.rs +++ b/runtime/near-vm-logic/src/logic.rs @@ -609,6 +609,39 @@ impl<'a> VMLogic<'a> { Ok(self.context.epoch_height) } + /// Get the stake of an account, if the account is currently a validator. Otherwise returns 0. + /// writes the value into the` u128` variable pointed by `stake_ptr`. + /// + /// # Cost + /// + /// `base + memory_write_base + memory_write_size * 16 + utf8_decoding_base + utf8_decoding_byte * account_id_len + validator_stake_base`. + pub fn validator_stake( + &mut self, + account_id_len: u64, + account_id_ptr: u64, + stake_ptr: u64, + ) -> Result<()> { + self.gas_counter.pay_base(base)?; + let account_id = self.read_and_parse_account_id(account_id_ptr, account_id_len)?; + self.gas_counter.pay_base(validator_stake_base)?; + let balance = self.ext.validator_stake(&account_id)?.unwrap_or_default(); + self.memory_set_u128(stake_ptr, balance) + } + + /// Get the total validator stake of the current epoch. + /// Write the u128 value into `stake_ptr`. + /// writes the value into the` u128` variable pointed by `stake_ptr`. + /// + /// # Cost + /// + /// `base + memory_write_base + memory_write_size * 16 + validator_total_stake_base` + pub fn validator_total_stake(&mut self, stake_ptr: u64) -> Result<()> { + self.gas_counter.pay_base(base)?; + self.gas_counter.pay_base(validator_total_stake_base)?; + let total_stake = self.ext.validator_total_stake()?; + self.memory_set_u128(stake_ptr, total_stake) + } + /// Returns the number of bytes used by the contract if it was saved to the trie as of the /// invocation. This includes: /// * The data written with storage_* functions during current and previous execution; diff --git a/runtime/near-vm-logic/src/mocks/mock_external.rs b/runtime/near-vm-logic/src/mocks/mock_external.rs index 81c87b5b15f..f01d8d398b2 100644 --- a/runtime/near-vm-logic/src/mocks/mock_external.rs +++ b/runtime/near-vm-logic/src/mocks/mock_external.rs @@ -10,6 +10,7 @@ use std::collections::HashMap; pub struct MockedExternal { pub fake_trie: HashMap, Vec>, receipts: Vec, + validators: HashMap, } pub struct MockedValuePtr { @@ -37,7 +38,9 @@ impl ValuePtr for MockedValuePtr { impl MockedExternal { pub fn new() -> Self { - Self::default() + let validators = + vec![("alice".to_string(), 100), ("bob".to_string(), 1)].into_iter().collect(); + MockedExternal { fake_trie: Default::default(), receipts: vec![], validators } } /// Get calls to receipt create that were performed during contract call. @@ -224,6 +227,14 @@ impl External for MockedExternal { } fn reset_touched_nodes_counter(&mut self) {} + + fn validator_stake(&self, account_id: &AccountId) -> Result> { + Ok(self.validators.get(account_id).cloned()) + } + + fn validator_total_stake(&self) -> Result { + Ok(self.validators.values().sum()) + } } #[derive(Serialize, Deserialize, Clone, Debug)] diff --git a/runtime/near-vm-runner-standalone/Cargo.toml b/runtime/near-vm-runner-standalone/Cargo.toml index d7674d6102d..91e7a1855ae 100644 --- a/runtime/near-vm-runner-standalone/Cargo.toml +++ b/runtime/near-vm-runner-standalone/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "near-vm-runner-standalone" -version = "0.8.0" +version = "0.9.0" authors = ["Near Inc "] edition = "2018" license = "Apache-2.0" @@ -24,6 +24,6 @@ serde_json = "1" clap = "2.33.0" base64 = "0.11" -near-vm-logic = { path = "../near-vm-logic", version = "0.8.0"} -near-vm-runner = { path = "../near-vm-runner", version = "0.8.0" } -near-runtime-fees = { path = "../near-runtime-fees", version = "0.8.0" } +near-vm-logic = { path = "../near-vm-logic", version = "0.9.0"} +near-vm-runner = { path = "../near-vm-runner", version = "0.9.0" } +near-runtime-fees = { path = "../near-runtime-fees", version = "0.9.0" } diff --git a/runtime/near-vm-runner/Cargo.toml b/runtime/near-vm-runner/Cargo.toml index a144a1a348d..2063dad0914 100644 --- a/runtime/near-vm-runner/Cargo.toml +++ b/runtime/near-vm-runner/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "near-vm-runner" -version = "0.8.0" +version = "0.9.0" authors = ["Near Inc "] edition = "2018" license = "Apache-2.0" @@ -19,9 +19,9 @@ wasmer-runtime-core = { version = "=0.17.0" } pwasm-utils = "0.12" parity-wasm = "0.41" -near-runtime-fees = { path="../near-runtime-fees", version = "0.8.0" } -near-vm-logic = { path="../near-vm-logic", version = "0.8.0", default-features = false, features = []} -near-vm-errors = { path = "../near-vm-errors", version = "0.8.0" } +near-runtime-fees = { path="../near-runtime-fees", version = "0.9.0" } +near-vm-logic = { path="../near-vm-logic", version = "0.9.0", default-features = false, features = []} +near-vm-errors = { path = "../near-vm-errors", version = "0.9.0" } [dev-dependencies] assert_matches = "1.3" diff --git a/runtime/near-vm-runner/src/imports.rs b/runtime/near-vm-runner/src/imports.rs index 0ac423d5dfc..1c417ede988 100644 --- a/runtime/near-vm-runner/src/imports.rs +++ b/runtime/near-vm-runner/src/imports.rs @@ -175,4 +175,9 @@ wrapped_imports! { storage_iter_next<[iterator_id: u64, key_register_id: u64, value_register_id: u64] -> [u64]>, // Function for the injected gas counter. Automatically called by the gas meter. gas<[gas_amount: u32] -> []>, + // ############### + // # Validator API # + // ############### + validator_stake<[account_id_len: u64, account_id_ptr: u64, stake_ptr: u64] -> []>, + validator_total_stake<[stake_ptr: u64] -> []>, } diff --git a/runtime/near-vm-runner/tests/res/test_contract_rs.wasm b/runtime/near-vm-runner/tests/res/test_contract_rs.wasm index dda54bea68e..1ec8975b269 100755 Binary files a/runtime/near-vm-runner/tests/res/test_contract_rs.wasm and b/runtime/near-vm-runner/tests/res/test_contract_rs.wasm differ diff --git a/runtime/near-vm-runner/tests/test-contract-rs/src/lib.rs b/runtime/near-vm-runner/tests/test-contract-rs/src/lib.rs index f57ffa17fae..7d1d45ae8ca 100644 --- a/runtime/near-vm-runner/tests/test-contract-rs/src/lib.rs +++ b/runtime/near-vm-runner/tests/test-contract-rs/src/lib.rs @@ -141,6 +141,11 @@ extern "C" { fn storage_iter_prefix(prefix_len: u64, prefix_ptr: u64) -> u64; fn storage_iter_range(start_len: u64, start_ptr: u64, end_len: u64, end_ptr: u64) -> u64; fn storage_iter_next(iterator_id: u64, key_register_id: u64, value_register_id: u64) -> u64; + // ############### + // # Validator API # + // ############### + fn validator_stake(account_id_len: u64, account_id_ptr: u64, stake_ptr: u64); + fn validator_total_stake(stake_ptr: u64); } macro_rules! ext_test { @@ -192,6 +197,8 @@ ext_test!(ext_account_id, current_account_id); ext_test_u128!(ext_account_balance, account_balance); ext_test_u128!(ext_attached_deposit, attached_deposit); +ext_test_u128!(ext_validator_total_stake, validator_total_stake); + #[no_mangle] pub unsafe fn ext_sha256() { input(0); @@ -203,6 +210,20 @@ pub unsafe fn ext_sha256() { value_return(result.len() as u64, result.as_ptr() as *const u64 as u64); } +#[no_mangle] +pub unsafe fn ext_validator_stake() { + input(0); + let account_id = vec![0; register_len(0) as usize]; + read_register(0, account_id.as_ptr() as *const u64 as u64); + let result = [0u8; size_of::()]; + validator_stake( + account_id.len() as u64, + account_id.as_ptr() as *const u64 as u64, + result.as_ptr() as *const u64 as u64, + ); + value_return(result.len() as u64, result.as_ptr() as *const u64 as u64); +} + #[no_mangle] pub unsafe fn write_key_value() { input(0); diff --git a/runtime/near-vm-runner/tests/test_rs_contract.rs b/runtime/near-vm-runner/tests/test_rs_contract.rs index e5a0b096c08..ff4daead028 100644 --- a/runtime/near-vm-runner/tests/test_rs_contract.rs +++ b/runtime/near-vm-runner/tests/test_rs_contract.rs @@ -152,6 +152,21 @@ def_test_ext!( def_test_ext!(ext_account_balance, b"ext_account_balance", &(2u128 + 2).to_le_bytes()); def_test_ext!(ext_attached_deposit, b"ext_attached_deposit", &2u128.to_le_bytes()); +def_test_ext!( + ext_validator_stake_alice, + b"ext_validator_stake", + &(100u128).to_le_bytes(), + b"alice" +); +def_test_ext!(ext_validator_stake_bob, b"ext_validator_stake", &(1u128).to_le_bytes(), b"bob"); +def_test_ext!(ext_validator_stake_carol, b"ext_validator_stake", &(0u128).to_le_bytes(), b"carol"); + +def_test_ext!( + ext_validator_total_stake, + b"ext_validator_total_stake", + &(100u128 + 1).to_le_bytes() +); + #[test] pub fn test_out_of_memory() { let code = &TEST_CONTRACT; diff --git a/runtime/runtime-params-estimator/Cargo.toml b/runtime/runtime-params-estimator/Cargo.toml index abe9fe165c6..9f12a66f4cb 100644 --- a/runtime/runtime-params-estimator/Cargo.toml +++ b/runtime/runtime-params-estimator/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "runtime-params-estimator" -version = "0.1.0" +version = "0.9.0" authors = ["Near Inc "] edition = "2018" diff --git a/runtime/runtime-params-estimator/src/cases.rs b/runtime/runtime-params-estimator/src/cases.rs index 99d93fbb788..d5cfc6f2a74 100644 --- a/runtime/runtime-params-estimator/src/cases.rs +++ b/runtime/runtime-params-estimator/src/cases.rs @@ -608,6 +608,9 @@ fn get_ext_costs_config(measurement: &Measurements) -> ExtCostsConfig { promise_and_base: measured_to_gas(metric, &measured, promise_and_base), promise_and_per_promise: measured_to_gas(metric, &measured, promise_and_per_promise), promise_return: measured_to_gas(metric, &measured, promise_return), + // TODO: accurately price host functions that expose validator information. + validator_stake_base: measured_to_gas(metric, &measured, validator_stake_base), + validator_total_stake_base: measured_to_gas(metric, &measured, validator_total_stake_base), } } diff --git a/runtime/runtime-params-estimator/src/testbed.rs b/runtime/runtime-params-estimator/src/testbed.rs index 2c1ce8654a9..0313eda333b 100644 --- a/runtime/runtime-params-estimator/src/testbed.rs +++ b/runtime/runtime-params-estimator/src/testbed.rs @@ -5,6 +5,7 @@ use std::path::Path; use borsh::BorshDeserialize; use near_primitives::receipt::Receipt; +use near_primitives::test_utils::MockEpochInfoProvider; use near_primitives::transaction::{ExecutionStatus, SignedTransaction}; use near_primitives::types::{Gas, MerkleHash, StateRoot}; use near_store::{create_store, ColState, ShardTries}; @@ -25,6 +26,7 @@ pub struct RuntimeTestbed { runtime: Runtime, prev_receipts: Vec, apply_state: ApplyState, + epoch_info_provider: MockEpochInfoProvider, } impl RuntimeTestbed { @@ -75,13 +77,22 @@ impl RuntimeTestbed { // Put each runtime into a separate shard. block_index: 0, // Epoch length is long enough to avoid corner cases. - epoch_length: 4, + last_block_hash: Default::default(), + epoch_id: Default::default(), epoch_height: 0, gas_price: 1, block_timestamp: 0, gas_limit: None, }; - Self { workdir, tries, root, runtime, prev_receipts, apply_state } + Self { + workdir, + tries, + root, + runtime, + prev_receipts, + apply_state, + epoch_info_provider: MockEpochInfoProvider::default(), + } } pub fn process_block( @@ -98,6 +109,7 @@ impl RuntimeTestbed { &self.apply_state, &self.prev_receipts, transactions, + &self.epoch_info_provider, ) .unwrap(); diff --git a/runtime/runtime-standalone/Cargo.toml b/runtime/runtime-standalone/Cargo.toml index 006e214fe41..c6d89d8e6f8 100644 --- a/runtime/runtime-standalone/Cargo.toml +++ b/runtime/runtime-standalone/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "near-runtime-standalone" -version = "0.1.0" +version = "0.9.0" authors = ["Near Inc "] edition = "2018" diff --git a/runtime/runtime-standalone/src/lib.rs b/runtime/runtime-standalone/src/lib.rs index 5dc6d35fffb..c7294cb8acb 100644 --- a/runtime/runtime-standalone/src/lib.rs +++ b/runtime/runtime-standalone/src/lib.rs @@ -1,5 +1,7 @@ use near_crypto::{InMemorySigner, KeyType, PublicKey, Signer}; use near_pool::{types::PoolIterator, TransactionPool}; +use near_primitives::test_utils::MockEpochInfoProvider; +use near_primitives::types::{AccountInfo, EpochId, EpochInfoProvider}; use near_primitives::{account::AccessKey, test_utils::account_new}; use near_primitives::{ account::Account, @@ -36,6 +38,7 @@ pub struct GenesisConfig { pub epoch_length: u64, pub runtime_config: RuntimeConfig, pub state_records: Vec, + pub validators: Vec, } impl Default for GenesisConfig { @@ -48,6 +51,7 @@ impl Default for GenesisConfig { epoch_length: DEFAULT_EPOCH_LENGTH, runtime_config: RuntimeConfig::default(), state_records: vec![], + validators: vec![], } } } @@ -117,6 +121,7 @@ pub struct RuntimeStandalone { runtime: Runtime, tries: ShardTries, pending_receipts: Vec, + epoch_info_provider: Box, } impl RuntimeStandalone { @@ -130,6 +135,7 @@ impl RuntimeStandalone { store_update.merge(s_update); store_update.commit().unwrap(); genesis_block.state_root = state_root; + let validators = genesis.validators.clone(); Self { genesis, tries, @@ -139,6 +145,9 @@ impl RuntimeStandalone { cur_block: genesis_block, tx_pool: TransactionPool::new(), pending_receipts: vec![], + epoch_info_provider: Box::new(MockEpochInfoProvider::new( + validators.into_iter().map(|info| (info.account_id, info.amount)), + )), } } @@ -194,11 +203,13 @@ impl RuntimeStandalone { pub fn produce_block(&mut self) -> Result<(), RuntimeError> { let apply_state = ApplyState { block_index: self.cur_block.block_height, - epoch_length: 0, // TODO: support for epochs epoch_height: self.cur_block.block_height, gas_price: self.cur_block.gas_price, block_timestamp: self.cur_block.block_timestamp, gas_limit: None, + // not used + last_block_hash: CryptoHash::default(), + epoch_id: EpochId::default(), }; let apply_result = self.runtime.apply( @@ -208,6 +219,7 @@ impl RuntimeStandalone { &apply_state, &self.pending_receipts, &Self::prepare_transactions(&mut self.tx_pool), + self.epoch_info_provider.as_ref(), )?; self.pending_receipts = apply_result.outgoing_receipts; apply_result.outcomes.iter().for_each(|outcome| { @@ -278,11 +290,14 @@ impl RuntimeStandalone { trie_update, self.cur_block.block_height, self.cur_block.block_timestamp, + &CryptoHash::default(), self.cur_block.epoch_height, + &EpochId::default(), account_id, method_name, args, &mut logs, + self.epoch_info_provider.as_ref(), )?; Ok((result, logs)) } diff --git a/runtime/runtime/Cargo.toml b/runtime/runtime/Cargo.toml index aefb03d36fa..6c6e170204c 100644 --- a/runtime/runtime/Cargo.toml +++ b/runtime/runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "node-runtime" -version = "0.0.1" +version = "0.9.0" authors = ["Near Inc "] edition = "2018" diff --git a/runtime/runtime/src/actions.rs b/runtime/runtime/src/actions.rs index e80e9a0a60f..e19b46362f3 100644 --- a/runtime/runtime/src/actions.rs +++ b/runtime/runtime/src/actions.rs @@ -11,7 +11,7 @@ use near_primitives::transaction::{ Action, AddKeyAction, DeleteAccountAction, DeleteKeyAction, DeployContractAction, FunctionCallAction, StakeAction, TransferAction, }; -use near_primitives::types::{AccountId, Balance, ValidatorStake}; +use near_primitives::types::{AccountId, Balance, EpochInfoProvider, ValidatorStake}; use near_primitives::utils::{ is_valid_account_id, is_valid_sub_account_id, is_valid_top_level_account_id, }; @@ -86,6 +86,7 @@ pub(crate) fn action_function_call( action_hash: &CryptoHash, config: &RuntimeConfig, is_last_action: bool, + epoch_info_provider: &dyn EpochInfoProvider, ) -> Result<(), RuntimeError> { let code = match get_code_with_cache(state_update, account_id, &account) { Ok(Some(code)) => code, @@ -115,6 +116,9 @@ pub(crate) fn action_function_call( &action_receipt.signer_public_key, action_receipt.gas_price, action_hash, + &apply_state.epoch_id, + &apply_state.last_block_hash, + epoch_info_provider, ); // Output data receipts are ignored if the function call is not the last action in the batch. let output_data_receivers: Vec<_> = if is_last_action { @@ -164,6 +168,7 @@ pub(crate) fn action_function_call( .expect("External error deserialization shouldn't fail"); return match err { ExternalError::StorageError(err) => Err(err.into()), + ExternalError::ValidatorError(err) => Err(RuntimeError::ValidatorError(err)), }; } Some(VMError::InconsistentStateError(err)) => { diff --git a/runtime/runtime/src/adapter.rs b/runtime/runtime/src/adapter.rs index f21e9de6cc2..c6cc221ce41 100644 --- a/runtime/runtime/src/adapter.rs +++ b/runtime/runtime/src/adapter.rs @@ -1,6 +1,9 @@ use near_crypto::PublicKey; use near_primitives::account::{AccessKey, Account}; -use near_primitives::types::{AccountId, BlockHeight, EpochHeight, MerkleHash, ShardId}; +use near_primitives::hash::CryptoHash; +use near_primitives::types::{ + AccountId, BlockHeight, EpochHeight, EpochId, EpochInfoProvider, MerkleHash, ShardId, +}; use near_primitives::views::ViewStateResult; /// Adapter for querying runtime. @@ -18,11 +21,14 @@ pub trait ViewRuntimeAdapter { state_root: MerkleHash, height: BlockHeight, block_timestamp: u64, + last_block_hash: &CryptoHash, epoch_height: EpochHeight, + epoch_id: &EpochId, contract_id: &AccountId, method_name: &str, args: &[u8], logs: &mut Vec, + epoch_info_provider: &dyn EpochInfoProvider, ) -> Result, Box>; fn view_access_key( diff --git a/runtime/runtime/src/ext.rs b/runtime/runtime/src/ext.rs index ce7d5f5b6f6..55317fe5f37 100644 --- a/runtime/runtime/src/ext.rs +++ b/runtime/runtime/src/ext.rs @@ -9,7 +9,7 @@ use near_primitives::transaction::{ DeployContractAction, FunctionCallAction, StakeAction, TransferAction, }; use near_primitives::trie_key::TrieKey; -use near_primitives::types::{AccountId, Balance}; +use near_primitives::types::{AccountId, Balance, EpochId, EpochInfoProvider}; use near_primitives::utils::create_nonce_with_nonce; use near_store::{TrieUpdate, TrieUpdateValuePtr}; use near_vm_logic::{External, HostError, VMLogicError, ValuePtr}; @@ -24,6 +24,9 @@ pub struct RuntimeExt<'a> { gas_price: Balance, base_data_id: &'a CryptoHash, data_count: u64, + epoch_id: &'a EpochId, + last_block_hash: &'a CryptoHash, + epoch_info_provider: &'a dyn EpochInfoProvider, } pub struct RuntimeExtValuePtr<'a>(TrieUpdateValuePtr<'a>); @@ -46,6 +49,9 @@ impl<'a> RuntimeExt<'a> { signer_public_key: &'a PublicKey, gas_price: Balance, base_data_id: &'a CryptoHash, + epoch_id: &'a EpochId, + last_block_hash: &'a CryptoHash, + epoch_info_provider: &'a dyn EpochInfoProvider, ) -> Self { RuntimeExt { trie_update, @@ -56,6 +62,9 @@ impl<'a> RuntimeExt<'a> { gas_price, base_data_id, data_count: 0, + epoch_id, + last_block_hash, + epoch_info_provider, } } @@ -320,4 +329,16 @@ impl<'a> External for RuntimeExt<'a> { fn reset_touched_nodes_counter(&mut self) { self.trie_update.trie.counter.reset() } + + fn validator_stake(&self, account_id: &AccountId) -> ExtResult> { + self.epoch_info_provider + .validator_stake(self.epoch_id, self.last_block_hash, account_id) + .map_err(|e| ExternalError::ValidatorError(e).into()) + } + + fn validator_total_stake(&self) -> ExtResult { + self.epoch_info_provider + .validator_total_stake(self.epoch_id, self.last_block_hash) + .map_err(|e| ExternalError::ValidatorError(e).into()) + } } diff --git a/runtime/runtime/src/lib.rs b/runtime/runtime/src/lib.rs index 3ec34bcebc6..41e2ca0cad2 100644 --- a/runtime/runtime/src/lib.rs +++ b/runtime/runtime/src/lib.rs @@ -17,8 +17,8 @@ use near_primitives::transaction::{ }; use near_primitives::trie_key::TrieKey; use near_primitives::types::{ - AccountId, Balance, BlockHeight, BlockHeightDelta, EpochHeight, Gas, MerkleHash, Nonce, - RawStateChangesWithTrieKey, ShardId, StateChangeCause, StateRoot, ValidatorStake, + AccountId, Balance, BlockHeight, EpochHeight, EpochId, EpochInfoProvider, Gas, MerkleHash, + Nonce, RawStateChangesWithTrieKey, ShardId, StateChangeCause, StateRoot, ValidatorStake, }; use near_primitives::utils::{create_nonce_with_nonce, system_account}; use near_store::{ @@ -57,8 +57,10 @@ pub struct ApplyState { /// Currently building block height. // TODO #1903 pub block_height: BlockHeight, pub block_index: BlockHeight, - /// Current epoch length. - pub epoch_length: BlockHeightDelta, + /// Prev block hash + pub last_block_hash: CryptoHash, + /// Current epoch id + pub epoch_id: EpochId, /// Current epoch height pub epoch_height: EpochHeight, /// Price for the gas. @@ -273,6 +275,7 @@ impl Runtime { promise_results: &[PromiseResult], action_hash: CryptoHash, is_last_action: bool, + epoch_info_provider: &dyn EpochInfoProvider, ) -> Result { let mut result = ActionResult::default(); let exec_fees = exec_fee(&self.config.transaction_costs, action); @@ -326,6 +329,7 @@ impl Runtime { &action_hash, &self.config, is_last_action, + epoch_info_provider, )?; } Action::Transfer(transfer) => { @@ -388,6 +392,7 @@ impl Runtime { outgoing_receipts: &mut Vec, validator_proposals: &mut Vec, stats: &mut ApplyStats, + epoch_info_provider: &dyn EpochInfoProvider, ) -> Result { let action_receipt = match receipt.receipt { ReceiptEnum::Action(ref action_receipt) => action_receipt, @@ -445,6 +450,7 @@ impl Runtime { u64::max_value() - action_index as u64, ), is_last_action, + epoch_info_provider, )?; if new_result.result.is_ok() { if let Err(e) = new_result.new_receipts.iter().try_for_each(|receipt| { @@ -663,6 +669,7 @@ impl Runtime { outgoing_receipts: &mut Vec, validator_proposals: &mut Vec, stats: &mut ApplyStats, + epoch_info_provider: &dyn EpochInfoProvider, ) -> Result, RuntimeError> { let account_id = &receipt.receiver_id; match receipt.receipt { @@ -730,6 +737,7 @@ impl Runtime { outgoing_receipts, validator_proposals, stats, + epoch_info_provider, ) .map(Some); } else { @@ -783,6 +791,7 @@ impl Runtime { outgoing_receipts, validator_proposals, stats, + epoch_info_provider, ) .map(Some); } else { @@ -939,6 +948,7 @@ impl Runtime { apply_state: &ApplyState, incoming_receipts: &[Receipt], transactions: &[SignedTransaction], + epoch_info_provider: &dyn EpochInfoProvider, ) -> Result { let initial_state = TrieUpdate::new(trie.clone(), root); let mut state_update = TrieUpdate::new(trie, root); @@ -992,6 +1002,7 @@ impl Runtime { &mut outgoing_receipts, &mut validator_proposals, &mut stats, + epoch_info_provider, )? .into_iter() .try_for_each( @@ -1270,7 +1281,7 @@ mod tests { use near_crypto::{InMemorySigner, KeyType, Signer}; use near_primitives::errors::ReceiptValidationError; use near_primitives::hash::hash; - use near_primitives::test_utils::account_new; + use near_primitives::test_utils::{account_new, MockEpochInfoProvider}; use near_primitives::transaction::TransferAction; use near_primitives::types::MerkleHash; use near_store::test_utils::create_tries; @@ -1318,7 +1329,8 @@ mod tests { initial_balance: Balance, initial_locked: Balance, gas_limit: Gas, - ) -> (Runtime, ShardTries, CryptoHash, ApplyState, Arc) { + ) -> (Runtime, ShardTries, CryptoHash, ApplyState, Arc, impl EpochInfoProvider) + { let tries = create_tries(); let root = MerkleHash::default(); let runtime = Runtime::new(RuntimeConfig::default()); @@ -1344,21 +1356,32 @@ mod tests { let apply_state = ApplyState { block_index: 0, - epoch_length: 3, + last_block_hash: Default::default(), + epoch_id: Default::default(), epoch_height: 0, gas_price: GAS_PRICE, block_timestamp: 100, gas_limit: Some(gas_limit), }; - (runtime, tries, root, apply_state, signer) + (runtime, tries, root, apply_state, signer, MockEpochInfoProvider::default()) } #[test] fn test_apply_no_op() { - let (runtime, tries, root, apply_state, _) = + let (runtime, tries, root, apply_state, _, epoch_info_provider) = setup_runtime(to_yocto(1_000_000), 0, 10u64.pow(15)); - runtime.apply(tries.get_trie_for_shard(0), root, &None, &apply_state, &[], &[]).unwrap(); + runtime + .apply( + tries.get_trie_for_shard(0), + root, + &None, + &apply_state, + &[], + &[], + &epoch_info_provider, + ) + .unwrap(); } #[test] @@ -1366,7 +1389,7 @@ mod tests { let initial_locked = to_yocto(500_000); let reward = to_yocto(10_000_000); let small_refund = to_yocto(500); - let (runtime, tries, root, apply_state, _) = + let (runtime, tries, root, apply_state, _, epoch_info_provider) = setup_runtime(to_yocto(1_000_000), initial_locked, 10u64.pow(15)); let validator_accounts_update = ValidatorAccountsUpdate { @@ -1385,6 +1408,7 @@ mod tests { &apply_state, &[Receipt::new_refund(&alice_account(), small_refund)], &[], + &epoch_info_provider, ) .unwrap(); } @@ -1395,7 +1419,7 @@ mod tests { let initial_locked = to_yocto(500_000); let small_transfer = to_yocto(10_000); let gas_limit = 1; - let (runtime, tries, mut root, apply_state, _) = + let (runtime, tries, mut root, apply_state, _, epoch_info_provider) = setup_runtime(initial_balance, initial_locked, gas_limit); let n = 10; @@ -1405,7 +1429,15 @@ mod tests { for i in 1..=n + 3 { let prev_receipts: &[Receipt] = if i == 1 { &receipts } else { &[] }; let apply_result = runtime - .apply(tries.get_trie_for_shard(0), root, &None, &apply_state, prev_receipts, &[]) + .apply( + tries.get_trie_for_shard(0), + root, + &None, + &apply_state, + prev_receipts, + &[], + &epoch_info_provider, + ) .unwrap(); let (store_update, new_root) = tries.apply_all(&apply_result.trie_changes, 0).unwrap(); root = new_root; @@ -1427,7 +1459,7 @@ mod tests { let initial_balance = to_yocto(1_000_000); let initial_locked = to_yocto(500_000); let small_transfer = to_yocto(10_000); - let (runtime, tries, mut root, mut apply_state, _) = + let (runtime, tries, mut root, mut apply_state, _, epoch_info_provider) = setup_runtime(initial_balance, initial_locked, 1); let receipt_gas_cost = @@ -1443,7 +1475,15 @@ mod tests { for i in 1..=n / 3 + 3 { let prev_receipts: &[Receipt] = receipt_chunks.next().unwrap_or_default(); let apply_result = runtime - .apply(tries.get_trie_for_shard(0), root, &None, &apply_state, prev_receipts, &[]) + .apply( + tries.get_trie_for_shard(0), + root, + &None, + &apply_state, + prev_receipts, + &[], + &epoch_info_provider, + ) .unwrap(); let (store_update, new_root) = tries.apply_all(&apply_result.trie_changes, 0).unwrap(); root = new_root; @@ -1465,7 +1505,7 @@ mod tests { let initial_balance = to_yocto(1_000_000); let initial_locked = to_yocto(500_000); let small_transfer = to_yocto(10_000); - let (runtime, tries, mut root, mut apply_state, _) = + let (runtime, tries, mut root, mut apply_state, _, epoch_info_provider) = setup_runtime(initial_balance, initial_locked, 1); let receipt_gas_cost = @@ -1490,7 +1530,15 @@ mod tests { let prev_receipts: &[Receipt] = receipt_chunks.next().unwrap_or_default(); num_receipts_given += prev_receipts.len() as u64; let apply_result = runtime - .apply(tries.get_trie_for_shard(0), root, &None, &apply_state, prev_receipts, &[]) + .apply( + tries.get_trie_for_shard(0), + root, + &None, + &apply_state, + prev_receipts, + &[], + &epoch_info_provider, + ) .unwrap(); let (store_update, new_root) = tries.apply_all(&apply_result.trie_changes, 0).unwrap(); root = new_root; @@ -1536,7 +1584,7 @@ mod tests { let initial_balance = to_yocto(1_000_000); let initial_locked = to_yocto(500_000); let small_transfer = to_yocto(10_000); - let (mut runtime, tries, root, mut apply_state, signer) = + let (mut runtime, tries, root, mut apply_state, signer, epoch_info_provider) = setup_runtime(initial_balance, initial_locked, 1); let receipt_exec_gas_fee = 1000; @@ -1575,6 +1623,7 @@ mod tests { &apply_state, &receipts[0..2], &local_transactions[0..4], + &epoch_info_provider, ) .unwrap(); let (store_update, root) = tries.apply_all(&apply_result.trie_changes, 0).unwrap(); @@ -1606,6 +1655,7 @@ mod tests { &apply_state, &receipts[2..3], &local_transactions[4..5], + &epoch_info_provider, ) .unwrap(); let (store_update, root) = tries.apply_all(&apply_result.trie_changes, 0).unwrap(); @@ -1634,6 +1684,7 @@ mod tests { &apply_state, &receipts[3..4], &local_transactions[5..9], + &epoch_info_provider, ) .unwrap(); let (store_update, root) = tries.apply_all(&apply_result.trie_changes, 0).unwrap(); @@ -1658,7 +1709,15 @@ mod tests { // R#4 is added to delayed queue. // The new delayed queue is R#3, R#4 let apply_result = runtime - .apply(tries.get_trie_for_shard(0), root, &None, &apply_state, &receipts[4..5], &[]) + .apply( + tries.get_trie_for_shard(0), + root, + &None, + &apply_state, + &receipts[4..5], + &[], + &epoch_info_provider, + ) .unwrap(); let (store_update, root) = tries.apply_all(&apply_result.trie_changes, 0).unwrap(); store_update.commit().unwrap(); @@ -1677,7 +1736,15 @@ mod tests { // We process R#3, R#4, R#5. // The new delayed queue is empty. let apply_result = runtime - .apply(tries.get_trie_for_shard(0), root, &None, &apply_state, &receipts[5..6], &[]) + .apply( + tries.get_trie_for_shard(0), + root, + &None, + &apply_state, + &receipts[5..6], + &[], + &epoch_info_provider, + ) .unwrap(); assert_eq!( @@ -1697,7 +1764,7 @@ mod tests { let initial_locked = to_yocto(500_000); let small_transfer = to_yocto(10_000); let gas_limit = 1; - let (runtime, tries, root, apply_state, _) = + let (runtime, tries, root, apply_state, _, epoch_info_provider) = setup_runtime(initial_balance, initial_locked, gas_limit); let n = 1; @@ -1706,7 +1773,15 @@ mod tests { receipts.get_mut(0).unwrap().predecessor_id = invalid_account_id.clone(); let err = runtime - .apply(tries.get_trie_for_shard(0), root, &None, &apply_state, &receipts, &[]) + .apply( + tries.get_trie_for_shard(0), + root, + &None, + &apply_state, + &receipts, + &[], + &epoch_info_provider, + ) .err() .unwrap(); assert_eq!( @@ -1723,7 +1798,7 @@ mod tests { let initial_locked = to_yocto(500_000); let small_transfer = to_yocto(10_000); let gas_limit = 1; - let (runtime, tries, root, apply_state, _) = + let (runtime, tries, root, apply_state, _, epoch_info_provider) = setup_runtime(initial_balance, initial_locked, gas_limit); let n = 1; @@ -1743,7 +1818,15 @@ mod tests { store_update.commit().unwrap(); let err = runtime - .apply(tries.get_trie_for_shard(0), root, &None, &apply_state, &[], &[]) + .apply( + tries.get_trie_for_shard(0), + root, + &None, + &apply_state, + &[], + &[], + &epoch_info_provider, + ) .err() .unwrap(); assert_eq!( diff --git a/runtime/runtime/src/state_viewer.rs b/runtime/runtime/src/state_viewer.rs index b4311899e85..fd0a56bb6bb 100644 --- a/runtime/runtime/src/state_viewer.rs +++ b/runtime/runtime/src/state_viewer.rs @@ -10,7 +10,7 @@ use near_primitives::hash::CryptoHash; use near_primitives::serialize::to_base64; use near_primitives::trie_key::trie_key_parsers; use near_primitives::types::EpochHeight; -use near_primitives::types::{AccountId, BlockHeight}; +use near_primitives::types::{AccountId, BlockHeight, EpochId, EpochInfoProvider}; use near_primitives::utils::is_valid_account_id; use near_primitives::views::{StateItem, ViewStateResult}; use near_runtime_fees::RuntimeFeesConfig; @@ -88,11 +88,14 @@ impl TrieViewer { mut state_update: TrieUpdate, block_height: BlockHeight, block_timestamp: u64, + last_block_hash: &CryptoHash, epoch_height: EpochHeight, + epoch_id: &EpochId, contract_id: &AccountId, method_name: &str, args: &[u8], logs: &mut Vec, + epoch_info_provider: &dyn EpochInfoProvider, ) -> Result, Box> { let now = Instant::now(); if !is_valid_account_id(contract_id) { @@ -116,6 +119,9 @@ impl TrieViewer { &public_key, 0, &empty_hash, + epoch_id, + last_block_hash, + epoch_info_provider, ); let context = VMContext { @@ -183,6 +189,7 @@ mod tests { }; use super::*; + use near_primitives::test_utils::MockEpochInfoProvider; #[test] fn test_view_call() { @@ -193,11 +200,14 @@ mod tests { root, 1, 1, + &CryptoHash::default(), 0, + &EpochId::default(), &AccountId::from("test.contract"), "run_test", &[], &mut logs, + &MockEpochInfoProvider::default(), ); assert_eq!(result.unwrap(), encode_int(10)); @@ -212,11 +222,14 @@ mod tests { root, 1, 1, + &CryptoHash::default(), 0, + &EpochId::default(), &"bad!contract".to_string(), "run_test", &[], &mut logs, + &MockEpochInfoProvider::default(), ); let err = result.unwrap_err(); @@ -235,11 +248,14 @@ mod tests { root, 1, 1, + &CryptoHash::default(), 0, + &EpochId::default(), &AccountId::from("test.contract"), "run_test_with_storage_change", &[], &mut logs, + &MockEpochInfoProvider::default(), ); let err = result.unwrap_err(); assert!( @@ -257,11 +273,14 @@ mod tests { root, 1, 1, + &CryptoHash::default(), 0, + &EpochId::default(), &AccountId::from("test.contract"), "sum_with_input", &args, &mut logs, + &MockEpochInfoProvider::default(), ); assert_eq!(view_call_result.unwrap(), 3u64.to_le_bytes().to_vec()); } @@ -333,11 +352,14 @@ mod tests { root, 1, 1, + &CryptoHash::default(), 0, + &EpochId::default(), &AccountId::from("test.contract"), "panic_after_logging", &[], &mut logs, + &MockEpochInfoProvider::default(), ) .unwrap_err(); diff --git a/runtime/runtime/tests/runtime_group_tools/mod.rs b/runtime/runtime/tests/runtime_group_tools/mod.rs index 14bfb843933..2c2c364e74e 100644 --- a/runtime/runtime/tests/runtime_group_tools/mod.rs +++ b/runtime/runtime/tests/runtime_group_tools/mod.rs @@ -3,6 +3,7 @@ use near_primitives::account::{AccessKey, Account}; use near_primitives::hash::{hash, CryptoHash}; use near_primitives::receipt::Receipt; use near_primitives::state_record::StateRecord; +use near_primitives::test_utils::MockEpochInfoProvider; use near_primitives::transaction::{ExecutionOutcomeWithId, SignedTransaction}; use near_primitives::types::Balance; use near_store::test_utils::create_tries; @@ -31,6 +32,7 @@ pub struct StandaloneRuntime { pub tries: ShardTries, pub signer: InMemorySigner, pub root: CryptoHash, + pub epoch_info_provider: MockEpochInfoProvider, } impl StandaloneRuntime { @@ -49,17 +51,23 @@ impl StandaloneRuntime { store_update.commit().unwrap(); let apply_state = ApplyState { - // Put each runtime into a separate shard. block_index: 0, - // Epoch length is long enough to avoid corner cases. - epoch_length: 4, + last_block_hash: Default::default(), + epoch_id: Default::default(), epoch_height: 0, gas_price: 100, block_timestamp: 0, gas_limit: None, }; - Self { apply_state, runtime, tries, signer, root: root } + Self { + apply_state, + runtime, + tries, + signer, + root, + epoch_info_provider: MockEpochInfoProvider::default(), + } } pub fn process_block( @@ -76,6 +84,7 @@ impl StandaloneRuntime { &self.apply_state, receipts, transactions, + &self.epoch_info_provider, ) .unwrap(); diff --git a/scripts/migrations/16-expose-validator-method.py b/scripts/migrations/16-expose-validator-method.py new file mode 100644 index 00000000000..30400a1b933 --- /dev/null +++ b/scripts/migrations/16-expose-validator-method.py @@ -0,0 +1,25 @@ +""" +Expose host functions for retrieving validator stake. + +No state migration needed for this change + +""" + +import sys +import os +import json +from collections import OrderedDict + +home = sys.argv[1] +output_home = sys.argv[2] + +config = json.load(open(os.path.join(home, 'output.json')), object_pairs_hook=OrderedDict) + +assert config['protocol_version'] == 15 + +config['protocol_version'] = 16 + +config['runtime_config']['wasm_config']['ext_costs']['validator_stake_base'] = 303944908800 +config['runtime_config']['wasm_config']['ext_costs']['validator_total_stake_base'] = 303944908800 + +json.dump(config, open(os.path.join(output_home, 'output.json'), 'w'), indent=2) diff --git a/test-utils/testlib/src/user/runtime_user.rs b/test-utils/testlib/src/user/runtime_user.rs index cf143f27fb1..1e1c142f85b 100644 --- a/test-utils/testlib/src/user/runtime_user.rs +++ b/test-utils/testlib/src/user/runtime_user.rs @@ -19,6 +19,7 @@ use node_runtime::state_viewer::TrieViewer; use node_runtime::{ApplyState, Runtime}; use crate::user::{User, POISONED_LOCK_ERR}; +use near_primitives::test_utils::MockEpochInfoProvider; use neard::config::MIN_GAS_PRICE; /// Mock client without chain, used in RuntimeUser and RuntimeNode @@ -45,6 +46,7 @@ pub struct RuntimeUser { // store receipts generated when applying transactions pub receipts: RefCell>, pub transactions: RefCell>, + pub epoch_info_provider: MockEpochInfoProvider, } impl RuntimeUser { @@ -57,6 +59,7 @@ impl RuntimeUser { transaction_results: Default::default(), receipts: Default::default(), transactions: RefCell::new(Default::default()), + epoch_info_provider: MockEpochInfoProvider::default(), } } @@ -82,6 +85,7 @@ impl RuntimeUser { &apply_state, &receipts, &txs, + &self.epoch_info_provider, ) .map_err(|e| match e { RuntimeError::InvalidTxError(e) => { @@ -93,6 +97,7 @@ impl RuntimeUser { panic!("UnexpectedIntegerOverflow error") } RuntimeError::ReceiptValidationError(e) => panic!("{}", e), + RuntimeError::ValidatorError(e) => panic!("{}", e), })?; for outcome_with_id in apply_result.outcomes { self.transaction_results @@ -113,14 +118,14 @@ impl RuntimeUser { } fn apply_state(&self) -> ApplyState { - let client = self.client.read().expect(POISONED_LOCK_ERR); ApplyState { block_index: 0, + last_block_hash: Default::default(), block_timestamp: 0, - epoch_length: client.epoch_length, epoch_height: 0, gas_price: MIN_GAS_PRICE, gas_limit: None, + epoch_id: Default::default(), } }