diff --git a/bindings/nodejs/README.md b/bindings/nodejs/README.md index bb7e78a09..650f32ff9 100644 --- a/bindings/nodejs/README.md +++ b/bindings/nodejs/README.md @@ -396,6 +396,16 @@ Get the treasury amount. **Returns** a promise resolving to the [Treasury](#Treasury). +#### getIncludedMessage(): Promise + +Get the included message of a transaction. + +| Param | Type | Description | +| ----- | ------------------- | -------------------------- | +| index | string | The id of the transaction | + +**Returns** A promise resolving to the new [Message](#message) instance. + #### reattach(messageId): Promise Reattaches the message associated with the given id. diff --git a/bindings/nodejs/lib/index.d.ts b/bindings/nodejs/lib/index.d.ts index f67e0c1a8..1faca28ac 100644 --- a/bindings/nodejs/lib/index.d.ts +++ b/bindings/nodejs/lib/index.d.ts @@ -102,6 +102,7 @@ export declare class Client { getReceipts(): Promise getReceiptsMigratedAt(index: number): Promise getTreasury(): Promise + getIncludedMessage(): Promise reattach(messageId: string): Promise promote(messageId: string): Promise } diff --git a/bindings/nodejs/lib/index.js b/bindings/nodejs/lib/index.js index 50d3e85ad..d648fb082 100644 --- a/bindings/nodejs/lib/index.js +++ b/bindings/nodejs/lib/index.js @@ -97,6 +97,8 @@ Client.prototype.getMilestoneUTXOChanges = promisify(Client.prototype.getMilesto Client.prototype.getReceipts = promisify(Client.prototype.getReceipts) Client.prototype.getReceiptsMigratedAt = promisify(Client.prototype.getReceiptsMigratedAt) Client.prototype.getTreasury = promisify(Client.prototype.getTreasury) +Client.prototype.getIncludedMessage = promisify(Client.prototype.getIncludedMessage) + Client.prototype.retry = promisify(Client.prototype.retry) const retryUntilIncluded = Client.prototype.retryUntilIncluded Client.prototype.retryUntilIncluded = function (msg_id, interval, maxAttempts) { diff --git a/bindings/nodejs/native/src/classes/client/api.rs b/bindings/nodejs/native/src/classes/client/api.rs index 920a054ed..981d139ef 100644 --- a/bindings/nodejs/native/src/classes/client/api.rs +++ b/bindings/nodejs/native/src/classes/client/api.rs @@ -7,7 +7,8 @@ use super::MessageDto; use crate::classes::client::dto::MessageWrapper; use iota::{ - Address, AddressOutputsOptions, Bech32Address, ClientMiner, MessageBuilder, MessageId, Parents, Seed, UTXOInput, + Address, AddressOutputsOptions, Bech32Address, ClientMiner, MessageBuilder, MessageId, Parents, Seed, + TransactionId, UTXOInput, }; use neon::prelude::*; @@ -68,6 +69,7 @@ pub(crate) enum Api { GetReceipts(), GetReceiptsMigratedAt(u32), GetTreasury(), + GetIncludedMessage(TransactionId), Retry(MessageId), RetryUntilIncluded(MessageId, Option, Option), Reattach(MessageId), @@ -298,6 +300,14 @@ impl Task for ClientTask { let treasury = client.get_treasury().await?; serde_json::to_string(&treasury).unwrap() } + Api::GetIncludedMessage(transaction_id) => { + let message = client.get_included_message(&*transaction_id).await?; + serde_json::to_string(&MessageWrapper { + message_id: message.id().0, + message, + }) + .unwrap() + } Api::Retry(message_id) => { let message = client.retry(message_id).await?; serde_json::to_string(&MessageWrapper { diff --git a/bindings/nodejs/native/src/classes/client/mod.rs b/bindings/nodejs/native/src/classes/client/mod.rs index 2e190b438..ecd3e7809 100644 --- a/bindings/nodejs/native/src/classes/client/mod.rs +++ b/bindings/nodejs/native/src/classes/client/mod.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use iota::{ - message::prelude::{Address, MessageId, UTXOInput}, + message::prelude::{Address, MessageId, TransactionId, UTXOInput}, AddressOutputsOptions, OutputType, Seed, }; use neon::prelude::*; @@ -540,6 +540,25 @@ declare_types! { Ok(cx.undefined().upcast()) } + method getIncludedMessage(mut cx) { + let transaction_id = cx.argument::(0)?.value(); + let transaction_id = TransactionId::from_str(transaction_id.as_str()).expect("invalid transaction id"); + + let cb = cx.argument::(0)?; + { + let this = cx.this(); + let guard = cx.lock(); + let id = &this.borrow(&guard).0; + let client_task = ClientTask { + client_id: id.clone(), + api: Api::GetIncludedMessage(transaction_id), + }; + client_task.schedule(cb); + } + + Ok(cx.undefined().upcast()) + } + method reattach(mut cx) { let message_id = cx.argument::(0)?.value(); let message_id = MessageId::from_str(message_id.as_str()).expect("invalid message id"); diff --git a/bindings/python/README.md b/bindings/python/README.md index 06822506c..856b670fe 100644 --- a/bindings/python/README.md +++ b/bindings/python/README.md @@ -321,6 +321,17 @@ Get the treasury amount. **Returns** the [TreasuryResponse](#TreasuryResponse). +#### get_included_message(): Message + +Get the included message of a transaction. + +| Param | Type | Description | +| ----- | ------------------- | -------------------------- | +| [index] | string | The id of the transaction | + +**Returns** the new [Message](#message). + + ### High-Level APIs #### message(seed (optional), account_index (optional), initial_address_index (optional), inputs (optional), input_range_begin (optional), input_range_end (optional), outputs (optional), dust_allowance_outputs (optional), index (optional), index_raw (optional), data (optional), data_str (optional), parents (optional)): Message diff --git a/bindings/python/native/src/client/full_node_api.rs b/bindings/python/native/src/client/full_node_api.rs index 297e94343..127bad667 100644 --- a/bindings/python/native/src/client/full_node_api.rs +++ b/bindings/python/native/src/client/full_node_api.rs @@ -7,7 +7,7 @@ use crate::client::{ }; use iota::{ Bech32Address as RustBech32Address, ClientMiner as RustClientMiner, MessageBuilder as RustMessageBuilder, - MessageId as RustMessageId, Parents, UTXOInput as RustUTXOInput, + MessageId as RustMessageId, Parents, TransactionId as RustTransactionId, UTXOInput as RustUTXOInput, }; use pyo3::prelude::*; @@ -128,4 +128,8 @@ impl Client { fn get_treasury(&self) -> Result { Ok(crate::block_on(async { self.client.get_treasury().await })?.into()) } + fn get_included_message(&self, input: String) -> Result { + let transaction_id = RustTransactionId::from_str(&input[..])?; + crate::block_on(async { self.client.get_included_message(&transaction_id).await })?.try_into() + } } diff --git a/bindings/python/native/src/client/high_level_api.rs b/bindings/python/native/src/client/high_level_api.rs index 214e4c51a..84c9390e6 100644 --- a/bindings/python/native/src/client/high_level_api.rs +++ b/bindings/python/native/src/client/high_level_api.rs @@ -7,7 +7,7 @@ use crate::client::{ }; use iota::{ Bech32Address as RustBech32Address, MessageId as RustMessageId, Seed as RustSeed, - TransactionId as RustTransationId, UTXOInput as RustUTXOInput, + TransactionId as RustTransactionId, UTXOInput as RustUTXOInput, }; use pyo3::{exceptions, prelude::*}; use std::{ @@ -52,7 +52,7 @@ impl Client { if let Some(inputs) = inputs { for input in inputs { send_builder = send_builder.with_input(RustUTXOInput::new( - RustTransationId::from_str(&input.transaction_id[..])?, + RustTransactionId::from_str(&input.transaction_id[..])?, input.index, )?); } diff --git a/bindings/python/native/src/client/types.rs b/bindings/python/native/src/client/types.rs index c245a2048..e50f0cfcf 100644 --- a/bindings/python/native/src/client/types.rs +++ b/bindings/python/native/src/client/types.rs @@ -32,7 +32,7 @@ use iota::{ Input as RustInput, Message as RustMessage, MilestonePayloadEssence as RustMilestonePayloadEssence, Output as RustOutput, OutputType, Payload as RustPayload, ReferenceUnlock as RustReferenceUnlock, RegularEssence as RustRegularEssence, SignatureLockedSingleOutput as RustSignatureLockedSingleOutput, - SignatureUnlock as RustSignatureUnlock, TransactionId as RustTransationId, + SignatureUnlock as RustSignatureUnlock, TransactionId as RustTransactionId, TransactionPayload as RustTransactionPayload, UTXOInput as RustUTXOInput, UnlockBlock as RustUnlockBlock, UnlockBlocks as RustUnlockBlocks, }; @@ -883,7 +883,7 @@ impl TryFrom for RustRegularEssence { .iter() .map(|input| { RustUTXOInput::new( - RustTransationId::from_str(&input.transaction_id[..]).unwrap_or_else(|_| { + RustTransactionId::from_str(&input.transaction_id[..]).unwrap_or_else(|_| { panic!( "invalid UTXOInput transaction_id: {} with input index {}", input.transaction_id, input.index diff --git a/iota-client/src/client.rs b/iota-client/src/client.rs index afffc7fb1..eed671958 100644 --- a/iota-client/src/client.rs +++ b/iota-client/src/client.rs @@ -9,7 +9,9 @@ use crate::{ node::*, }; use bee_common::packable::Packable; -use bee_message::prelude::{Address, Bech32Address, Message, MessageBuilder, MessageId, Parents, UTXOInput}; +use bee_message::prelude::{ + Address, Bech32Address, Message, MessageBuilder, MessageId, Parents, TransactionId, UTXOInput, +}; use bee_pow::providers::{MinerBuilder, Provider as PowProvider, ProviderBuilder as PowProviderBuilder}; use bee_rest_api::types::{ dtos::{MessageDto, PeerDto, ReceiptDto}, @@ -924,6 +926,25 @@ impl Client { Ok(resp.data) } + /// GET /api/v1/transactions/{transactionId}/included-message + /// Returns the included message of the transaction. + pub async fn get_included_message(&self, transaction_id: &TransactionId) -> Result { + let mut url = self.get_node().await?; + let path = &format!("api/v1/transactions/{}/included-message", transaction_id); + url.set_path(path); + #[derive(Debug, Serialize, Deserialize)] + struct ResponseWrapper { + data: Message, + } + let resp: ResponseWrapper = self + .http_client + .get(url.as_str(), GET_API_TIMEOUT) + .await? + .json() + .await?; + + Ok(resp.data) + } /// Reattaches messages for provided message id. Messages can be reattached only if they are valid and haven't been /// confirmed for a while. pub async fn reattach(&self, message_id: &MessageId) -> Result<(MessageId, Message)> { diff --git a/iota-client/src/node/mqtt.rs b/iota-client/src/node/mqtt.rs index c56afe197..48db09678 100644 --- a/iota-client/src/node/mqtt.rs +++ b/iota-client/src/node/mqtt.rs @@ -52,7 +52,8 @@ impl Topic { Regex::new("addresses/(iota|atoi|iot|toi)1[A-Za-z0-9]+/outputs").expect("regex failed"), // ED25519 address hex Regex::new("addresses/ed25519/([A-Fa-f0-9]{64})/outputs").expect("regex failed"), - Regex::new(r"messages/indexation/([a-f0-9]{2,128})").expect("regex failed") + Regex::new(r"messages/indexation/([a-f0-9]{2,128})").expect("regex failed"), + Regex::new(r"transactions/([A-Fa-f0-9]{64})/included-message").expect("regex failed"), ].to_vec() => Vec ); @@ -137,7 +138,10 @@ fn poll_mqtt(mqtt_topic_handlers_guard: Arc>, mut event_ let mqtt_topic_handlers = mqtt_topic_handlers_guard.read().await; if let Some(handlers) = mqtt_topic_handlers.get(&Topic(topic.clone())) { let event = { - if topic.as_str() == "messages" || topic.contains("messages/indexation/") { + if topic.as_str() == "messages" + || topic.contains("messages/indexation/") + || topic.contains("transactions/") + { let mut payload = &*p.payload; match Message::unpack(&mut payload) { Ok(iota_message) => match serde_json::to_string(&iota_message) { diff --git a/iota-client/tests/node_api.rs b/iota-client/tests/node_api.rs index 1cfb1f1eb..b6429add5 100644 --- a/iota-client/tests/node_api.rs +++ b/iota-client/tests/node_api.rs @@ -360,3 +360,21 @@ async fn test_get_treasury() { println!("{:#?}", r); } + +#[tokio::test] +#[ignore] +async fn test_get_included_message() { + let r = iota_client::Client::builder() + .with_node(DEFAULT_NODE_URL) + .unwrap() + .finish() + .await + .unwrap() + .get_included_message( + &TransactionId::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), + ) + .await + .unwrap(); + + println!("{:#?}", r); +} diff --git a/specs/iota-rs-ENGINEERING-SPEC-0000.md b/specs/iota-rs-ENGINEERING-SPEC-0000.md index 555a058fa..76a8fb7a9 100644 --- a/specs/iota-rs-ENGINEERING-SPEC-0000.md +++ b/specs/iota-rs-ENGINEERING-SPEC-0000.md @@ -35,6 +35,7 @@ * [`get_receipts`](#get_receipts) * [`get_receipts_migrated_at`](#get_receipts_migrated_at) * [`get_treasury`](#get_treasury) + * [`get_included_message`](#get_included_message) * [Objects](#Objects) * [Network] * [Seed] @@ -614,6 +615,28 @@ pub struct TreasuryResponse { } ``` +## `get_included_message()` + +(`GET /transactions/{transactionId}/included-message`) + +Get the included message of the transaction. + +### Parameters + +| Parameter | Required | Type | Definition | +| - | - | - | - | +| **transaction_id** | ✔ | [TransactionId] | The id of the transaction. | + +### Returns + +```Rust +struct Message { + parents: Vec, + payload: Option, + nonce: u64, +} +``` + # Objects Here are the objects used in the API above. They aim to provide a secure way to handle certain data structures specified in the Iota stack. @@ -864,6 +887,7 @@ messages messages/referenced messages/indexation/{index} messages/{messageId}/metadata +transactions/{transactionId}/included-message outputs/{outputId}