Skip to content

Commit

Permalink
feat(rpc): viewing large contract state is now prohibted to avoid exc…
Browse files Browse the repository at this point in the history
…essive resource consumption (near#4167)

Fixes near#3950.

Test plan
---------
* `test_view_state_too_large`
* Nayduck
  • Loading branch information
bowenwang1996 authored Apr 1, 2021
1 parent 78ac291 commit 1471192
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 0 deletions.
6 changes: 6 additions & 0 deletions chain/chain-primitives/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ pub enum QueryError {
block_height: near_primitives::types::BlockHeight,
block_hash: near_primitives::hash::CryptoHash,
},
#[error("The state of account {requested_account_id} is too large")]
TooLargeContractState {
requested_account_id: near_primitives::types::AccountId,
block_height: near_primitives::types::BlockHeight,
block_hash: near_primitives::hash::CryptoHash,
},
}

#[derive(Debug)]
Expand Down
6 changes: 6 additions & 0 deletions chain/client-primitives/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,12 @@ pub enum QueryError {
block_height: near_primitives::types::BlockHeight,
block_hash: near_primitives::hash::CryptoHash,
},
#[error("State of contract {contract_account_id} is too large to be viewed")]
TooLargeContractState {
contract_account_id: near_primitives::types::AccountId,
block_height: near_primitives::types::BlockHeight,
block_hash: near_primitives::hash::CryptoHash,
},
#[error("Access key for public key {public_key} has never been observed on the node at block #{block_height}")]
UnknownAccessKey {
public_key: near_crypto::PublicKey,
Expand Down
9 changes: 9 additions & 0 deletions chain/client/src/view_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,15 @@ impl ViewClientActor {
block_height,
block_hash,
},
near_chain::near_chain_primitives::error::QueryError::TooLargeContractState {
requested_account_id,
block_height,
block_hash,
} => QueryError::TooLargeContractState {
contract_account_id: requested_account_id,
block_height,
block_hash,
},
}),
}
}
Expand Down
11 changes: 11 additions & 0 deletions chain/jsonrpc-primitives/src/types/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ pub enum RpcQueryError {
block_height: near_primitives::types::BlockHeight,
block_hash: near_primitives::hash::CryptoHash,
},
#[error("State of contract {contract_account_id} is too large to be viewed")]
TooLargeContractState {
contract_account_id: near_primitives::types::AccountId,
block_height: near_primitives::types::BlockHeight,
block_hash: near_primitives::hash::CryptoHash,
},
#[error("Access key for public key {public_key} has never been observed on the node")]
UnknownAccessKey {
public_key: near_crypto::PublicKey,
Expand Down Expand Up @@ -201,6 +207,11 @@ impl From<near_client_primitives::types::QueryError> for RpcQueryError {
);
Self::Unreachable { error_message }
}
near_client_primitives::types::QueryError::TooLargeContractState {
contract_account_id,
block_height,
block_hash,
} => Self::TooLargeContractState { contract_account_id, block_height, block_hash },
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions neard/src/runtime/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ impl QueryError {
node_runtime::state_viewer::errors::ViewStateError::InternalError { error_message } => {
Self::InternalError { error_message, block_height, block_hash }
}
node_runtime::state_viewer::errors::ViewStateError::AccountDoesNotExist {
requested_account_id,
} => Self::UnknownAccount { requested_account_id, block_height, block_hash },
node_runtime::state_viewer::errors::ViewStateError::AccountStateTooLarge {
requested_account_id,
} => Self::TooLargeContractState { requested_account_id, block_height, block_hash },
}
}

Expand Down
4 changes: 4 additions & 0 deletions runtime/runtime/src/state_viewer/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ pub enum ViewAccessKeyError {
pub enum ViewStateError {
#[error("Account ID \"{requested_account_id}\" is invalid")]
InvalidAccountId { requested_account_id: near_primitives::types::AccountId },
#[error("Account {requested_account_id} does not exist")]
AccountDoesNotExist { requested_account_id: near_primitives::types::AccountId },
#[error("The state of {requested_account_id} is too large")]
AccountStateTooLarge { requested_account_id: near_primitives::types::AccountId },
#[error("Internal error: #{error_message}")]
InternalError { error_message: String },
}
Expand Down
54 changes: 54 additions & 0 deletions runtime/runtime/src/state_viewer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ pub mod errors;
pub struct TrieViewer {}

impl TrieViewer {
/// Upper bound of the size of contract state that is still viewable.
const CONTRACT_STATE_SIZE_LIMIT: u64 = 50_000;

pub fn new() -> Self {
Self {}
}
Expand Down Expand Up @@ -124,6 +127,24 @@ impl TrieViewer {
requested_account_id: account_id.clone(),
});
}
match get_account(state_update, account_id)? {
Some(account) => {
let code_len = get_code(state_update, account_id, Some(account.code_hash()))?
.map(|c| c.code.len() as u64)
.unwrap_or_default();
if account.storage_usage() > Self::CONTRACT_STATE_SIZE_LIMIT + code_len {
return Err(errors::ViewStateError::AccountStateTooLarge {
requested_account_id: account_id.clone(),
});
}
}
None => {
return Err(errors::ViewStateError::AccountDoesNotExist {
requested_account_id: account_id.clone(),
})
}
};

let mut values = vec![];
let query = trie_key_parsers::get_raw_prefix_for_contract_data(account_id, prefix);
let acc_sep_len = query.len() - prefix.len();
Expand Down Expand Up @@ -269,6 +290,7 @@ mod tests {
};

use super::*;
use near_store::set_account;

#[test]
fn test_view_call() {
Expand Down Expand Up @@ -455,6 +477,38 @@ mod tests {
);
}

#[test]
fn test_view_state_too_large() {
let (_, tries, root) = get_runtime_and_trie();
let mut state_update = tries.new_trie_update(0, root);
set_account(
&mut state_update,
alice_account(),
&Account::new(0, 0, CryptoHash::default(), TrieViewer::CONTRACT_STATE_SIZE_LIMIT + 1),
);
let trie_viewer = TrieViewer::new();
let result = trie_viewer.view_state(&state_update, &alice_account(), b"");
assert!(matches!(result, Err(errors::ViewStateError::AccountStateTooLarge { .. })));
}

#[test]
fn test_view_state_with_large_contract() {
let (_, tries, root) = get_runtime_and_trie();
let mut state_update = tries.new_trie_update(0, root);
set_account(
&mut state_update,
alice_account(),
&Account::new(0, 0, CryptoHash::default(), TrieViewer::CONTRACT_STATE_SIZE_LIMIT + 1),
);
state_update.set(
TrieKey::ContractCode { account_id: alice_account() },
[0; Account::MAX_ACCOUNT_DELETION_STORAGE_USAGE as usize].to_vec(),
);
let trie_viewer = TrieViewer::new();
let result = trie_viewer.view_state(&state_update, &alice_account(), b"");
assert!(result.is_ok());
}

#[test]
fn test_log_when_panic() {
let (viewer, root) = get_test_trie_viewer();
Expand Down

0 comments on commit 1471192

Please sign in to comment.