Skip to content

Commit

Permalink
Add /api/core/v3/outputs/{outputId}/full route
Browse files Browse the repository at this point in the history
  • Loading branch information
Thoralf-M committed Dec 5, 2023
1 parent 59cfa35 commit f1050f5
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 29 deletions.
5 changes: 5 additions & 0 deletions sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,11 @@ name = "node_api_core_get_output_metadata"
path = "examples/client/node_api_core/11_get_output_metadata.rs"
required-features = ["client"]

[[example]]
name = "node_api_core_get_output_full"
path = "examples/client/node_api_core/12_get_output_full.rs"
required-features = ["client"]

[[example]]
name = "node_api_core_get_included_block"
path = "examples/client/node_api_core/15_get_included_block.rs"
Expand Down
44 changes: 44 additions & 0 deletions sdk/examples/client/node_api_core/12_get_output_full.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

//! Find an output with its metadata, by its identifier by querying the `/api/core/v3/outputs/{outputId}/full` node
//! endpoint.
//!
//! Make sure to provide a somewhat recent output id to make this example run successfully!
//!
//! Rename `.env.example` to `.env` first, then run the command:
//! ```sh
//! cargo run --release --example node_api_core_get_output_full <OUTPUT ID> [NODE URL]
//! ```

use iota_sdk::{
client::{Client, Result},
types::block::output::OutputId,
};

#[tokio::main]
async fn main() -> Result<()> {
// If not provided we use the default node from the `.env` file.
dotenvy::dotenv().ok();

// Take the node URL from command line argument or use one from env as default.
let node_url = std::env::args()
.nth(2)
.unwrap_or_else(|| std::env::var("NODE_URL").expect("NODE_URL not set"));

// Create a node client.
let client = Client::builder().with_node(&node_url)?.finish().await?;

// Take the output id from the command line, or panic.
let output_id = std::env::args()
.nth(1)
.expect("missing example argument: OUTPUT ID")
.parse::<OutputId>()?;

// Get the output with its metadata.
let output_with_metadata = client.get_output_with_metadata(&output_id).await?;

println!("{output_with_metadata:?}");

Ok(())
}
15 changes: 0 additions & 15 deletions sdk/src/client/node_api/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,12 @@

pub mod routes;

use packable::PackableExt;

use crate::{
client::{node_api::error::Error as NodeApiError, Client, Error, Result},
types::block::output::{Output, OutputId, OutputMetadata, OutputWithMetadata},
};

impl Client {
// Finds output and its metadata by output ID.
/// GET /api/core/v3/outputs/{outputId}
/// + GET /api/core/v3/outputs/{outputId}/metadata
pub async fn get_output_with_metadata(&self, output_id: &OutputId) -> Result<OutputWithMetadata> {
let output = Output::unpack_verified(
self.get_output_raw(output_id).await?,
&self.get_protocol_parameters().await?,
)?;
let metadata = self.get_output_metadata(output_id).await?;

Ok(OutputWithMetadata::new(output, metadata))
}

/// Requests outputs by their output ID in parallel.
pub async fn get_outputs(&self, output_ids: &[OutputId]) -> Result<Vec<Output>> {
futures::future::try_join_all(output_ids.iter().map(|id| self.get_output(id))).await
Expand Down
10 changes: 9 additions & 1 deletion sdk/src/client/node_api/core/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use crate::{
},
block::{
address::ToBech32Ext,
output::{AccountId, Output, OutputId, OutputMetadata},
output::{AccountId, Output, OutputId, OutputMetadata, OutputWithMetadata},
payload::signed_transaction::TransactionId,
slot::{EpochIndex, SlotCommitment, SlotCommitmentId, SlotIndex},
Block, BlockDto, BlockId,
Expand Down Expand Up @@ -256,6 +256,14 @@ impl ClientInner {
self.get_request(path, None, false, true).await
}

/// Finds an output with its metadata by output ID.
/// GET /api/core/v3/outputs/{outputId}/full
pub async fn get_output_with_metadata(&self, output_id: &OutputId) -> Result<OutputWithMetadata> {
let path = &format!("api/core/v3/outputs/{output_id}/full");

self.get_request(path, None, false, true).await
}

/// Returns the earliest confirmed block containing the transaction with the given ID.
/// GET /api/core/v3/transactions/{transactionId}/included-block
pub async fn get_included_block(&self, transaction_id: &TransactionId) -> Result<Block> {
Expand Down
84 changes: 83 additions & 1 deletion sdk/src/types/api/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use alloc::{
};

use serde::{Deserialize, Serialize};
use serde_json::Value;

use crate::{
types::block::{
Expand All @@ -21,7 +22,7 @@ use crate::{
slot::{EpochIndex, SlotCommitment, SlotCommitmentId, SlotIndex},
BlockDto, BlockId,
},
utils::serde::{option_string, string},
utils::serde::{option_string, prefix_hex_bytes, string},
};

/// Response of GET /api/core/v3/info.
Expand Down Expand Up @@ -525,3 +526,84 @@ pub struct UtxoChangesResponse {
pub created_outputs: Vec<OutputId>,
pub consumed_outputs: Vec<OutputId>,
}

/// Contains the generic [`Output`] with associated [`OutputIdProof`].
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OutputResponse {
pub output: Output,
pub output_id_proof: OutputIdProof,
}

/// The proof of the output identifier.
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OutputIdProof {
pub slot: SlotIndex,
pub output_index: u16,
pub transaction_commitment: String,
pub output_commitment_proof: OutputCommitmentProof,
}

#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
#[serde(untagged)]
pub enum OutputCommitmentProof {
HashableNode(HashableNode),
LeafHash(LeafHash),
ValueHash(ValueHash),
}

impl<'de> Deserialize<'de> for OutputCommitmentProof {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let value = Value::deserialize(d)?;
Ok(
match value
.get("type")
.and_then(Value::as_u64)
.ok_or_else(|| serde::de::Error::custom("invalid output commitment proof type"))?
as u8
{
0 => Self::HashableNode(
serde_json::from_value::<HashableNode>(value)
.map_err(|e| serde::de::Error::custom(format!("cannot deserialize hashable node: {e}")))?,
),
1 => Self::LeafHash(
serde_json::from_value::<LeafHash>(value)
.map_err(|e| serde::de::Error::custom(format!("cannot deserialize leaf hash: {e}")))?,
),
2 => Self::ValueHash(
serde_json::from_value::<ValueHash>(value)
.map_err(|e| serde::de::Error::custom(format!("cannot deserialize value hash: {e}")))?,
),
_ => return Err(serde::de::Error::custom("invalid output commitment proof")),
},
)
}
}

/// The proof of the output commitment.
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct HashableNode {
#[serde(rename = "type")]
pub kind: u8,
pub l: Box<OutputCommitmentProof>,
pub r: Box<OutputCommitmentProof>,
}

/// Leaf Hash contains the hash of a leaf in the tree.
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct LeafHash {
#[serde(rename = "type")]
pub kind: u8,
#[serde(with = "prefix_hex_bytes")]
pub hash: [u8; 32],
}

/// Value Hash contains the hash of the value for which the proof is being computed.
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct ValueHash {
#[serde(rename = "type")]
pub kind: u8,
#[serde(with = "prefix_hex_bytes")]
pub hash: [u8; 32],
}
45 changes: 34 additions & 11 deletions sdk/src/types/block/output/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,15 @@ pub(crate) use self::{
output_id::OutputIndex,
unlock_condition::AddressUnlockCondition,
};
use crate::types::block::{
address::Address,
protocol::{ProtocolParameters, WorkScore, WorkScoreParameters},
semantic::SemanticValidationContext,
slot::SlotIndex,
Error,
use crate::types::{
api::core::OutputIdProof,
block::{
address::Address,
protocol::{ProtocolParameters, WorkScore, WorkScoreParameters},
semantic::SemanticValidationContext,
slot::SlotIndex,
Error,
},
};

/// The maximum number of outputs of a transaction.
Expand All @@ -75,17 +78,27 @@ pub enum OutputBuilderAmount {
MinimumAmount(StorageScoreParameters),
}

/// Contains the generic [`Output`] with associated [`OutputMetadata`].
/// Contains the generic [`Output`] with associated [`OutputIdProof`] and [`OutputMetadata`].
#[derive(Clone, Debug)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(rename_all = "camelCase")
)]
pub struct OutputWithMetadata {
pub(crate) output: Output,
pub(crate) metadata: OutputMetadata,
pub output: Output,
pub output_id_proof: OutputIdProof,
pub metadata: OutputMetadata,
}

impl OutputWithMetadata {
/// Creates a new [`OutputWithMetadata`].
pub fn new(output: Output, metadata: OutputMetadata) -> Self {
Self { output, metadata }
pub fn new(output: Output, output_id_proof: OutputIdProof, metadata: OutputMetadata) -> Self {
Self {
output,
output_id_proof,
metadata,
}
}

/// Returns the [`Output`].
Expand All @@ -98,6 +111,16 @@ impl OutputWithMetadata {
self.output
}

/// Returns the [`OutputIdProof`].
pub fn output_id_proof(&self) -> &OutputIdProof {
&self.output_id_proof
}

/// Consumes self and returns the [`OutputIdProof`].
pub fn into_output_id_proof(self) -> OutputIdProof {
self.output_id_proof
}

/// Returns the [`OutputMetadata`].
pub fn metadata(&self) -> &OutputMetadata {
&self.metadata
Expand Down
2 changes: 2 additions & 0 deletions sdk/src/wallet/operations/syncing/outputs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ where
output_id: output_with_meta.metadata().output_id().to_owned(),
metadata: *output_with_meta.metadata(),
output: output_with_meta.output().clone(),
output_id_proof: output_with_meta.output_id_proof().clone(),
is_spent: output_with_meta.metadata().is_spent(),
address: associated_address.address.inner.clone(),
network_id,
Expand Down Expand Up @@ -91,6 +92,7 @@ where
unspent_outputs.push((output_id, output_data.clone()));
outputs.push(OutputWithMetadata::new(
output_data.output.clone(),
output_data.output_id_proof.clone(),
output_data.metadata,
));
}
Expand Down
4 changes: 3 additions & 1 deletion sdk/src/wallet/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub use self::{
use crate::{
client::secret::types::InputSigningData,
types::{
api::core::OutputWithMetadataResponse,
api::core::{OutputIdProof, OutputWithMetadataResponse},
block::{
address::Address,
output::{Output, OutputId, OutputMetadata},
Expand All @@ -43,6 +43,8 @@ pub struct OutputData {
pub metadata: OutputMetadata,
/// The actual Output
pub output: Output,
/// The OutputIdProof
pub output_id_proof: OutputIdProof,
/// If an output is spent
pub is_spent: bool,
/// Associated wallet address.
Expand Down

0 comments on commit f1050f5

Please sign in to comment.