Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add /api/core/v3/outputs/{outputId}/full route #1733

Merged
merged 10 commits into from
Dec 11, 2023
Merged
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
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
9 changes: 9 additions & 0 deletions sdk/src/types/api/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -528,3 +528,12 @@ pub struct UtxoChangesResponse {
pub created_outputs: Vec<OutputId>,
pub consumed_outputs: Vec<OutputId>,
}

// TODO use for outputs route https://github.com/iotaledger/iota-sdk/issues/1686
// /// 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,
// }
32 changes: 27 additions & 5 deletions sdk/src/types/block/output/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mod delegation;
mod metadata;
mod native_token;
mod output_id;
mod output_id_proof;
mod state_transition;
mod storage_score;
mod token_scheme;
Expand Down Expand Up @@ -41,6 +42,7 @@ pub use self::{
native_token::{NativeToken, NativeTokens, NativeTokensBuilder, TokenId},
nft::{NftId, NftOutput, NftOutputBuilder},
output_id::OutputId,
output_id_proof::{HashableNode, LeafHash, OutputCommitmentProof, OutputIdProof, ValueHash},
state_transition::{StateTransitionError, StateTransitionVerifier},
storage_score::{StorageScore, StorageScoreParameters},
token_scheme::{SimpleTokenScheme, TokenScheme},
Expand Down Expand Up @@ -75,17 +77,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,
Alex6323 marked this conversation as resolved.
Show resolved Hide resolved
}

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 +110,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
90 changes: 90 additions & 0 deletions sdk/src/types/block/output/output_id_proof.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright 2023 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use alloc::{boxed::Box, string::String};

#[cfg(feature = "serde")]
use {crate::utils::serde::prefix_hex_bytes, alloc::format, serde::de::Deserialize, serde_json::Value};

use crate::types::block::slot::SlotIndex;

/// The proof of the output identifier.
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::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)]
#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(untagged))]
pub enum OutputCommitmentProof {
HashableNode(HashableNode),
LeafHash(LeafHash),
ValueHash(ValueHash),
}

#[cfg(feature = "serde")]
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")),
},
)
}
}

/// Node contains the hashes of the left and right children of a node in the tree.
#[derive(Clone, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct HashableNode {
#[cfg_attr(feature = "serde", 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)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct LeafHash {
#[cfg_attr(feature = "serde", serde(rename = "type"))]
pub kind: u8,
#[cfg_attr(feature = "serde", 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)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ValueHash {
#[cfg_attr(feature = "serde", serde(rename = "type"))]
pub kind: u8,
#[cfg_attr(feature = "serde", serde(with = "prefix_hex_bytes"))]
pub hash: [u8; 32],
}
5 changes: 1 addition & 4 deletions sdk/src/wallet/operations/syncing/addresses/outputs.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
// Copyright 2022 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

use std::collections::HashMap;

use instant::Instant;

use crate::{
client::secret::SecretManage,
types::block::address::Address,
wallet::{
constants::PARALLEL_REQUESTS_AMOUNT,
task,
Expand All @@ -25,7 +22,7 @@ where
pub(crate) async fn get_outputs_from_address_output_ids(
&self,
addresses_with_unspent_outputs: Vec<AddressWithUnspentOutputs>,
) -> crate::wallet::Result<(Vec<(AddressWithUnspentOutputs, Vec<OutputData>)>)> {
) -> crate::wallet::Result<Vec<(AddressWithUnspentOutputs, Vec<OutputData>)>> {
log::debug!("[SYNC] start get_outputs_from_address_output_ids");
let address_outputs_start_time = Instant::now();

Expand Down
2 changes: 1 addition & 1 deletion sdk/src/wallet/operations/syncing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub use self::options::SyncOptions;
use crate::{
client::secret::SecretManage,
types::block::{
address::{AccountAddress, Address, Bech32Address, NftAddress, ToBech32Ext},
address::{AccountAddress, Address, Bech32Address, NftAddress},
output::{FoundryId, Output, OutputId, OutputMetadata},
},
wallet::{
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(),
network_id,
remainder,
Expand Down Expand Up @@ -90,6 +91,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 @@ -21,7 +21,7 @@ use crate::{
types::{
api::core::OutputWithMetadataResponse,
block::{
output::{Output, OutputId, OutputMetadata},
output::{Output, OutputId, OutputIdProof, OutputMetadata},
payload::signed_transaction::{dto::SignedTransactionPayloadDto, SignedTransactionPayload, TransactionId},
protocol::{CommittableAgeRange, ProtocolParameters},
slot::SlotIndex,
Expand All @@ -42,6 +42,8 @@ pub struct OutputData {
pub metadata: OutputMetadata,
/// The actual Output
pub output: Output,
/// The output ID proof
pub output_id_proof: OutputIdProof,
thibault-martinez marked this conversation as resolved.
Show resolved Hide resolved
/// If an output is spent
pub is_spent: bool,
/// Network ID
Expand Down
15 changes: 14 additions & 1 deletion sdk/tests/wallet/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@ use iota_sdk::{
types::block::{
address::{Address, Bech32Address, Ed25519Address},
input::{Input, UtxoInput},
output::{unlock_condition::AddressUnlockCondition, BasicOutput, Output},
output::{
unlock_condition::AddressUnlockCondition, BasicOutput, LeafHash, Output, OutputCommitmentProof,
OutputIdProof,
},
payload::signed_transaction::{Transaction, TransactionHash, TransactionId},
protocol::protocol_parameters,
rand::{
mana::rand_mana_allotment,
output::{rand_basic_output, rand_output_metadata},
},
slot::SlotIndex,
},
wallet::{
events::types::{
Expand Down Expand Up @@ -48,6 +52,15 @@ fn wallet_events_serde() {
output_id: TransactionHash::null().into_transaction_id(0).into_output_id(0),
metadata: rand_output_metadata(),
output: Output::from(rand_basic_output(1_813_620_509_061_365)),
output_id_proof: OutputIdProof {
slot: SlotIndex(1),
output_index: 0,
transaction_commitment: "0x".to_string(),
output_commitment_proof: OutputCommitmentProof::LeafHash(LeafHash {
kind: 1,
hash: [0u8; 32],
}),
},
is_spent: false,
network_id: 42,
remainder: true,
Expand Down
Loading