From be869fc47117f996cf040c8466ba4023d70cacfa Mon Sep 17 00:00:00 2001 From: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> Date: Mon, 8 Mar 2021 12:58:24 +0100 Subject: [PATCH] Add retry_until_included (#395) * add retry_until_included * fix: callback argument position * fix parameter index * use default for getAddressOutputs Co-authored-by: Lucas Nogueira --- bindings/nodejs/README.md | 13 +++++++ bindings/nodejs/lib/index.d.ts | 5 ++- bindings/nodejs/lib/index.js | 19 ++++++++-- bindings/nodejs/lib/types/models.d.ts | 1 + .../nodejs/native/src/classes/client/api.rs | 18 ++++++++- .../nodejs/native/src/classes/client/mod.rs | 32 +++++++++++++++- bindings/nodejs/tests/client.js | 10 ++--- bindings/python/README.md | 12 ++++++ .../native/src/client/high_level_api.rs | 18 +++++++++ examples/message_time.rs | 17 +-------- examples/multiple_outputs.rs | 18 +-------- examples/send_all.rs | 18 +-------- examples/split_all.rs | 18 +-------- examples/split_outputs_single_address.rs | 21 ++-------- examples/transaction.rs | 24 +++--------- examples/txspam.rs | 19 ++-------- iota-client/src/client.rs | 38 +++++++++++++++++++ iota-client/src/error.rs | 3 ++ iota-client/tests/node_api.rs | 2 +- specs/iota-rs-ENGINEERING-SPEC-0000.md | 16 ++++++++ 20 files changed, 193 insertions(+), 129 deletions(-) diff --git a/bindings/nodejs/README.md b/bindings/nodejs/README.md index f632413cb..ec2f17361 100644 --- a/bindings/nodejs/README.md +++ b/bindings/nodejs/README.md @@ -272,6 +272,19 @@ Retries (promotes or reattaches) the message associated with the given id. **Returns** A promise resolving to the new [Message](#message) instance. +#### retryUntilIncluded(messageId: string[, interval: int, max_attempts: int]): Promise + +Retries (promotes or reattaches) the message associated with the given id until it's included in the Tangle. +Default interval is 5 seconds and max_attempts is 10. + +| Param | Type | Description | +| ---------------------- | ------------------- | ------------------------------------------------------ | +| messageId | string | The id of the message to retry | +| [options.interval] | int | The interval in seconds in which we retry the message. | +| [options.max_attempts] | int | The maximum of attempts we retry the message. | + +**Returns** the message ids and [Message](#message) of reattached messages. + #### getInfo(): Promise Gets information about the node. diff --git a/bindings/nodejs/lib/index.d.ts b/bindings/nodejs/lib/index.d.ts index 250e9b995..f67e0c1a8 100644 --- a/bindings/nodejs/lib/index.d.ts +++ b/bindings/nodejs/lib/index.d.ts @@ -76,7 +76,7 @@ export declare interface AddressOutputsOptions { } export declare class Client { - networkInfo(): NetworkInfo + networkInfo(): Promise subscriber(): TopicSubscriber message(): MessageSender getUnspentAddress(seed: string): UnspentAddressGetter @@ -85,6 +85,7 @@ export declare class Client { getBalance(seed: string): BalanceGetter getAddressBalances(addresses: string[]): Promise retry(messageId: string): Promise + retryUntilIncluded(messageId: string, interval?: number, maxAttempts?: number): Promise getInfo(): Promise getTips(): Promise @@ -94,7 +95,7 @@ export declare class Client { getOutput(outputId: string): Promise findOutputs(outputIds: string[], addresses: string[]): Promise getAddressOutputs(address: string, options?: AddressOutputsOptions): Promise - getAddressBalance(address: string): Promise + getAddressBalance(address: string): Promise isAddressValid(address: string): boolean getMilestone(index: number): Promise getMilestoneUTXOChanges(index: number): Promise diff --git a/bindings/nodejs/lib/index.js b/bindings/nodejs/lib/index.js index 8867f05a6..50d3e85ad 100644 --- a/bindings/nodejs/lib/index.js +++ b/bindings/nodejs/lib/index.js @@ -84,11 +84,12 @@ Client.prototype.getOutput = promisify(Client.prototype.getOutput) Client.prototype.findOutputs = promisify(Client.prototype.findOutputs) const getAddressOutputs = Client.prototype.getAddressOutputs Client.prototype.getAddressOutputs = function (address, options) { - if (options) { - return promisify(getAddressOutputs).apply(this, [address, JSON.stringify(options)]) - } else { - return promisify(getAddressOutputs).apply(this, [address]) + if (typeof options == 'undefined') { + options = { + includeSpent: false + } } + return promisify(getAddressOutputs).apply(this, [address, JSON.stringify(options)]) } Client.prototype.getAddressBalance = promisify(Client.prototype.getAddressBalance) Client.prototype.getMilestone = promisify(Client.prototype.getMilestone) @@ -97,6 +98,16 @@ Client.prototype.getReceipts = promisify(Client.prototype.getReceipts) Client.prototype.getReceiptsMigratedAt = promisify(Client.prototype.getReceiptsMigratedAt) Client.prototype.getTreasury = promisify(Client.prototype.getTreasury) Client.prototype.retry = promisify(Client.prototype.retry) +const retryUntilIncluded = Client.prototype.retryUntilIncluded +Client.prototype.retryUntilIncluded = function (msg_id, interval, maxAttempts) { + if (typeof interval == 'undefined') { + interval = 5 + } + if (typeof maxAttempts == 'undefined') { + maxAttempts = 10 + } + return promisify(retryUntilIncluded).apply(this, [msg_id, interval, maxAttempts]) +} Client.prototype.reattach = promisify(Client.prototype.reattach) Client.prototype.promote = promisify(Client.prototype.promote) diff --git a/bindings/nodejs/lib/types/models.d.ts b/bindings/nodejs/lib/types/models.d.ts index f21599346..d2217ebaa 100644 --- a/bindings/nodejs/lib/types/models.d.ts +++ b/bindings/nodejs/lib/types/models.d.ts @@ -51,6 +51,7 @@ export declare interface BrokerOptions { export declare type Address = 'string' export declare interface AddressBalance { + type: number address: Address balance: number } diff --git a/bindings/nodejs/native/src/classes/client/api.rs b/bindings/nodejs/native/src/classes/client/api.rs index bc4a82ff6..920a054ed 100644 --- a/bindings/nodejs/native/src/classes/client/api.rs +++ b/bindings/nodejs/native/src/classes/client/api.rs @@ -69,6 +69,7 @@ pub(crate) enum Api { GetReceiptsMigratedAt(u32), GetTreasury(), Retry(MessageId), + RetryUntilIncluded(MessageId, Option, Option), Reattach(MessageId), Promote(MessageId), } @@ -300,11 +301,26 @@ impl Task for ClientTask { Api::Retry(message_id) => { let message = client.retry(message_id).await?; serde_json::to_string(&MessageWrapper { - message: message.1, message_id: message.0, + message: message.1, }) .unwrap() } + Api::RetryUntilIncluded(message_id, interval, max_attempts) => { + let messages = client + .retry_until_included(message_id, *interval, *max_attempts) + .await?; + messages + .into_iter() + .map(|msg| { + serde_json::to_string(&MessageWrapper { + message_id: msg.0, + message: msg.1, + }) + .unwrap() + }) + .collect() + } Api::Reattach(message_id) => { let message = client.reattach(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 7dbe4bbe3..2e190b438 100644 --- a/bindings/nodejs/native/src/classes/client/mod.rs +++ b/bindings/nodejs/native/src/classes/client/mod.rs @@ -217,6 +217,36 @@ declare_types! { Ok(cx.undefined().upcast()) } + method retryUntilIncluded(mut cx) { + let message_id = cx.argument::(0)?.value(); + let message_id = MessageId::from_str(message_id.as_str()).expect("invalid message id"); + let interval: Option = match cx.argument_opt(1) { + Some(arg) => { + Some(arg.downcast::().or_throw(&mut cx)?.value() as u64) + }, + None => None, + }; + let max_attempts: Option = match cx.argument_opt(2) { + Some(arg) => { + Some(arg.downcast::().or_throw(&mut cx)?.value() as u64) + }, + None => None, + }; + let cb = cx.argument::(cx.len()-1)?; + { + let this = cx.this(); + let guard = cx.lock(); + let id = &this.borrow(&guard).0; + let client_task = ClientTask { + client_id: id.clone(), + api: Api::RetryUntilIncluded(message_id, interval, max_attempts), + }; + client_task.schedule(cb); + } + + Ok(cx.undefined().upcast()) + } + method networkInfo(mut cx) { let cb = cx.argument::(0)?; { @@ -389,7 +419,7 @@ declare_types! { None => Default::default(), }; - let cb = cx.argument::(1)?; + let cb = cx.argument::(cx.len()-1)?; { let this = cx.this(); let guard = cx.lock(); diff --git a/bindings/nodejs/tests/client.js b/bindings/nodejs/tests/client.js index bc18cd0bb..3d07b05f6 100644 --- a/bindings/nodejs/tests/client.js +++ b/bindings/nodejs/tests/client.js @@ -46,7 +46,7 @@ describe('Client', () => { }) it('sends a value transaction and checks output balance', async () => { - const depositAddress = 'atoi1qzt0nhsf38nh6rs4p6zs5knqp6psgha9wsv74uajqgjmwc75ugupx3y7x0r' + const depositAddress = 'atoi1qpnrumvaex24dy0duulp4q07lpa00w20ze6jfd0xly422kdcjxzakzsz5kf' const message = await client .message() .seed(seed) @@ -65,8 +65,8 @@ describe('Client', () => { } } - const depositBalance = await client.getAddressBalance(depositAddress) - assert.strictEqual(depositBalance >= 2, true) + const addressBalanceObject = await client.getAddressBalance(depositAddress) + assert.strictEqual(addressBalanceObject.balance >= 1000000, true) }) it('gets an unspent address', async () => { @@ -84,11 +84,11 @@ describe('Client', () => { }) it('get milestone and message', async () => { - const milestone = await client.getMilestone(750) + const info = await client.getInfo() + const milestone = await client.getMilestone(info.confirmedMilestoneIndex) assert.strictEqual(typeof milestone, 'object') assert.strictEqual('message_id' in milestone, true) assertMessageId(milestone.message_id) - const message = await client.getMessage().data(milestone.message_id) assertMessage(message) diff --git a/bindings/python/README.md b/bindings/python/README.md index c0675c71a..764da8ac9 100644 --- a/bindings/python/README.md +++ b/bindings/python/README.md @@ -461,6 +461,18 @@ Retries (promotes or reattaches) the message associated with the given id. **Returns** the message id and the retried [Message](#message). +#### retry_until_included(message_id, interval (optional), max_attempts (optional)): list[(str, Message)] + +Retries (promotes or reattaches) the message associated with the given id. + +| Param | Type | Default | Description | +| ------------ | ---------------- | ---------------------- | ------------------------------------------------------ | +| [message_id] | str | undefined | The message id | +| interval | int | 5 | The interval in seconds in which we retry the message. | +| max_attempts | int | 10 | The maximum of attempts we retry the message. | + +**Returns** the message ids and [Message](#message) of reattached messages. + #### reattach(message_id): (str, Message) Reattaches the message associated with the given id. diff --git a/bindings/python/native/src/client/high_level_api.rs b/bindings/python/native/src/client/high_level_api.rs index 7fd25d2de..575591cd5 100644 --- a/bindings/python/native/src/client/high_level_api.rs +++ b/bindings/python/native/src/client/high_level_api.rs @@ -316,6 +316,24 @@ impl Client { rt.block_on(async { self.client.retry(&RustMessageId::from_str(&message_id)?).await })?; Ok((message_id_message.0.to_string(), message_id_message.1.try_into()?)) } + fn retry_until_included( + &self, + message_id: String, + interval: Option, + max_attempts: Option, + ) -> Result> { + let rt = tokio::runtime::Runtime::new()?; + let messages = rt.block_on(async { + self.client + .retry_until_included(&RustMessageId::from_str(&message_id)?, interval, max_attempts) + .await + })?; + let mut res = Vec::new(); + for msg in messages { + res.push((msg.0.to_string(), msg.1.try_into()?)); + } + Ok(res) + } fn reattach(&self, message_id: String) -> Result<(String, Message)> { let rt = tokio::runtime::Runtime::new()?; let message_id_message = diff --git a/examples/message_time.rs b/examples/message_time.rs index 04b4bb0c3..58aa14e31 100644 --- a/examples/message_time.rs +++ b/examples/message_time.rs @@ -3,9 +3,7 @@ //! cargo run --example message_time --release -use iota::{Client, MessageId}; -use std::time::Duration; -use tokio::time::sleep; +use iota::Client; #[tokio::main] async fn main() { @@ -28,7 +26,7 @@ async fn main() { let message_id = message.id().0; println!("Message ID: {}", message_id); - reattach_promote_until_confirmed(&message_id, &iota).await; + let _ = iota.retry_until_included(&message_id, None, None).await.unwrap(); let metadata = iota.get_message().metadata(&message_id).await.unwrap(); match metadata.referenced_by_milestone_index { @@ -39,14 +37,3 @@ async fn main() { _ => println!("Message is not referenced by a milestone"), } } - -async fn reattach_promote_until_confirmed(message_id: &MessageId, iota: &Client) { - while let Ok(metadata) = iota.get_message().metadata(&message_id).await { - if metadata.referenced_by_milestone_index.is_some() { - break; - } else if let Ok(msg_id) = iota.reattach(&message_id).await { - println!("Reattached or promoted {}", msg_id.0); - } - sleep(Duration::from_secs(5)).await; - } -} diff --git a/examples/multiple_outputs.rs b/examples/multiple_outputs.rs index f9a442a8c..dba2c060d 100644 --- a/examples/multiple_outputs.rs +++ b/examples/multiple_outputs.rs @@ -3,9 +3,7 @@ //! cargo run --example multiple_outputs --release -use iota::{Client, MessageId, Seed}; -use std::time::Duration; -use tokio::time::sleep; +use iota::{Client, Seed}; extern crate dotenv; use dotenv::dotenv; use std::env; @@ -64,17 +62,5 @@ async fn main() { message.id().0 ); - reattach_promote_until_confirmed(message.id().0, &iota).await; -} - -async fn reattach_promote_until_confirmed(message_id: MessageId, iota: &Client) { - while let Ok(metadata) = iota.get_message().metadata(&message_id).await { - if let Some(state) = metadata.ledger_inclusion_state { - println!("Leder inclusion state: {:?}", state); - break; - } else if let Ok(msg_id) = iota.reattach(&message_id).await { - println!("Reattached or promoted {}", msg_id.0); - } - sleep(Duration::from_secs(5)).await; - } + let _ = iota.retry_until_included(&message.id().0, None, None).await.unwrap(); } diff --git a/examples/send_all.rs b/examples/send_all.rs index 27b233344..9074dd9d4 100644 --- a/examples/send_all.rs +++ b/examples/send_all.rs @@ -3,9 +3,7 @@ //! cargo run --example send_all --release -use iota::{client::Result, Client, MessageId, Seed}; -use std::time::Duration; -use tokio::time::sleep; +use iota::{client::Result, Client, Seed}; extern crate dotenv; use dotenv::dotenv; use std::env; @@ -46,18 +44,6 @@ async fn main() -> Result<()> { message.id().0 ); - reattach_promote_until_confirmed(message.id().0, &iota).await; + let _ = iota.retry_until_included(&message.id().0, None, None).await.unwrap(); Ok(()) } - -async fn reattach_promote_until_confirmed(message_id: MessageId, iota: &Client) { - while let Ok(metadata) = iota.get_message().metadata(&message_id).await { - if let Some(state) = metadata.ledger_inclusion_state { - println!("Leder inclusion state: {:?}", state); - break; - } else if let Ok(msg_id) = iota.reattach(&message_id).await { - println!("Reattached or promoted {}", msg_id.0); - } - sleep(Duration::from_secs(5)).await; - } -} diff --git a/examples/split_all.rs b/examples/split_all.rs index bcd7bf6b2..a250fd277 100644 --- a/examples/split_all.rs +++ b/examples/split_all.rs @@ -3,9 +3,7 @@ //! cargo run --example split_all --release -use iota::{client::Result, Client, MessageId, Seed}; -use std::time::Duration; -use tokio::time::sleep; +use iota::{client::Result, Client, Seed}; extern crate dotenv; use dotenv::dotenv; use std::env; @@ -63,18 +61,6 @@ async fn main() -> Result<()> { message.id().0 ); - reattach_promote_until_confirmed(message.id().0, &iota).await; + let _ = iota.retry_until_included(&message.id().0, None, None).await.unwrap(); Ok(()) } - -async fn reattach_promote_until_confirmed(message_id: MessageId, iota: &Client) { - while let Ok(metadata) = iota.get_message().metadata(&message_id).await { - if let Some(state) = metadata.ledger_inclusion_state { - println!("Leder inclusion state: {:?}", state); - break; - } else if let Ok(msg_id) = iota.reattach(&message_id).await { - println!("Reattached or promoted {}", msg_id.0); - } - sleep(Duration::from_secs(5)).await; - } -} diff --git a/examples/split_outputs_single_address.rs b/examples/split_outputs_single_address.rs index 3c3f05df4..ca2f95d9e 100644 --- a/examples/split_outputs_single_address.rs +++ b/examples/split_outputs_single_address.rs @@ -3,11 +3,10 @@ //! cargo run --example split_outputs_single_address --release -use iota::{Client, Essence, MessageId, Output, Payload, Seed, UTXOInput}; -use tokio::time::sleep; +use iota::{Client, Essence, Output, Payload, Seed, UTXOInput}; extern crate dotenv; use dotenv::dotenv; -use std::{env, time::Duration}; +use std::env; /// In this example we will create 100 outputs on a single address /// You need to have >=100 Mi on the first address before you run it @@ -47,7 +46,7 @@ async fn main() { message.id().0 ); - reattach_promote_until_confirmed(message.id().0, &iota).await; + let _ = iota.retry_until_included(&message.id().0, None, None).await.unwrap(); // At this point we have 100 Mi on 100 addresses and we will just send it to the final address // We use the outputs directly so we don't double spend them @@ -93,18 +92,6 @@ async fn main() { sent_messages.push(message_id); } for message_id in sent_messages { - reattach_promote_until_confirmed(message_id, &iota).await - } -} - -async fn reattach_promote_until_confirmed(message_id: MessageId, iota: &Client) { - while let Ok(metadata) = iota.get_message().metadata(&message_id).await { - if let Some(state) = metadata.ledger_inclusion_state { - println!("Ledger inclusion state: {:?}", state); - break; - } else if let Ok(msg_id) = iota.reattach(&message_id).await { - println!("Reattached or promoted {}", msg_id.0); - } - sleep(Duration::from_secs(5)).await; + let _ = iota.retry_until_included(&message_id, None, None).await.unwrap(); } } diff --git a/examples/transaction.rs b/examples/transaction.rs index 78e7278fa..67e09bc0d 100644 --- a/examples/transaction.rs +++ b/examples/transaction.rs @@ -3,9 +3,7 @@ //! cargo run --example transaction --release -use iota::{client::Result, Client, MessageId, Seed}; -use std::time::Duration; -use tokio::time::sleep; +use iota::{client::Result, Client, Seed}; extern crate dotenv; use dotenv::dotenv; use std::env; @@ -56,7 +54,7 @@ async fn main() -> Result<()> { .await?; println!("First transaction sent: {}{}", EXPLORER_URL, message.id().0); - reattach_promote_until_confirmed(message.id().0, &iota).await; + let _ = iota.retry_until_included(&message.id().0, None, None).await?; let message = iota .message() @@ -69,7 +67,7 @@ async fn main() -> Result<()> { .await?; println!("Second transaction sent: {}{}", EXPLORER_URL, message.id().0); - reattach_promote_until_confirmed(message.id().0, &iota).await; + let _ = iota.retry_until_included(&message.id().0, None, None).await?; let message = iota .message() @@ -82,7 +80,7 @@ async fn main() -> Result<()> { .await?; println!("Third transaction sent: {}{}", EXPLORER_URL, message.id().0); - reattach_promote_until_confirmed(message.id().0, &iota).await; + let _ = iota.retry_until_included(&message.id().0, None, None).await?; let message = iota .message() @@ -100,22 +98,10 @@ async fn main() -> Result<()> { .await?; println!("Last transaction sent: {}{}", EXPLORER_URL, message.id().0); - reattach_promote_until_confirmed(message.id().0, &iota).await; + let _ = iota.retry_until_included(&message.id().0, None, None).await?; let message_metadata = iota.get_message().metadata(&message.id().0).await; println!("Ledger Inclusion State: {:?}", message_metadata?.ledger_inclusion_state); Ok(()) } - -async fn reattach_promote_until_confirmed(message_id: MessageId, iota: &Client) { - while let Ok(metadata) = iota.get_message().metadata(&message_id).await { - if let Some(state) = metadata.ledger_inclusion_state { - println!("Leder inclusion state: {:?}", state); - break; - } else if let Ok(msg_id) = iota.reattach(&message_id).await { - println!("Reattached or promoted {}", msg_id.0); - } - sleep(Duration::from_secs(5)).await; - } -} diff --git a/examples/txspam.rs b/examples/txspam.rs index e56bece05..8be5c6600 100644 --- a/examples/txspam.rs +++ b/examples/txspam.rs @@ -3,11 +3,10 @@ //! cargo run --example txspam --release -use iota::{Client, Essence, MessageId, Payload, Seed, UTXOInput}; -use tokio::time::sleep; +use iota::{Client, Essence, Payload, Seed, UTXOInput}; extern crate dotenv; use dotenv::dotenv; -use std::{env, time::Duration}; +use std::env; /// In this example we will spam transactions /// Send 10 Mi from the faucet to the first address before you run it @@ -47,7 +46,7 @@ async fn main() { message.id().0 ); - reattach_promote_until_confirmed(message.id().0, &iota).await; + let _ = iota.retry_until_included(&message.id().0, None, None).await.unwrap(); // At this point we have 10 Mi on 10 addresses and we will just send it to their addresses again // Use own outputs directly so we don't double spend them @@ -81,15 +80,3 @@ async fn main() { ); } } - -async fn reattach_promote_until_confirmed(message_id: MessageId, iota: &Client) { - while let Ok(metadata) = iota.get_message().metadata(&message_id).await { - if let Some(state) = metadata.ledger_inclusion_state { - println!("Ledger inclusion state: {:?}", state); - break; - } else if let Ok(msg_id) = iota.reattach(&message_id).await { - println!("Reattached or promoted {}", msg_id.0); - } - sleep(Duration::from_secs(5)).await; - } -} diff --git a/iota-client/src/client.rs b/iota-client/src/client.rs index 248225650..c5b178176 100644 --- a/iota-client/src/client.rs +++ b/iota-client/src/client.rs @@ -1075,6 +1075,44 @@ impl Client { Err(Error::NoNeedPromoteOrReattach(message_id.to_string())) } } + + /// Retries (promotes or reattaches) a message for provided message id until it's included (referenced by a + /// milestone). Default interval is 5 seconds and max attempts is 10. Returns reattached messages + pub async fn retry_until_included( + &self, + message_id: &MessageId, + interval: Option, + max_attempts: Option, + ) -> Result> { + // Attachments of the Message to check inclusion state + let mut message_ids = vec![*message_id]; + // Reattached Messages that get returned + let mut messages_with_id = Vec::new(); + for _ in 0..max_attempts.unwrap_or(10) { + sleep(Duration::from_secs(interval.unwrap_or(5))).await; + // Check inclusion state for each attachment + let message_ids_len = message_ids.len(); + for (index, msg_id) in message_ids.clone().iter().enumerate() { + let message_metadata = self.get_message().metadata(&msg_id).await?; + if message_metadata.ledger_inclusion_state.is_some() { + return Ok(messages_with_id); + } + // Only reattach or promote latest attachment of the message + if index == message_ids_len { + if message_metadata.should_promote.unwrap_or(false) { + // Safe to unwrap since we iterate over it + self.promote_unchecked(&message_ids.last().unwrap()).await?; + } else if message_metadata.should_reattach.unwrap_or(false) { + // Safe to unwrap since we iterate over it + let reattached = self.reattach_unchecked(&message_ids.last().unwrap()).await?; + message_ids.push(reattached.0); + messages_with_id.push(reattached); + } + } + } + } + Err(Error::TangleInclusionError(message_id.to_string())) + } } /// Hash the network id str from the nodeinfo to an u64 for the messageBuilder diff --git a/iota-client/src/error.rs b/iota-client/src/error.rs index 53f83b3c8..8fcbfbf6e 100644 --- a/iota-client/src/error.rs +++ b/iota-client/src/error.rs @@ -46,6 +46,9 @@ pub enum Error { /// The message cannot be promoted or reattached #[error("Message ID `{0}` doesn't need to be promoted or reattached")] NoNeedPromoteOrReattach(String), + /// The message cannot be promoted or reattached + #[error("Message ID `{0}` couldn't get included into the Tangle")] + TangleInclusionError(String), /// Mqtt client error #[cfg(feature = "mqtt")] #[error("{0}")] diff --git a/iota-client/tests/node_api.rs b/iota-client/tests/node_api.rs index 4f578f78d..987cfc19b 100644 --- a/iota-client/tests/node_api.rs +++ b/iota-client/tests/node_api.rs @@ -8,7 +8,7 @@ use crypto::slip10::Seed; use std::str::FromStr; -const DEFAULT_NODE_URL: &str = "http://0.0.0.0:14265"; +const DEFAULT_NODE_URL: &str = "https://api.hornet-1.testnet.chrysalis2.com"; // Sends a full message object to the node with already computed nonce. Serves as a test object. async fn setup_indexation_message() -> MessageId { diff --git a/specs/iota-rs-ENGINEERING-SPEC-0000.md b/specs/iota-rs-ENGINEERING-SPEC-0000.md index 1d464bdd3..b639f1f59 100644 --- a/specs/iota-rs-ENGINEERING-SPEC-0000.md +++ b/specs/iota-rs-ENGINEERING-SPEC-0000.md @@ -18,6 +18,7 @@ * [`is_address_valid`](#is_address_valid) * [`subscriber`](#subscriber) * [`retry`](#retry) + * [`retry_until_included`](#retry_until_included) * [`reattach`](#reattach) * [`promote`](#promote) * [Full node API](#Full-node-API) @@ -336,6 +337,21 @@ Following are the steps for implementing this method: * The method should also validate if a retry is necessary. This can be done by leveraging the `/messages/{messageId}/metadata` endpoint (already available through [get_message](#get_message)). See [this](https://github.com/iotaledger/trinity-wallet/blob/develop/src/shared/libs/iota/transfers.js#L105-L131) implementation for reference; * Use [reattach](#reattach) or [promote](#promote) accordingly. +## `retry_until_included()` + +Retries (promotes or reattaches) a message for provided [MessageId] until it's included (referenced by a milestone). Default interval is 5 seconds and max attempts is 10. The need to use this function should be low, because the confirmation throughput of the node is expected to be quite high. +### Parameters + +| Parameter | Required | Type | Definition | +| - | - | - | - | +| **message_id** | ✔ | [&MessageId] | The identifier of message. | +| **interval** | ✘ | Option | The interval in which we retry the message. | +| **max_attempts** | ✘ | Option | The maximum of attempts we retry the message. | + +### Returns: + +An array of tuples with the newly reattached `(MessageId, Message)`. + ## `reattach()` Depends on [find_messages](#find_messages), [get_message](#get_message) and [post_message](#post_message).