From 88f62028fc812ef4145fde8e033d8383559d8f24 Mon Sep 17 00:00:00 2001 From: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> Date: Wed, 5 Jan 2022 13:42:54 +0100 Subject: [PATCH] Use local transaction payload to reattach (#851) * use local transaction payload to reattach * check messages for conflicts --- .changes/reattach.md | 5 +++ src/account/sync/mod.rs | 73 ++++++++++++++++++++++++++++++++++++++--- src/account_manager.rs | 2 ++ src/message.rs | 42 ++++++++++++++++++++++++ 4 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 .changes/reattach.md diff --git a/.changes/reattach.md b/.changes/reattach.md new file mode 100644 index 000000000..50aeec474 --- /dev/null +++ b/.changes/reattach.md @@ -0,0 +1,5 @@ +--- +"nodejs-binding": patch +--- + +Use local data when reattaching transactions and check inclusion state for reattached messages. diff --git a/src/account/sync/mod.rs b/src/account/sync/mod.rs index df4e129ea..75e603fee 100644 --- a/src/account/sync/mod.rs +++ b/src/account/sync/mod.rs @@ -2405,6 +2405,10 @@ pub(crate) async fn repost_message( let message = match account.get_message(message_id).await { Some(message_to_repost) => { let mut message_to_repost = message_to_repost.clone(); + + let client = crate::client::get_client(account.client_options()).await?; + let client = client.read().await; + // check all reattachments of the message we want to promote/rettry/reattach while let Some(reattachment_message_id) = message_to_repost.reattachment_message_id { match account.get_message(&reattachment_message_id).await { @@ -2414,19 +2418,78 @@ pub(crate) async fn repost_message( return Err(crate::Error::ClientError(Box::new( iota_client::Error::NoNeedPromoteOrReattach(message_id.to_string()), ))); + } else { + let metadata = client.get_message().metadata(&reattachment_message_id).await?; + if metadata.conflict_reason.is_some() { + // if the message is conflicting, then any reattachment is also useless, because it + // can't get confirmed or was already confirmed + return Err(crate::Error::ClientError(Box::new( + iota_client::Error::NoNeedPromoteOrReattach(message_id.to_string()), + ))); + } } } None => break, } } - - let client = crate::client::get_client(account.client_options()).await?; - let client = client.read().await; + let metadata = client.get_message().metadata(message_id).await?; + if metadata.conflict_reason.is_some() { + // if the message is conflicting, then any reattachment is also useless, because it can't get confirmed + // or was already confirmed + return Err(crate::Error::ClientError(Box::new( + iota_client::Error::NoNeedPromoteOrReattach(message_id.to_string()), + ))); + } let (id, message) = match action { RepostAction::Promote => client.promote(message_id).await?, - RepostAction::Reattach => client.reattach(message_id).await?, - RepostAction::Retry => client.retry(message_id).await?, + // Reattach with the local message + RepostAction::Reattach => match client.reattach(message_id).await { + Ok(res) => res, + Err(err) => match err { + iota_client::Error::NoNeedPromoteOrReattach(_) => { + return Err(crate::Error::ClientError(Box::new( + iota_client::Error::NoNeedPromoteOrReattach(message_id.to_string()), + ))) + } + // if reattaching with the message from the node failed, we reattach it with the local data + _ => match message_to_repost.payload { + Some(crate::message::MessagePayload::Transaction(tx_payload)) => { + let msg = client + .message() + .finish_message(Some(Payload::Transaction(Box::new( + tx_payload.to_transaction_payload()?, + )))) + .await?; + (msg.id().0, msg) + } + _ => return Err(crate::Error::MessageNotFound), + }, + }, + }, + RepostAction::Retry => match client.retry(message_id).await { + Ok(res) => res, + Err(err) => match err { + iota_client::Error::NoNeedPromoteOrReattach(_) => { + return Err(crate::Error::ClientError(Box::new( + iota_client::Error::NoNeedPromoteOrReattach(message_id.to_string()), + ))) + } + // if retrying failed, we reattach it with the local data + _ => match message_to_repost.payload { + Some(crate::message::MessagePayload::Transaction(tx_payload)) => { + let msg = client + .message() + .finish_message(Some(Payload::Transaction(Box::new( + tx_payload.to_transaction_payload()?, + )))) + .await?; + (msg.id().0, msg) + } + _ => return Err(crate::Error::MessageNotFound), + }, + }, + }, }; let message = Message::from_iota_message( id, diff --git a/src/account_manager.rs b/src/account_manager.rs index f80bae5f6..a15e2de7b 100644 --- a/src/account_manager.rs +++ b/src/account_manager.rs @@ -1959,6 +1959,8 @@ async fn retry_unconfirmed_transactions(synced_accounts: &[SyncedAccount]) -> cr Err(crate::Error::ClientError(ref e)) => { if let iota_client::Error::NoNeedPromoteOrReattach(_) = e.as_ref() { no_need_promote_or_reattach.push(message_id); + } else { + log::debug!("[POLLING] retrying failed: {:?}", e); } } _ => {} diff --git a/src/message.rs b/src/message.rs index 54910c560..9a21cfe74 100644 --- a/src/message.rs +++ b/src/message.rs @@ -758,6 +758,48 @@ impl MessageTransactionPayload { unlock_blocks: unlock_blocks.into_boxed_slice(), }) } + + /// Convert to a transaction payload from bee_message + pub fn to_transaction_payload(&self) -> crate::Result { + let mut essence = iota_client::bee_message::payload::transaction::RegularEssenceBuilder::new(); + let TransactionEssence::Regular(message_essence) = self.essence(); + let mut inputs = Vec::new(); + for input in message_essence.inputs() { + if let TransactionInput::Utxo(input) = input { + inputs.push(iota_client::bee_message::input::Input::Utxo(input.input.clone())); + } + } + inputs.sort_unstable_by_key(|a| a.pack_new()); + essence = essence.with_inputs(inputs); + let mut outputs = Vec::new(); + for output in message_essence.outputs() { + match output { + TransactionOutput::SignatureLockedSingle(output) => { + outputs.push(iota_client::bee_message::output::Output::SignatureLockedSingle( + SignatureLockedSingleOutput::new(output.address.inner, output.amount)?, + )) + } + TransactionOutput::SignatureLockedDustAllowance(output) => { + outputs.push(iota_client::bee_message::output::Output::SignatureLockedDustAllowance( + SignatureLockedDustAllowanceOutput::new(output.address.inner, output.amount)?, + )) + } + _ => {} + } + } + outputs.sort_unstable_by_key(|a| a.pack_new()); + essence = essence.with_outputs(outputs); + if let Some(indexation) = message_essence.payload() { + essence = essence.with_payload(indexation.clone()); + } + let essence = essence.finish()?; + Ok(TransactionPayload::builder() + .with_essence(Essence::Regular(essence)) + .with_unlock_blocks(iota_client::bee_message::unlock::UnlockBlocks::new( + self.unlock_blocks.to_vec(), + )?) + .finish()?) + } } /// Milestone payload essence.