use super::*;
use snarkos_node_network::PeerPoolHandling;
use snarkos_node_router::messages::UnconfirmedSolution;
#[cfg(feature = "history-staking-rewards")]
use snarkvm::ledger::store::helpers::MapRead;
use snarkvm::{
ledger::puzzle::Solution,
prelude::{Address, Identifier, LimitedWriter, Plaintext, Program, ToBytes, block::Transaction},
};
use axum::{Json, extract::rejection::JsonRejection};
use aleo_std::aleo_ledger_dir;
use anyhow::{Context, anyhow};
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use serde_json::json;
use serde_with::skip_serializing_none;
use std::{collections::HashMap, fs};
#[cfg(not(feature = "serial"))]
use rayon::prelude::*;
use version::VersionInfo;
#[cfg(feature = "history")]
type HistoricalMappingKey<N> = (ProgramID<N>, Identifier<N>, Plaintext<N>, u32);
fn de_csv<'de, D>(de: D) -> std::result::Result<Vec<String>, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(de)?;
Ok(if s.trim().is_empty() { Vec::new() } else { s.split(',').map(|x| x.trim().to_string()).collect() })
}
#[derive(Deserialize, Serialize)]
pub(crate) struct BlockRange {
start: u32,
end: u32,
}
#[derive(Deserialize, Serialize)]
pub(crate) struct BackupPath {
path: std::path::PathBuf,
}
#[derive(Copy, Clone, Deserialize, Serialize)]
pub(crate) struct Metadata {
metadata: Option<bool>,
all: Option<bool>,
}
#[derive(Copy, Clone, Deserialize, Serialize)]
pub(crate) struct CheckTransaction {
check_transaction: Option<bool>,
}
#[derive(Copy, Clone, Deserialize, Serialize)]
pub(crate) struct CheckSolution {
check_solution: Option<bool>,
}
#[derive(Clone, Deserialize, Serialize)]
pub(crate) struct Commitments {
#[serde(deserialize_with = "de_csv")]
commitments: Vec<String>,
}
#[skip_serializing_none]
#[derive(Copy, Clone, Serialize)]
struct SyncStatus<'a> {
is_synced: bool,
ledger_height: u32,
sync_mode: &'a str,
cdn_height: Option<u32>,
p2p_height: Option<u32>,
outstanding_block_requests: usize,
sync_speed_bps: f64,
}
impl<N: Network, C: ConsensusStorage<N>, R: Routing<N>> Rest<N, C, R> {
pub(crate) async fn get_version() -> ErasedJson {
ErasedJson::pretty(VersionInfo::get::<N>())
}
pub(crate) async fn get_consensus_version(State(rest): State<Self>) -> Result<ErasedJson, RestError> {
Ok(ErasedJson::pretty(N::CONSENSUS_VERSION(rest.ledger.latest_height())? as u16))
}
pub(crate) async fn get_block_height_latest(State(rest): State<Self>) -> ErasedJson {
ErasedJson::pretty(rest.ledger.latest_height())
}
pub(crate) async fn get_block_hash_latest(State(rest): State<Self>) -> ErasedJson {
ErasedJson::pretty(rest.ledger.latest_hash())
}
pub(crate) async fn get_block_latest(State(rest): State<Self>) -> ErasedJson {
ErasedJson::pretty(rest.ledger.latest_block())
}
pub(crate) async fn get_block(
State(rest): State<Self>,
Path(height_or_hash): Path<String>,
) -> Result<ErasedJson, RestError> {
let id_name;
let block = if let Ok(height) = height_or_hash.parse::<u32>() {
id_name = "hash";
rest.ledger.try_get_block(height).with_context(|| "Failed to get block by height")?
} else if let Ok(hash) = height_or_hash.parse::<N::BlockHash>() {
id_name = "height";
rest.ledger.try_get_block_by_hash(&hash).with_context(|| "Failed to get block by hash")?
} else {
return Err(RestError::bad_request(anyhow!(
"invalid input, it is neither a block height nor a block hash"
)));
};
match block {
Some(block) => Ok(ErasedJson::pretty(block)),
None => Err(RestError::not_found(anyhow!("No block with {id_name} {height_or_hash} found"))),
}
}
pub(crate) async fn get_blocks(
State(rest): State<Self>,
Query(block_range): Query<BlockRange>,
) -> Result<ErasedJson, RestError> {
let start_height = block_range.start;
let end_height = block_range.end;
const MAX_BLOCK_RANGE: u32 = 50;
if start_height > end_height {
return Err(RestError::bad_request(anyhow!("Invalid block range")));
}
if end_height - start_height > MAX_BLOCK_RANGE {
return Err(RestError::bad_request(anyhow!(
"Cannot request more than {MAX_BLOCK_RANGE} blocks per call (requested {})",
end_height - start_height
)));
}
let get_json_blocks = move || -> Result<ErasedJson, RestError> {
let blocks = cfg_into_iter!(start_height..end_height)
.map(|height| rest.ledger.get_block(height))
.collect::<Result<Vec<_>, _>>()?;
Ok(ErasedJson::pretty(blocks))
};
match tokio::task::spawn_blocking(get_json_blocks).await {
Ok(json) => json,
Err(err) => {
let err: anyhow::Error = err.into();
Err(RestError::internal_server_error(
err.context(format!("Failed to get blocks '{start_height}..{end_height}'")),
))
}
}
}
pub(crate) async fn get_sync_status(State(rest): State<Self>) -> Result<ErasedJson, RestError> {
let (cdn_sync, cdn_height) = if let Some(cdn_sync) = &rest.cdn_sync {
let done = cdn_sync.is_done();
let cdn_height = if done { None } else { Some(cdn_sync.get_cdn_height().await?) };
(!done, cdn_height)
} else {
(false, None)
};
let sync_mode = if cdn_sync { "cdn" } else { "p2p" };
Ok(ErasedJson::pretty(SyncStatus {
sync_mode,
cdn_height,
is_synced: !cdn_sync && rest.routing.is_block_synced(),
ledger_height: rest.ledger.latest_height(),
p2p_height: rest.block_sync.greatest_peer_block_height(),
outstanding_block_requests: rest.block_sync.num_outstanding_block_requests(),
sync_speed_bps: rest.block_sync.get_sync_speed(),
}))
}
pub(crate) async fn get_sync_peers(State(rest): State<Self>) -> Result<ErasedJson, RestError> {
let peers: HashMap<String, u32> =
rest.block_sync.get_peer_heights().into_iter().map(|(addr, height)| (addr.to_string(), height)).collect();
Ok(ErasedJson::pretty(peers))
}
pub(crate) async fn get_sync_requests_summary(State(rest): State<Self>) -> Result<ErasedJson, RestError> {
let summary = rest.block_sync.get_block_requests_summary();
Ok(ErasedJson::pretty(summary))
}
pub(crate) async fn get_sync_requests_list(State(rest): State<Self>) -> Result<ErasedJson, RestError> {
let requests = rest.block_sync.get_block_requests_info();
Ok(ErasedJson::pretty(requests))
}
pub(crate) async fn get_height(
State(rest): State<Self>,
Path(hash): Path<N::BlockHash>,
) -> Result<ErasedJson, RestError> {
Ok(ErasedJson::pretty(rest.ledger.get_height(&hash)?))
}
pub(crate) async fn get_block_header(
State(rest): State<Self>,
Path(height): Path<u32>,
) -> Result<ErasedJson, RestError> {
Ok(ErasedJson::pretty(rest.ledger.get_header(height)?))
}
pub(crate) async fn get_block_transactions(
State(rest): State<Self>,
Path(height): Path<u32>,
) -> Result<ErasedJson, RestError> {
Ok(ErasedJson::pretty(rest.ledger.get_transactions(height)?))
}
pub(crate) async fn get_transaction(
State(rest): State<Self>,
Path(tx_id): Path<N::TransactionID>,
) -> Result<ErasedJson, RestError> {
Ok(ErasedJson::pretty(rest.ledger.get_transaction(tx_id).map_err(|err| {
if err.to_string().contains("Missing") { RestError::not_found(err) } else { RestError::from(err) }
})?))
}
pub(crate) async fn get_confirmed_transaction(
State(rest): State<Self>,
Path(tx_id): Path<N::TransactionID>,
) -> Result<ErasedJson, RestError> {
Ok(ErasedJson::pretty(rest.ledger.get_confirmed_transaction(tx_id).map_err(|err| {
if err.to_string().contains("Missing") { RestError::not_found(err) } else { RestError::from(err) }
})?))
}
pub(crate) async fn get_unconfirmed_transaction(
State(rest): State<Self>,
Path(tx_id): Path<N::TransactionID>,
) -> Result<ErasedJson, RestError> {
Ok(ErasedJson::pretty(rest.ledger.get_unconfirmed_transaction(&tx_id).map_err(|err| {
if err.to_string().contains("Missing") { RestError::not_found(err) } else { RestError::from(err) }
})?))
}
pub(crate) async fn get_memory_pool_transmissions(State(rest): State<Self>) -> Result<ErasedJson, RestError> {
match rest.consensus {
Some(consensus) => {
Ok(ErasedJson::pretty(consensus.unconfirmed_transmissions().collect::<IndexMap<_, _>>()))
}
None => Err(RestError::service_unavailable(anyhow!("Route isn't available for this node type"))),
}
}
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::service_unavailable(anyhow!("Route isn't available for this node type"))),
}
}
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::service_unavailable(anyhow!("Route isn't available for this node type"))),
}
}
pub(crate) async fn get_program(
State(rest): State<Self>,
Path(id): Path<ProgramID<N>>,
metadata: Query<Metadata>,
) -> Result<ErasedJson, RestError> {
let program = rest.ledger.get_program(id).with_context(|| format!("Failed to find program `{id}`"))?;
if metadata.metadata.unwrap_or(false) {
let edition = rest.ledger.get_latest_edition_for_program(&id)?;
return rest.return_program_with_metadata(program, edition);
}
Ok(ErasedJson::pretty(program))
}
pub(crate) async fn get_program_for_edition(
State(rest): State<Self>,
Path((id, edition)): Path<(ProgramID<N>, u16)>,
metadata: Query<Metadata>,
) -> Result<ErasedJson, RestError> {
match rest
.ledger
.try_get_program_for_edition(&id, edition)
.with_context(|| format!("Failed get program `{id}` for edition {edition}"))?
{
Some(program) => {
if metadata.metadata.unwrap_or(false) {
rest.return_program_with_metadata(program, edition)
} else {
Ok(ErasedJson::pretty(program))
}
}
None => Err(RestError::not_found(anyhow!("No program `{id}` exists for edition {edition}"))),
}
}
fn return_program_with_metadata(&self, program: Program<N>, edition: u16) -> Result<ErasedJson, RestError> {
let id = program.id();
let tx_id = self.ledger.find_latest_transaction_id_from_program_id_and_edition(id, edition)?;
let program_owner = match &tx_id {
Some(tid) => self
.ledger
.vm()
.block_store()
.transaction_store()
.deployment_store()
.get_deployment(tid)?
.and_then(|deployment| deployment.program_owner()),
None => None,
};
let amendment_count =
self.ledger.vm().block_store().transaction_store().get_amendment_count(id, edition)?.unwrap_or(0);
Ok(ErasedJson::pretty(json!({
"program": program,
"edition": edition,
"transaction_id": tx_id,
"program_owner": program_owner,
"amendment_count": amendment_count,
})))
}
pub(crate) async fn get_latest_program_edition(
State(rest): State<Self>,
Path(id): Path<ProgramID<N>>,
) -> Result<ErasedJson, RestError> {
Ok(ErasedJson::pretty(rest.ledger.get_latest_edition_for_program(&id)?))
}
pub(crate) async fn get_mapping_names(
State(rest): State<Self>,
Path(id): Path<ProgramID<N>>,
) -> Result<ErasedJson, RestError> {
Ok(ErasedJson::pretty(rest.ledger.vm().finalize_store().get_mapping_names_confirmed(&id)?))
}
pub(crate) async fn get_mapping_value(
State(rest): State<Self>,
Path((id, name, key)): Path<(ProgramID<N>, Identifier<N>, Plaintext<N>)>,
metadata: Query<Metadata>,
) -> Result<ErasedJson, RestError> {
let mapping_value = rest.ledger.vm().finalize_store().get_value_confirmed(id, name, &key)?;
if metadata.metadata.unwrap_or(false) {
return Ok(ErasedJson::pretty(json!({
"data": mapping_value,
"height": rest.ledger.latest_height(),
})));
}
Ok(ErasedJson::pretty(mapping_value))
}
pub(crate) async fn get_mapping_values(
State(rest): State<Self>,
Path((id, name)): Path<(ProgramID<N>, Identifier<N>)>,
metadata: Query<Metadata>,
) -> Result<ErasedJson, RestError> {
if metadata.all != Some(true) {
return Err(RestError::bad_request(anyhow!(
"Invalid query parameter. At this time, 'all=true' must be included"
)));
}
let height = rest.ledger.latest_height();
match tokio::task::spawn_blocking(move || rest.ledger.vm().finalize_store().get_mapping_confirmed(id, name))
.await
{
Ok(Ok(mapping_values)) => {
if metadata.metadata.unwrap_or(false) {
return Ok(ErasedJson::pretty(json!({
"data": mapping_values,
"height": height,
})));
}
Ok(ErasedJson::pretty(mapping_values))
}
Ok(Err(err)) => Err(RestError::internal_server_error(err.context("Unable to read mapping"))),
Err(err) => Err(RestError::internal_server_error(anyhow!("Tokio error: {err}"))),
}
}
pub(crate) async fn get_program_amendment_count(
State(rest): State<Self>,
Path(id): Path<ProgramID<N>>,
) -> Result<ErasedJson, RestError> {
let edition = rest.ledger.get_latest_edition_for_program(&id)?;
let amendment_count =
rest.ledger.vm().block_store().transaction_store().get_amendment_count(&id, edition)?.unwrap_or(0);
Ok(ErasedJson::pretty(json!({
"program_id": id,
"edition": edition,
"amendment_count": amendment_count,
})))
}
pub(crate) async fn get_program_amendment_count_for_edition(
State(rest): State<Self>,
Path((id, edition)): Path<(ProgramID<N>, u16)>,
) -> Result<ErasedJson, RestError> {
let amendment_count =
rest.ledger.vm().block_store().transaction_store().get_amendment_count(&id, edition)?.unwrap_or(0);
Ok(ErasedJson::pretty(json!({
"program_id": id,
"edition": edition,
"amendment_count": amendment_count,
})))
}
pub(crate) async fn get_state_path_for_commitment(
State(rest): State<Self>,
Path(commitment): Path<Field<N>>,
) -> Result<ErasedJson, RestError> {
Ok(ErasedJson::pretty(rest.ledger.get_state_path_for_commitment(&commitment)?))
}
pub(crate) async fn get_state_paths_for_commitments(
State(rest): State<Self>,
Query(commitments): Query<Commitments>,
) -> Result<ErasedJson, RestError> {
let num_commitments = commitments.commitments.len();
if num_commitments == 0 {
return Err(RestError::unprocessable_entity(anyhow!("No commitments provided")));
}
if num_commitments > N::MAX_INPUTS {
return Err(RestError::unprocessable_entity(anyhow!(
"Too many commitments provided (max: {}, got: {})",
N::MAX_INPUTS,
num_commitments
)));
}
let commitments = commitments
.commitments
.iter()
.map(|s| {
s.parse::<Field<N>>()
.map_err(|err| RestError::unprocessable_entity(err.context(format!("Invalid commitment: {s}"))))
})
.collect::<Result<Vec<_>, _>>()?;
Ok(ErasedJson::pretty(rest.ledger.get_state_paths_for_commitments(&commitments)?))
}
pub(crate) async fn get_state_root_latest(State(rest): State<Self>) -> ErasedJson {
ErasedJson::pretty(rest.ledger.latest_state_root())
}
pub(crate) async fn get_state_root(
State(rest): State<Self>,
Path(height): Path<u32>,
) -> Result<ErasedJson, RestError> {
Ok(ErasedJson::pretty(rest.ledger.get_state_root(height)?))
}
pub(crate) async fn get_committee_latest(State(rest): State<Self>) -> Result<ErasedJson, RestError> {
Ok(ErasedJson::pretty(rest.ledger.latest_committee()?))
}
pub(crate) async fn get_committee(
State(rest): State<Self>,
Path(height): Path<u32>,
) -> Result<ErasedJson, RestError> {
Ok(ErasedJson::pretty(rest.ledger.get_committee(height)?))
}
pub(crate) async fn get_delegators_for_validator(
State(rest): State<Self>,
Path(validator): Path<Address<N>>,
) -> Result<ErasedJson, RestError> {
if !rest.routing.is_within_sync_leniency() {
return Err(RestError::service_unavailable(anyhow!("Unable to request delegators (node is syncing)")));
}
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::internal_server_error(err.context("Unable to request delegators"))),
Err(err) => Err(RestError::internal_server_error(anyhow!(err).context("Tokio error"))),
}
}
pub(crate) async fn get_peers_count(State(rest): State<Self>) -> ErasedJson {
ErasedJson::pretty(rest.routing.router().number_of_connected_peers())
}
pub(crate) async fn get_peers_all(State(rest): State<Self>) -> ErasedJson {
ErasedJson::pretty(rest.routing.router().connected_peers())
}
pub(crate) async fn get_peers_all_metrics(State(rest): State<Self>) -> ErasedJson {
ErasedJson::pretty(rest.routing.router().connected_metrics())
}
pub(crate) async fn get_bft_connections_count(State(rest): State<Self>) -> Result<ErasedJson, RestError> {
match rest.consensus {
Some(consensus) => Ok(ErasedJson::pretty(consensus.bft().primary().gateway().number_of_connected_peers())),
None => Err(RestError::service_unavailable(anyhow!("Route isn't available for this node type"))),
}
}
pub(crate) async fn get_bft_connections_all(State(rest): State<Self>) -> Result<ErasedJson, RestError> {
match rest.consensus {
Some(consensus) => Ok(ErasedJson::pretty(consensus.bft().primary().gateway().connected_peers())),
None => Err(RestError::service_unavailable(anyhow!("Route isn't available for this node type"))),
}
}
pub(crate) async fn get_node_address(State(rest): State<Self>) -> ErasedJson {
ErasedJson::pretty(rest.routing.router().address())
}
pub(crate) async fn find_block_hash(
State(rest): State<Self>,
Path(tx_id): Path<N::TransactionID>,
) -> Result<ErasedJson, RestError> {
Ok(ErasedJson::pretty(rest.ledger.find_block_hash(&tx_id)?))
}
pub(crate) async fn find_block_height_from_state_root(
State(rest): State<Self>,
Path(state_root): Path<N::StateRoot>,
) -> Result<ErasedJson, RestError> {
Ok(ErasedJson::pretty(rest.ledger.find_block_height_from_state_root(state_root)?))
}
pub(crate) async fn find_latest_transaction_id_from_program_id(
State(rest): State<Self>,
Path(program_id): Path<ProgramID<N>>,
) -> Result<ErasedJson, RestError> {
Ok(ErasedJson::pretty(rest.ledger.find_latest_transaction_id_from_program_id(&program_id)?))
}
pub(crate) async fn find_latest_transaction_id_from_program_id_and_edition(
State(rest): State<Self>,
Path((program_id, edition)): Path<(ProgramID<N>, u16)>,
) -> Result<ErasedJson, RestError> {
Ok(ErasedJson::pretty(
rest.ledger.find_latest_transaction_id_from_program_id_and_edition(&program_id, edition)?,
))
}
pub(crate) async fn find_original_deployment_transaction_id(
State(rest): State<Self>,
Path((program_id, edition)): Path<(ProgramID<N>, u16)>,
) -> Result<ErasedJson, RestError> {
Ok(ErasedJson::pretty(
rest.ledger.find_original_transaction_id_from_program_id_and_edition(&program_id, edition)?,
))
}
pub(crate) async fn find_transaction_id_from_program_id_edition_and_amendment(
State(rest): State<Self>,
Path((program_id, edition, amendment)): Path<(ProgramID<N>, u16, u64)>,
) -> Result<ErasedJson, RestError> {
Ok(ErasedJson::pretty(rest.ledger.find_transaction_id_from_program_id_edition_and_amendment(
&program_id,
edition,
amendment,
)?))
}
pub(crate) async fn find_transaction_id_from_transition_id(
State(rest): State<Self>,
Path(transition_id): Path<N::TransitionID>,
) -> Result<ErasedJson, RestError> {
Ok(ErasedJson::pretty(rest.ledger.find_transaction_id_from_transition_id(&transition_id)?))
}
pub(crate) async fn find_transition_id(
State(rest): State<Self>,
Path(input_or_output_id): Path<Field<N>>,
) -> Result<ErasedJson, RestError> {
Ok(ErasedJson::pretty(rest.ledger.find_transition_id(&input_or_output_id)?))
}
pub(crate) async fn transaction_broadcast(
State(rest): State<Self>,
check_transaction: Query<CheckTransaction>,
json_result: Result<Json<Transaction<N>>, JsonRejection>,
) -> Result<impl axum::response::IntoResponse, RestError> {
let Json(tx) = match json_result {
Ok(json) => json,
Err(JsonRejection::JsonDataError(err)) => {
return Err(RestError::unprocessable_entity(anyhow!("Invalid transaction data: {err}")));
}
Err(other_rejection) => return Err(other_rejection.into()),
};
let buffer = Vec::with_capacity(3000);
if tx.write_le(LimitedWriter::new(buffer, N::LATEST_MAX_TRANSACTION_SIZE())).is_err() {
return Err(RestError::bad_request(anyhow!("Transaction size exceeds the byte limit")));
}
let tx_id = tx.id();
let message = Message::UnconfirmedTransaction(UnconfirmedTransaction {
transaction_id: tx_id,
transaction: Data::Object(tx.clone()),
});
let is_within_sync_leniency = rest.routing.is_within_sync_leniency();
let check_transaction = check_transaction.check_transaction.unwrap_or(false);
if check_transaction {
let (slot, err_msg) = if tx.is_execute() {
(rest.num_verifying_executions.acquire().await, "Too many execution verifications in progress")
} else {
(rest.num_verifying_deploys.acquire().await, "Too many deploy verifications in progress")
};
if slot.is_err() {
return Err(RestError::too_many_requests(anyhow!("{err_msg}")));
}
let res = rest.ledger.check_transaction_basic(&tx, None, &mut rand::thread_rng()).map_err(|err| {
match is_within_sync_leniency {
true => RestError::unprocessable_entity(err.context("Invalid transaction")),
false => {
RestError::service_unavailable(err.context("Unable to validate transaction (node is syncing)"))
}
}
});
res?;
}
if let Some(consensus) = rest.consensus {
consensus.add_unconfirmed_transaction(tx.clone()).await?;
}
rest.routing.propagate(message, &[]);
match !is_within_sync_leniency && check_transaction {
true => Ok((StatusCode::NON_AUTHORITATIVE_INFORMATION, ErasedJson::pretty(tx_id))),
false => Ok((StatusCode::OK, ErasedJson::pretty(tx_id))),
}
}
pub(crate) async fn solution_broadcast(
State(rest): State<Self>,
check_solution: Query<CheckSolution>,
Json(solution): Json<Solution<N>>,
) -> Result<impl axum::response::IntoResponse, RestError> {
let is_within_sync_leniency = rest.routing.is_within_sync_leniency();
let check_solution = check_solution.check_solution.unwrap_or(false);
if check_solution {
let slot = rest.num_verifying_solutions.acquire().await;
if slot.is_err() {
return Err(RestError::too_many_requests(anyhow!("Too many solution verifications in progress")));
}
let epoch_hash = rest.ledger.latest_epoch_hash()?;
let proof_target = rest.ledger.latest_proof_target();
let puzzle = rest.ledger.puzzle().clone();
let prover_address = solution.address();
if rest.ledger.is_solution_limit_reached(&prover_address, 0) {
return Err(RestError::unprocessable_entity(anyhow!(
"Invalid solution '{}' - Prover '{prover_address}' has reached their solution limit for the current epoch",
fmt_id(solution.id())
)));
}
let res: Result<(), anyhow::Error> =
match tokio::task::spawn_blocking(move || puzzle.check_solution(&solution, epoch_hash, proof_target))
.await
{
Ok(Ok(())) => Ok(()),
Ok(Err(err)) => {
return match is_within_sync_leniency {
true => Err(RestError::unprocessable_entity(
err.context(format!("Invalid solution '{}'", fmt_id(solution.id()))),
)),
false => Err(RestError::service_unavailable(anyhow!(
"Unable to validate solution '{}' (node is syncing)",
fmt_id(solution.id())
))),
};
}
Err(err) => {
return Err(RestError::internal_server_error(anyhow!("Tokio error: {err}")));
}
};
res?;
}
if let Some(consensus) = rest.consensus {
let _ = consensus.add_unconfirmed_solution(solution).await;
}
let solution_id = solution.id();
let message =
Message::UnconfirmedSolution(UnconfirmedSolution { solution_id, solution: Data::Object(solution) });
rest.routing.propagate(message, &[]);
match !is_within_sync_leniency && check_solution {
true => Ok((StatusCode::NON_AUTHORITATIVE_INFORMATION, ErasedJson::pretty(solution_id))),
false => Ok((StatusCode::OK, ErasedJson::pretty(solution_id))),
}
}
pub(crate) async fn db_backup(
State(rest): State<Self>,
backup_path: Query<BackupPath>,
) -> Result<ErasedJson, RestError> {
let mut backup_path = backup_path.path.clone();
rest.ledger.backup_database(&backup_path)?;
let ret = ErasedJson::pretty(());
if let Err(e) = rest.ledger.cache_block_tree() {
warn!("Couldn't cache the block tree for a ledger checkpoint: {e}");
return Ok(ret);
}
let mut block_tree_path = aleo_ledger_dir(N::ID, rest.ledger.vm().block_store().storage_mode());
block_tree_path.push("block_tree");
backup_path.push("block_tree");
if let Err(e) = fs::copy(block_tree_path, backup_path) {
warn!("Couldn't copy the block tree file to a ledger checkpoint: {e}");
}
Ok(ret)
}
#[cfg(feature = "history")]
pub(crate) async fn get_history(
State(rest): State<Self>,
Path((program_id, mapping_name, mapping_key, height)): Path<HistoricalMappingKey<N>>,
) -> Result<impl axum::response::IntoResponse, RestError> {
let value = rest.ledger.vm().finalize_store().get_historical_mapping_value(program_id, mapping_name, mapping_key.clone(), height)
.map_err(|err| {
RestError::not_found(err.context(format!("Could not load mapping '{mapping_name}/{mapping_key}' for program '{program_id}' from block '{height}'")))
})?;
Ok((StatusCode::OK, ErasedJson::pretty(value)))
}
#[cfg(feature = "history-staking-rewards")]
pub(crate) async fn get_staking_reward(
State(rest): State<Self>,
Path((address, height)): Path<(Address<N>, u32)>,
) -> Result<impl axum::response::IntoResponse, RestError> {
let value = rest.ledger.vm().finalize_store().staking_rewards_map().get_confirmed(&(address, height)).map_err(
|err| {
RestError::not_found(
err.context(format!("Could not load the staking reward for {address} from block '{height}'")),
)
},
)?;
Ok((StatusCode::OK, ErasedJson::pretty(value)))
}
#[cfg(feature = "telemetry")]
pub(crate) async fn get_validator_participation_scores(
State(rest): State<Self>,
metadata: Query<Metadata>,
) -> Result<impl axum::response::IntoResponse, RestError> {
match rest.consensus {
Some(consensus) => {
let latest_committee = rest.ledger.latest_committee()?;
let participation_scores = consensus
.bft()
.primary()
.gateway()
.validator_telemetry()
.get_participation_scores(&latest_committee);
if metadata.metadata.unwrap_or(false) {
return Ok(ErasedJson::pretty(json!({
"participation_scores": participation_scores,
"height": rest.ledger.latest_height(),
})));
}
Ok(ErasedJson::pretty(participation_scores))
}
None => Err(RestError::service_unavailable(anyhow!("Route isn't available for this node type"))),
}
}
}