Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions node/rest/src/helpers/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,32 @@ use axum::{
response::{IntoResponse, Response},
};

/// An enum of error handlers for the REST API server.
pub struct RestError(pub String);
/// An error handler for the REST API server.
pub struct RestError {
pub status: StatusCode,
pub message: String,
}

impl RestError {
/// Creates a new `RestError` with an internal server error status.
pub fn new(message: impl Into<String>) -> Self {
Self { status: StatusCode::INTERNAL_SERVER_ERROR, message: message.into() }
}

/// Creates a new `RestError` with the given status code.
pub fn with_status(status: StatusCode, message: impl Into<String>) -> Self {
Self { status, message: message.into() }
}
}

impl IntoResponse for RestError {
fn into_response(self) -> Response {
(StatusCode::INTERNAL_SERVER_ERROR, format!("Something went wrong: {}", self.0)).into_response()
(self.status, format!("Something went wrong: {}", self.message)).into_response()
}
}

impl From<anyhow::Error> for RestError {
fn from(err: anyhow::Error) -> Self {
Self(err.to_string())
Self::new(err.to_string())
}
}
69 changes: 44 additions & 25 deletions node/rest/src/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
// limitations under the License.

use super::*;
use axum::http::StatusCode;
use snarkos_node_router::messages::UnconfirmedSolution;
use snarkvm::{
ledger::puzzle::Solution,
Expand Down Expand Up @@ -114,9 +115,9 @@ impl<N: Network, C: ConsensusStorage<N>, R: Routing<N>> Rest<N, C, R> {
let block = if let Ok(height) = height_or_hash.parse::<u32>() {
rest.ledger.get_block(height)?
} else {
let hash = height_or_hash
.parse::<N::BlockHash>()
.map_err(|_| RestError("invalid input, it is neither a block height nor a block hash".to_string()))?;
let hash = height_or_hash.parse::<N::BlockHash>().map_err(|_| {
RestError::new("invalid input, it is neither a block height nor a block hash".to_string())
})?;

rest.ledger.get_block_by_hash(&hash)?
};
Expand All @@ -136,17 +137,28 @@ impl<N: Network, C: ConsensusStorage<N>, R: Routing<N>> Rest<N, C, R> {

// Ensure the end height is greater than the start height.
if start_height > end_height {
return Err(RestError("Invalid block range".to_string()));
return Err(RestError::new("Invalid block range".to_string()));
}

// Ensure the block range is bounded.
if end_height - start_height > MAX_BLOCK_RANGE {
return Err(RestError(format!(
return Err(RestError::new(format!(
"Cannot request more than {MAX_BLOCK_RANGE} blocks per call (requested {})",
end_height - start_height
)));
}

// Determine the latest block height in the ledger.
let latest_height = rest.ledger.latest_height();
if start_height > latest_height || end_height > latest_height.saturating_add(1) {
return Err(RestError::with_status(
StatusCode::NOT_FOUND,
format!(
"Requested block range {start_height}..{end_height} is out of bounds (latest height {latest_height})"
),
));
}

// Prepare a closure for the blocking work.
let get_json_blocks = move || -> Result<ErasedJson, RestError> {
let blocks = cfg_into_iter!(start_height..end_height)
Expand All @@ -159,7 +171,7 @@ impl<N: Network, C: ConsensusStorage<N>, R: Routing<N>> Rest<N, C, R> {
// Fetch the blocks from ledger and serialize to json.
match tokio::task::spawn_blocking(get_json_blocks).await {
Ok(json) => json,
Err(err) => Err(RestError(format!("Failed to get blocks '{start_height}..{end_height}' - {err}"))),
Err(err) => Err(RestError::new(format!("Failed to get blocks '{start_height}..{end_height}' - {err}"))),
}
}

Expand Down Expand Up @@ -264,23 +276,23 @@ impl<N: Network, C: ConsensusStorage<N>, R: Routing<N>> Rest<N, C, R> {
Some(consensus) => {
Ok(ErasedJson::pretty(consensus.unconfirmed_transmissions().collect::<IndexMap<_, _>>()))
}
None => Err(RestError("Route isn't available for this node type".to_string())),
None => Err(RestError::new("Route isn't available for this node type".to_string())),
}
}

// GET /<network>/memoryPool/solutions
pub(crate) async fn get_memory_pool_solutions(State(rest): State<Self>) -> Result<ErasedJson, RestError> {
match rest.consensus {
Some(consensus) => Ok(ErasedJson::pretty(consensus.unconfirmed_solutions().collect::<IndexMap<_, _>>())),
None => Err(RestError("Route isn't available for this node type".to_string())),
None => Err(RestError::new("Route isn't available for this node type".to_string())),
}
}

// GET /<network>/memoryPool/transactions
pub(crate) async fn get_memory_pool_transactions(State(rest): State<Self>) -> Result<ErasedJson, RestError> {
match rest.consensus {
Some(consensus) => Ok(ErasedJson::pretty(consensus.unconfirmed_transactions().collect::<IndexMap<_, _>>())),
None => Err(RestError("Route isn't available for this node type".to_string())),
None => Err(RestError::new("Route isn't available for this node type".to_string())),
}
}

Expand Down Expand Up @@ -392,7 +404,9 @@ impl<N: Network, C: ConsensusStorage<N>, R: Routing<N>> Rest<N, C, R> {
) -> Result<ErasedJson, RestError> {
// Return an error if the `all` query parameter is not set to `true`.
if metadata.all != Some(true) {
return Err(RestError("Invalid query parameter. At this time, 'all=true' must be included".to_string()));
return Err(RestError::new(
"Invalid query parameter. At this time, 'all=true' must be included".to_string(),
));
}

// Retrieve the latest height.
Expand All @@ -414,8 +428,8 @@ impl<N: Network, C: ConsensusStorage<N>, R: Routing<N>> Rest<N, C, R> {
// Return the full mapping without metadata.
Ok(ErasedJson::pretty(mapping_values))
}
Ok(Err(err)) => Err(RestError(format!("Unable to read mapping - {err}"))),
Err(err) => Err(RestError(format!("Unable to read mapping - {err}"))),
Ok(Err(err)) => Err(RestError::new(format!("Unable to read mapping - {err}"))),
Err(err) => Err(RestError::new(format!("Unable to read mapping - {err}"))),
}
}

Expand Down Expand Up @@ -460,14 +474,14 @@ impl<N: Network, C: ConsensusStorage<N>, R: Routing<N>> Rest<N, C, R> {
) -> Result<ErasedJson, RestError> {
// Do not process the request if the node is too far behind to avoid sending outdated data.
if !rest.routing.is_within_sync_leniency() {
return Err(RestError("Unable to request delegators (node is syncing)".to_string()));
return Err(RestError::new("Unable to request delegators (node is syncing)".to_string()));
}

// Return the delegators for the given validator.
match tokio::task::spawn_blocking(move || rest.ledger.get_delegators_for_validator(&validator)).await {
Ok(Ok(delegators)) => Ok(ErasedJson::pretty(delegators)),
Ok(Err(err)) => Err(RestError(format!("Unable to request delegators - {err}"))),
Err(err) => Err(RestError(format!("Unable to request delegators - {err}"))),
Ok(Err(err)) => Err(RestError::new(format!("Unable to request delegators - {err}"))),
Err(err) => Err(RestError::new(format!("Unable to request delegators - {err}"))),
}
}

Expand Down Expand Up @@ -548,7 +562,10 @@ impl<N: Network, C: ConsensusStorage<N>, R: Routing<N>> Rest<N, C, R> {
) -> Result<ErasedJson, RestError> {
// Do not process the transaction if the node is too far behind.
if !rest.routing.is_within_sync_leniency() {
return Err(RestError(format!("Unable to broadcast transaction '{}' (node is syncing)", fmt_id(tx.id()))));
return Err(RestError::new(format!(
"Unable to broadcast transaction '{}' (node is syncing)",
fmt_id(tx.id())
)));
}

// If the transaction exceeds the transaction size limit, return an error.
Expand All @@ -557,7 +574,7 @@ impl<N: Network, C: ConsensusStorage<N>, R: Routing<N>> Rest<N, C, R> {
// TODO: Should this be a blocking task?
let buffer = Vec::with_capacity(3000);
if tx.write_le(LimitedWriter::new(buffer, N::MAX_TRANSACTION_SIZE)).is_err() {
return Err(RestError("Transaction size exceeds the byte limit".to_string()));
return Err(RestError::new("Transaction size exceeds the byte limit".to_string()));
}

if check_transaction.check_transaction.unwrap_or(false) {
Expand All @@ -581,13 +598,13 @@ impl<N: Network, C: ConsensusStorage<N>, R: Routing<N>> Rest<N, C, R> {
let prev = counter.fetch_add(1, Ordering::Relaxed);
if prev >= limit {
counter.fetch_sub(1, Ordering::Relaxed);
return Err(RestError(err_msg.to_string()));
return Err(RestError::new(err_msg.to_string()));
}
// Perform the check.
let res = rest
.ledger
.check_transaction_basic(&tx, None, &mut rand::thread_rng())
.map_err(|e| RestError(format!("Invalid transaction: {e}")));
.map_err(|e| RestError::new(format!("Invalid transaction: {e}")));
// Release the slot.
counter.fetch_sub(1, Ordering::Relaxed);
// Propagate error if any.
Expand Down Expand Up @@ -620,7 +637,7 @@ impl<N: Network, C: ConsensusStorage<N>, R: Routing<N>> Rest<N, C, R> {
) -> Result<ErasedJson, RestError> {
// Do not process the solution if the node is too far behind.
if !rest.routing.is_within_sync_leniency() {
return Err(RestError(format!(
return Err(RestError::new(format!(
"Unable to broadcast solution '{}' (node is syncing)",
fmt_id(solution.id())
)));
Expand All @@ -644,7 +661,7 @@ impl<N: Network, C: ConsensusStorage<N>, R: Routing<N>> Rest<N, C, R> {
// here prevents the to-be aborted solutions from propagating through the network.
let prover_address = solution.address();
if rest.ledger.is_solution_limit_reached(&prover_address, 0) {
return Err(RestError(format!(
return Err(RestError::new(format!(
"Invalid solution '{}' - Prover '{prover_address}' has reached their solution limit for the current epoch",
fmt_id(solution.id())
)));
Expand All @@ -655,9 +672,11 @@ impl<N: Network, C: ConsensusStorage<N>, R: Routing<N>> Rest<N, C, R> {
{
Ok(Ok(())) => {}
Ok(Err(err)) => {
return Err(RestError(format!("Invalid solution '{}' - {err}", fmt_id(solution.id()))));
return Err(RestError::new(format!("Invalid solution '{}' - {err}", fmt_id(solution.id()))));
}
Err(err) => {
return Err(RestError::new(format!("Invalid solution '{}' - {err}", fmt_id(solution.id()))));
}
Err(err) => return Err(RestError(format!("Invalid solution '{}' - {err}", fmt_id(solution.id())))),
}
}
}
Expand Down Expand Up @@ -693,7 +712,7 @@ impl<N: Network, C: ConsensusStorage<N>, R: Routing<N>> Rest<N, C, R> {
let history = snarkvm::synthesizer::History::new(N::ID, rest.ledger.vm().finalize_store().storage_mode());
let result = history
.load_mapping(height, mapping)
.map_err(|_| RestError(format!("Could not load mapping '{mapping}' from block '{height}'")))?;
.map_err(|_| RestError::new(format!("Could not load mapping '{mapping}' from block '{height}'")))?;

Ok((StatusCode::OK, [(CONTENT_TYPE, "application/json")], result))
}
Expand Down Expand Up @@ -727,7 +746,7 @@ impl<N: Network, C: ConsensusStorage<N>, R: Routing<N>> Rest<N, C, R> {

Ok(ErasedJson::pretty(participation_scores))
}
None => Err(RestError("Route isn't available for this node type".to_string())),
None => Err(RestError::new("Route isn't available for this node type".to_string())),
}
}
}