Skip to content

Commit

Permalink
[API] Add filter support for view and simulate
Browse files Browse the repository at this point in the history
  • Loading branch information
banool committed Jan 17, 2024
1 parent 72cffa4 commit 0c517d8
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 5 deletions.
17 changes: 16 additions & 1 deletion api/src/transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::{
api_disabled, api_forbidden, transaction_not_found_by_hash,
transaction_not_found_by_version, version_pruned, BadRequestError, BasicError,
BasicErrorWith404, BasicResponse, BasicResponseStatus, BasicResult, BasicResultWith404,
InsufficientStorageError, InternalError,
ForbiddenError, InsufficientStorageError, InternalError,
},
ApiTags,
};
Expand Down Expand Up @@ -443,6 +443,21 @@ impl TransactionsApi {
let ledger_info = context.get_latest_ledger_info()?;
let mut signed_transaction = api.get_signed_transaction(&ledger_info, data)?;

// Confirm the simulation filter allows the transaction. We use HashValue::zero()
// here for the block ID because we don't allow filtering by block ID for the
// simulation filters. See the ConfigSanitizer for ApiConfig.
if !context.node_config.api.simulation_filter.allows(
aptos_crypto::HashValue::zero(),
ledger_info.timestamp(),
&signed_transaction,
) {
return Err(SubmitTransactionError::forbidden_with_code(
"Transaction not allowed by simulation filter",
AptosErrorCode::InvalidInput,
&ledger_info,
));
}

let estimated_gas_unit_price = match (
estimate_gas_unit_price.0.unwrap_or_default(),
estimate_prioritized_gas_unit_price.0.unwrap_or_default(),
Expand Down
17 changes: 16 additions & 1 deletion api/src/view_function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::{
failpoint::fail_point_poem,
response::{
BadRequestError, BasicErrorWith404, BasicResponse, BasicResponseStatus, BasicResultWith404,
InternalError,
ForbiddenError, InternalError,
},
ApiTags, Context,
};
Expand Down Expand Up @@ -120,6 +120,21 @@ fn view_request(
},
};

// Reject the request if it's not allowed by the filter.
if !context.node_config.api.view_filter.allows(
view_function.module.address(),
view_function.module.name().as_str(),
view_function.function.as_str(),
) {
return Err(BasicErrorWith404::forbidden_with_code_no_info(
format!(
"Function {}::{} is not allowed",
view_function.module, view_function.function
),
AptosErrorCode::InvalidInput,
));
}

let return_vals = AptosVM::execute_view_function(
&state_view,
view_function.module.clone(),
Expand Down
61 changes: 60 additions & 1 deletion config/src/config/api_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
// Parts of the project are originally copyright © Meta Platforms, Inc.
// SPDX-License-Identifier: Apache-2.0

use super::transaction_filter_type::{Filter, Matcher};
use crate::{
config::{
config_sanitizer::ConfigSanitizer, gas_estimation_config::GasEstimationConfig,
node_config_loader::NodeType, Error, NodeConfig,
},
utils,
};
use aptos_types::chain_id::ChainId;
use aptos_types::{account_address::AccountAddress, chain_id::ChainId};
use serde::{Deserialize, Serialize};
use std::net::SocketAddr;

Expand Down Expand Up @@ -74,6 +75,10 @@ pub struct ApiConfig {
pub gas_estimation: GasEstimationConfig,
/// Periodically call gas estimation
pub periodic_gas_estimation_ms: Option<u64>,
/// Configuration to filter simulation requests.
pub simulation_filter: Filter,
/// Configuration to filter view function requests.
pub view_filter: ViewFilter,
}

const DEFAULT_ADDRESS: &str = "127.0.0.1";
Expand Down Expand Up @@ -119,6 +124,8 @@ impl Default for ApiConfig {
runtime_worker_multiplier: 2,
gas_estimation: GasEstimationConfig::default(),
periodic_gas_estimation_ms: Some(30_000),
simulation_filter: Filter::default(),
view_filter: ViewFilter::default(),
}
}
}
Expand Down Expand Up @@ -168,13 +175,65 @@ impl ConfigSanitizer for ApiConfig {
));
}

// We don't support Block ID based simulation filters.
for rule in api_config.simulation_filter.rules() {
if let Matcher::BlockId(_) = rule.matcher() {
return Err(Error::ConfigSanitizerFailed(
sanitizer_name,
"Block ID based simulation filters are not supported!".into(),
));
}
}

// Sanitize the gas estimation config
GasEstimationConfig::sanitize(node_config, node_type, chain_id)?;

Ok(())
}
}

// This is necessary because we can't import the EntryFunctionId type from the API types.
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub struct ViewFunctionId {
pub address: AccountAddress,
pub module: String,
pub function_name: String,
}

// We just accept Strings here because we can't import EntryFunctionId. We sanitize
// the values later.
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ViewFilter {
/// Allowlist of functions. If a function is not found here, the API will refuse to
/// service the view / simulation request.
Allowlist(Vec<ViewFunctionId>),
/// Blocklist of functions. If a function is found here, the API will refuse to
/// service the view / simulation request.
Blocklist(Vec<ViewFunctionId>),
}

impl Default for ViewFilter {
fn default() -> Self {
ViewFilter::Blocklist(vec![])
}
}

impl ViewFilter {
/// Returns true if the given function is allowed by the filter.
pub fn allows(&self, address: &AccountAddress, module: &str, function: &str) -> bool {
match self {
ViewFilter::Allowlist(ids) => ids.iter().any(|id| {
&id.address == address && id.module == module && id.function_name == function
}),
ViewFilter::Blocklist(ids) => !ids.iter().any(|id| {
&id.address == address && id.module == module && id.function_name == function
}),
}
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
17 changes: 15 additions & 2 deletions config/src/config/transaction_filter_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use aptos_types::{
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
enum Matcher {
pub enum Matcher {
All,
BlockId(HashValue),
BlockTimeStampGreaterThan(u64),
Expand Down Expand Up @@ -48,11 +48,20 @@ impl Matcher {
}

#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
enum Rule {
pub enum Rule {
Allow(Matcher),
Deny(Matcher),
}

impl Rule {
pub fn matcher(&self) -> &Matcher {
match self {
Rule::Allow(matcher) => matcher,
Rule::Deny(matcher) => matcher,
}
}
}

enum EvalResult {
Allow,
Deny,
Expand Down Expand Up @@ -188,6 +197,10 @@ impl Filter {
self
}

pub fn rules(&self) -> &[Rule] {
&self.rules
}

pub fn allows(&self, block_id: HashValue, timestamp: u64, txn: &SignedTransaction) -> bool {
for rule in &self.rules {
// Rules are evaluated in the order and the first rule that matches is used. If no rule
Expand Down

0 comments on commit 0c517d8

Please sign in to comment.