From a31f6d01dc546cd9fb4c7d769eaa494deea71af2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Fri, 1 Mar 2024 17:07:06 +0100 Subject: [PATCH 1/8] Handle BlockState::Failed --- .../wallet/operations/syncing/transactions.rs | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/sdk/src/wallet/operations/syncing/transactions.rs b/sdk/src/wallet/operations/syncing/transactions.rs index 947da1d539..f4bf09bded 100644 --- a/sdk/src/wallet/operations/syncing/transactions.rs +++ b/sdk/src/wallet/operations/syncing/transactions.rs @@ -4,7 +4,7 @@ use crate::{ client::secret::SecretManage, types::{ - api::core::TransactionState, + api::core::{BlockState, TransactionState}, block::{input::Input, output::OutputId, BlockId}, }, wallet::{ @@ -100,7 +100,36 @@ where if let Some(block_id) = &transaction.block_id { match self.client().get_block_metadata(block_id).await { Ok(metadata) => { - if let Some(tx_state) = metadata.transaction_metadata.map(|m| m.transaction_state) { + // TODO: refactor, this is the same code as in `TransactionState::Failed` https://github.com/iotaledger/iota-sdk/issues/2111 + if metadata.block_state == BlockState::Failed { + // try to get the included block, because maybe only this attachment is + // conflicting because it got confirmed in another block + if let Ok(included_block) = self + .client() + .get_included_block(&transaction.payload.transaction().id()) + .await + { + confirmed_unknown_output = true; + updated_transaction_and_outputs( + transaction, + Some(self.client().block_id(&included_block).await?), + // block metadata was Conflicting, but it's confirmed in another attachment + InclusionState::Confirmed, + &mut updated_transactions, + &mut spent_output_ids, + ); + } else { + log::debug!("[SYNC] conflicting transaction {transaction_id}"); + updated_transaction_and_outputs( + transaction, + None, + InclusionState::Conflicting, + &mut updated_transactions, + &mut spent_output_ids, + ); + } + } else if let Some(tx_state) = metadata.transaction_metadata.map(|m| m.transaction_state) { + println!("tx_state: {tx_state:?}"); match tx_state { // TODO: Separate TransactionState::Finalized, TransactionState::Accepted? https://github.com/iotaledger/iota-sdk/issues/1814 TransactionState::Accepted From 45d903f9097abbd7732fcd4d27016fcad35d3275 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Tue, 12 Mar 2024 20:37:07 +0100 Subject: [PATCH 2/8] Correctly set the block id, try multiple times to post a block --- sdk/src/wallet/operations/block.rs | 10 +++ .../wallet/operations/syncing/transactions.rs | 75 +++++++++++-------- sdk/src/wallet/operations/transaction/mod.rs | 12 +-- .../transaction/submit_transaction.rs | 26 ------- 4 files changed, 61 insertions(+), 62 deletions(-) delete mode 100644 sdk/src/wallet/operations/transaction/submit_transaction.rs diff --git a/sdk/src/wallet/operations/block.rs b/sdk/src/wallet/operations/block.rs index afdfb18240..2b6d1aa1ff 100644 --- a/sdk/src/wallet/operations/block.rs +++ b/sdk/src/wallet/operations/block.rs @@ -12,6 +12,8 @@ use crate::{ wallet::{Wallet, WalletError}, }; +const MAX_POST_BLOCK_ATTEMPTS: u64 = 3; + impl Wallet where WalletError: From, @@ -65,6 +67,14 @@ where self.emit(WalletEvent::TransactionProgress(TransactionProgressEvent::Broadcasting)) .await; + for attempt in 1..MAX_POST_BLOCK_ATTEMPTS { + if let Ok(block_id) = self.client().post_block(&block).await { + log::debug!("submitted block {}", block_id); + return Ok(block_id); + } + tokio::time::sleep(std::time::Duration::from_secs(attempt)).await; + } + let block_id = self.client().post_block(&block).await?; log::debug!("submitted block {}", block_id); diff --git a/sdk/src/wallet/operations/syncing/transactions.rs b/sdk/src/wallet/operations/syncing/transactions.rs index 0dd36f1ca1..8cbce452de 100644 --- a/sdk/src/wallet/operations/syncing/transactions.rs +++ b/sdk/src/wallet/operations/syncing/transactions.rs @@ -6,7 +6,7 @@ use std::collections::HashMap; use crate::{ client::{secret::SecretManage, ClientError}, types::{ - api::core::{BlockState, TransactionState}, + api::core::TransactionState, block::{input::Input, output::OutputId, BlockId}, }, wallet::{ @@ -128,10 +128,20 @@ where metadata.earliest_attachment_slot ); confirmed_unknown_output = true; + + let mut block_id = transaction.block_id; + if transaction.block_id.is_none() { + if let Ok(metadata) = self + .client() + .get_included_block_metadata(&transaction.payload.transaction().id()) + .await + { + block_id.replace(metadata.block_id); + } + } updated_transaction_and_outputs( transaction, - // Some(metadata.block_id), - None, + block_id, InclusionState::Confirmed, &mut updated_transactions, &mut spent_output_ids, @@ -156,9 +166,20 @@ where ); } else { log::debug!("[SYNC] conflicting transaction {transaction_id}"); + + let mut block_id = transaction.block_id; + if transaction.block_id.is_none() { + if let Ok(metadata) = self + .client() + .get_included_block_metadata(&transaction.payload.transaction().id()) + .await + { + block_id.replace(metadata.block_id); + } + } updated_transaction_and_outputs( transaction, - None, + block_id, InclusionState::Conflicting, &mut updated_transactions, &mut spent_output_ids, @@ -168,14 +189,6 @@ where // Do nothing, just need to wait a bit more TransactionState::Pending => {} } - // else if input_got_spent { - // process_transaction_with_unknown_state( - // &*self.ledger().await, - // transaction, - // &mut updated_transactions, - // &mut output_ids_to_unlock, - // )?; - // } } Err(ClientError::Node(crate::client::node_api::error::Error::NotFound(_))) => { if input_got_spent { @@ -185,28 +198,26 @@ where &mut updated_transactions, &mut output_ids_to_unlock, )?; + } else { + log::debug!( + "[SYNC] setting transaction {transaction_id} without block as conflicting so inputs get available again" + ); + for input in transaction.payload.transaction().inputs() { + let Input::Utxo(input) = input; + output_ids_to_unlock.push(*input.output_id()); + } + updated_transaction_and_outputs( + transaction, + None, + // No block with this transaction, set it as conflicting so the inputs get available again + InclusionState::Conflicting, + &mut updated_transactions, + &mut spent_output_ids, + ); } } Err(e) => return Err(e.into()), } - - // else if input_got_spent { - // process_transaction_with_unknown_state( - // &*self.ledger().await, - // transaction, - // &mut updated_transactions, - // &mut output_ids_to_unlock, - // )?; - // } - // else { - // // Reissue if there was no block id yet, because then we also didn't burn any mana - // log::debug!("[SYNC] reissue transaction {}", transaction.transaction_id); - // let reissued_block = self - // .submit_signed_transaction(transaction.payload.clone(), None) - // .await?; - // transaction.block_id.replace(reissued_block); - // updated_transactions.push(transaction); - // } } // updates account with balances, output ids, outputs @@ -225,7 +236,9 @@ fn updated_transaction_and_outputs( updated_transactions: &mut Vec, spent_output_ids: &mut Vec, ) { - transaction.block_id = block_id; + if block_id.is_some() { + transaction.block_id = block_id; + } transaction.inclusion_state = inclusion_state; // get spent inputs for input in transaction.payload.transaction().inputs() { diff --git a/sdk/src/wallet/operations/transaction/mod.rs b/sdk/src/wallet/operations/transaction/mod.rs index 063180d48d..5f4c953b08 100644 --- a/sdk/src/wallet/operations/transaction/mod.rs +++ b/sdk/src/wallet/operations/transaction/mod.rs @@ -7,7 +7,6 @@ pub(crate) mod high_level; pub(crate) mod prepare_output; mod send_outputs; mod sign_transaction; -pub(crate) mod submit_transaction; #[cfg(feature = "storage")] use crate::wallet::core::WalletLedgerDto; @@ -17,7 +16,10 @@ use crate::{ secret::SecretManage, ClientError, }, - types::block::{output::OutputWithMetadata, payload::signed_transaction::SignedTransactionPayload}, + types::block::{ + output::OutputWithMetadata, + payload::{signed_transaction::SignedTransactionPayload, Payload}, + }, wallet::{ types::{InclusionState, TransactionWithMetadata}, Wallet, WalletError, @@ -85,11 +87,11 @@ where } drop(wallet_ledger); - // Ignore errors from sending, we will try to send it again during [`sync_pending_transactions`] let block_id = match self - .submit_signed_transaction( - signed_transaction_data.payload.clone(), + .submit_basic_block( + Some(Payload::from(signed_transaction_data.payload.clone())), options.as_ref().and_then(|options| options.issuer_id), + true, ) .await { diff --git a/sdk/src/wallet/operations/transaction/submit_transaction.rs b/sdk/src/wallet/operations/transaction/submit_transaction.rs deleted file mode 100644 index 994299045a..0000000000 --- a/sdk/src/wallet/operations/transaction/submit_transaction.rs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2021 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use crate::{ - client::{secret::SecretManage, ClientError}, - types::block::{output::AccountId, payload::Payload, BlockId}, - wallet::{operations::transaction::SignedTransactionPayload, Wallet, WalletError}, -}; - -impl Wallet -where - WalletError: From, - ClientError: From, -{ - /// Submits a signed transaction in a block. - pub(crate) async fn submit_signed_transaction( - &self, - payload: SignedTransactionPayload, - issuer_id: impl Into> + Send, - ) -> Result { - log::debug!("[TRANSACTION] submit_signed_transaction"); - - self.submit_basic_block(Some(Payload::from(payload)), issuer_id, true) - .await - } -} From 91e2eb47cf980f88aa9e424724dead8bbdc08495 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Mon, 25 Mar 2024 17:07:44 +0100 Subject: [PATCH 3/8] Filter issuer accounts by network id, add block id debug log --- sdk/src/wallet/core/mod.rs | 5 ++++- sdk/src/wallet/operations/block.rs | 7 +++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/sdk/src/wallet/core/mod.rs b/sdk/src/wallet/core/mod.rs index 30fa56f3e7..e9d5a75eaf 100644 --- a/sdk/src/wallet/core/mod.rs +++ b/sdk/src/wallet/core/mod.rs @@ -299,9 +299,12 @@ impl WalletLedger { } // Returns the first possible unexpired block issuer Account id, which can be an implicit account. - pub fn first_block_issuer_account_id(&self, current_slot: SlotIndex) -> Option { + pub fn first_block_issuer_account_id(&self, current_slot: SlotIndex, network_id: u64) -> Option { self.accounts() .find_map(|o| { + if o.network_id != network_id { + return None; + } let account = o.output.as_account(); account.features().block_issuer().and_then(|block_issuer| { if block_issuer.expiry_slot() > current_slot { diff --git a/sdk/src/wallet/operations/block.rs b/sdk/src/wallet/operations/block.rs index 2995610223..b26b7fce01 100644 --- a/sdk/src/wallet/operations/block.rs +++ b/sdk/src/wallet/operations/block.rs @@ -31,17 +31,18 @@ where Some(id) => id, None => { let current_slot = self.client().get_slot_index().await?; + let network_id = self.client().get_network_id().await?; self.ledger() .await - .first_block_issuer_account_id(current_slot) + .first_block_issuer_account_id(current_slot, network_id) .ok_or(WalletError::AccountNotFound)? } }; let unsigned_block = self.client().build_basic_block(issuer_id, payload).await?; + let protocol_parameters = self.client().get_protocol_parameters().await?; if !allow_negative_bic { - let protocol_parameters = self.client().get_protocol_parameters().await?; let work_score = protocol_parameters.work_score(unsigned_block.body.as_basic()); let congestion = self.client().get_account_congestion(&issuer_id, work_score).await?; if (congestion.reference_mana_cost * work_score as u64) as i128 > congestion.block_issuance_credits { @@ -69,6 +70,8 @@ where self.emit(WalletEvent::TransactionProgress(TransactionProgressEvent::Broadcasting)) .await; + log::debug!("submitting block {}", block.id(&protocol_parameters)); + log::debug!("submitting block {:?}", block); for attempt in 1..MAX_POST_BLOCK_ATTEMPTS { if let Ok(block_id) = self.client().post_block(&block).await { log::debug!("submitted block {}", block_id); From db04a1371e17d646baf39bb43f35867aaec88005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Tue, 26 Mar 2024 16:20:20 +0100 Subject: [PATCH 4/8] Add debug logs and test --- sdk/src/wallet/operations/block.rs | 14 +++ .../transaction_builder/account_outputs.rs | 110 +++++++++++------- 2 files changed, 83 insertions(+), 41 deletions(-) diff --git a/sdk/src/wallet/operations/block.rs b/sdk/src/wallet/operations/block.rs index b26b7fce01..580fc80ba5 100644 --- a/sdk/src/wallet/operations/block.rs +++ b/sdk/src/wallet/operations/block.rs @@ -71,6 +71,20 @@ where .await; log::debug!("submitting block {}", block.id(&protocol_parameters)); + log::debug!( + "submitting block alloted {}/{} max burned mana", + block + .as_basic() + .payload() + .unwrap() + .as_signed_transaction() + .transaction() + .allotments() + .first() + .unwrap() + .mana(), + block.as_basic().max_burned_mana(), + ); log::debug!("submitting block {:?}", block); for attempt in 1..MAX_POST_BLOCK_ATTEMPTS { if let Ok(block_id) = self.client().post_block(&block).await { diff --git a/sdk/tests/client/transaction_builder/account_outputs.rs b/sdk/tests/client/transaction_builder/account_outputs.rs index 86533dd7c5..ec165a7207 100644 --- a/sdk/tests/client/transaction_builder/account_outputs.rs +++ b/sdk/tests/client/transaction_builder/account_outputs.rs @@ -3,23 +3,37 @@ use std::str::FromStr; +use crypto::keys::bip44::Bip44; use iota_sdk::{ client::{ - api::transaction_builder::{Burn, Requirement, TransactionBuilder, TransactionBuilderError, Transitions}, - secret::types::InputSigningData, + api::{ + transaction_builder::{Burn, Requirement, TransactionBuilder, TransactionBuilderError, Transitions}, + GetAddressesOptions, + }, + constants::SHIMMER_COIN_TYPE, + secret::{mnemonic::MnemonicSecretManager, types::InputSigningData, SecretManage, SecretManager}, + Client, }, types::block::{ address::{Address, ImplicitAccountCreationAddress}, + core::{ + basic::{MaxBurnedManaAmount, StrongParents}, + BlockHeader, + }, mana::ManaAllotment, output::{ feature::{BlockIssuerFeature, BlockIssuerKeys, Ed25519PublicKeyHashBlockIssuerKey}, unlock_condition::AddressUnlockCondition, AccountId, AccountOutputBuilder, BasicOutputBuilder, Output, }, - payload::signed_transaction::{TransactionCapabilities, TransactionCapabilityFlag}, - protocol::iota_mainnet_protocol_parameters, + payload::{ + signed_transaction::{TransactionCapabilities, TransactionCapabilityFlag}, + Payload, SignedTransactionPayload, + }, + protocol::{iota_mainnet_protocol_parameters, WorkScore}, rand::output::{rand_output_id_with_slot_index, rand_output_metadata_with_id}, - slot::SlotIndex, + slot::{SlotCommitmentId, SlotIndex}, + BlockBody, BlockId, UnsignedBlock, }, }; use pretty_assertions::{assert_eq, assert_ne}; @@ -2408,35 +2422,38 @@ fn account_transition_with_required_context_inputs() { ); } -#[test] -fn send_amount_from_block_issuer_account_with_generated_mana() { +#[tokio::test] +async fn basic_input_automatic_allotment() { + let secret_manager = SecretManager::try_from_mnemonic(Client::generate_mnemonic().unwrap()).unwrap(); + + let ed25519_address = secret_manager + .generate_ed25519_addresses(GetAddressesOptions::default().with_range(0..1)) + .await + .unwrap()[0] + .clone() + .into_inner(); + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); - let ed25519_address = Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(); - let inputs = [AccountOutputBuilder::new_with_amount(10_000_000, account_id_1) - .with_mana(20000) - .add_unlock_condition(AddressUnlockCondition::new( - Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), - )) - .with_features([BlockIssuerFeature::new( - u32::MAX, - BlockIssuerKeys::from_vec(vec![ - Ed25519PublicKeyHashBlockIssuerKey::new(**ed25519_address.as_ed25519()).into(), - ]) - .unwrap(), - ) - .unwrap()]) - .finish_output() - .unwrap()]; - let inputs = inputs - .into_iter() - .map(|input| InputSigningData { - output: input, - output_metadata: rand_output_metadata_with_id(rand_output_id_with_slot_index(SlotIndex(5))), - chain: None, - }) - .collect::>(); + let reference_mana_cost = 1; + + let inputs = build_inputs( + [( + Basic { + amount: 10_000_000, + mana: 10_000, + address: ed25519_address.clone(), + native_token: None, + sender: None, + sdruc: None, + timelock: None, + expiration: None, + }, + Some(Bip44::new(SHIMMER_COIN_TYPE)), + )], + Some(SLOT_INDEX), + ); let outputs = vec![ BasicOutputBuilder::new_with_amount(1_000_000) @@ -2448,25 +2465,36 @@ fn send_amount_from_block_issuer_account_with_generated_mana() { let selected = TransactionBuilder::new( inputs.clone(), outputs.clone(), - [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], + [ed25519_address], SLOT_INDEX, SLOT_COMMITMENT_ID, - protocol_parameters, + protocol_parameters.clone(), ) - .with_min_mana_allotment(account_id_1, 2) - .with_remainder_address(Address::Account(account_id_1.into())) + .with_min_mana_allotment(account_id_1, reference_mana_cost) + .finish() + .unwrap(); + + let unlocks = secret_manager + .transaction_unlocks(&selected, &protocol_parameters) + .await + .unwrap(); + + let signed_transaction_payload = SignedTransactionPayload::new(selected.transaction.clone(), unlocks).unwrap(); + + let basic_block_body = BlockBody::build_basic( + StrongParents::from_vec(vec![BlockId::new([0; 36])]).unwrap(), + (protocol_parameters.work_score_parameters(), reference_mana_cost), + ) + .with_payload(Payload::from(signed_transaction_payload)) .finish() .unwrap(); assert!(unsorted_eq(&selected.inputs_data, &inputs)); assert_eq!(selected.transaction.outputs().len(), 2); - assert!(selected.transaction.outputs()[1].is_account()); assert_eq!(selected.transaction.allotments().len(), 1); - // Required context inputs are added when the account is transitioned - assert_eq!(selected.transaction.context_inputs().len(), 2); - assert!(selected.transaction.context_inputs().commitment().is_some()); assert_eq!( - selected.transaction.context_inputs().block_issuance_credits().count(), - 1 + // 73 less than it should be? + selected.transaction.allotments().first().unwrap().mana(), + basic_block_body.max_burned_mana(), ); } From 304e9d07c820b5da6515aae65ebc0aad93654bc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Tue, 26 Mar 2024 16:28:50 +0100 Subject: [PATCH 5/8] Move test --- .../transaction_builder/account_outputs.rs | 77 ---------------- .../transaction_builder/basic_outputs.rs | 90 ++++++++++++++++++- 2 files changed, 89 insertions(+), 78 deletions(-) diff --git a/sdk/tests/client/transaction_builder/account_outputs.rs b/sdk/tests/client/transaction_builder/account_outputs.rs index ec165a7207..26933e50c0 100644 --- a/sdk/tests/client/transaction_builder/account_outputs.rs +++ b/sdk/tests/client/transaction_builder/account_outputs.rs @@ -2421,80 +2421,3 @@ fn account_transition_with_required_context_inputs() { 1 ); } - -#[tokio::test] -async fn basic_input_automatic_allotment() { - let secret_manager = SecretManager::try_from_mnemonic(Client::generate_mnemonic().unwrap()).unwrap(); - - let ed25519_address = secret_manager - .generate_ed25519_addresses(GetAddressesOptions::default().with_range(0..1)) - .await - .unwrap()[0] - .clone() - .into_inner(); - - let protocol_parameters = iota_mainnet_protocol_parameters().clone(); - let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); - - let reference_mana_cost = 1; - - let inputs = build_inputs( - [( - Basic { - amount: 10_000_000, - mana: 10_000, - address: ed25519_address.clone(), - native_token: None, - sender: None, - sdruc: None, - timelock: None, - expiration: None, - }, - Some(Bip44::new(SHIMMER_COIN_TYPE)), - )], - Some(SLOT_INDEX), - ); - - let outputs = vec![ - BasicOutputBuilder::new_with_amount(1_000_000) - .add_unlock_condition(AddressUnlockCondition::new(ed25519_address.clone())) - .finish_output() - .unwrap(), - ]; - - let selected = TransactionBuilder::new( - inputs.clone(), - outputs.clone(), - [ed25519_address], - SLOT_INDEX, - SLOT_COMMITMENT_ID, - protocol_parameters.clone(), - ) - .with_min_mana_allotment(account_id_1, reference_mana_cost) - .finish() - .unwrap(); - - let unlocks = secret_manager - .transaction_unlocks(&selected, &protocol_parameters) - .await - .unwrap(); - - let signed_transaction_payload = SignedTransactionPayload::new(selected.transaction.clone(), unlocks).unwrap(); - - let basic_block_body = BlockBody::build_basic( - StrongParents::from_vec(vec![BlockId::new([0; 36])]).unwrap(), - (protocol_parameters.work_score_parameters(), reference_mana_cost), - ) - .with_payload(Payload::from(signed_transaction_payload)) - .finish() - .unwrap(); - - assert!(unsorted_eq(&selected.inputs_data, &inputs)); - assert_eq!(selected.transaction.outputs().len(), 2); - assert_eq!(selected.transaction.allotments().len(), 1); - assert_eq!( - // 73 less than it should be? - selected.transaction.allotments().first().unwrap().mana(), - basic_block_body.max_burned_mana(), - ); -} diff --git a/sdk/tests/client/transaction_builder/basic_outputs.rs b/sdk/tests/client/transaction_builder/basic_outputs.rs index aa9f36d366..101cdddce4 100644 --- a/sdk/tests/client/transaction_builder/basic_outputs.rs +++ b/sdk/tests/client/transaction_builder/basic_outputs.rs @@ -3,13 +3,25 @@ use std::str::FromStr; +use crypto::keys::bip44::Bip44; use iota_sdk::{ - client::api::transaction_builder::{Requirement, TransactionBuilder, TransactionBuilderError}, + client::{ + api::{ + transaction_builder::{Requirement, TransactionBuilder, TransactionBuilderError}, + GetAddressesOptions, + }, + constants::SHIMMER_COIN_TYPE, + secret::{SecretManage, SecretManager}, + Client, + }, types::block::{ address::{Address, AddressCapabilities, MultiAddress, RestrictedAddress, WeightedAddress}, + core::basic::StrongParents, mana::ManaAllotment, output::{unlock_condition::AddressUnlockCondition, AccountId, BasicOutputBuilder, NftId}, + payload::{Payload, SignedTransactionPayload}, protocol::iota_mainnet_protocol_parameters, + BlockBody, BlockId, }, }; use pretty_assertions::assert_eq; @@ -2596,3 +2608,79 @@ fn automatic_allotment_provided_in_and_output() { ); assert_eq!(selected.transaction.outputs()[0].mana(), 1); } + +#[tokio::test] +async fn basic_input_automatic_allotment() { + let secret_manager = SecretManager::try_from_mnemonic(Client::generate_mnemonic().unwrap()).unwrap(); + + let ed25519_address = secret_manager + .generate_ed25519_addresses(GetAddressesOptions::default().with_range(0..1)) + .await + .unwrap()[0] + .clone() + .into_inner(); + + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); + let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); + + let reference_mana_cost = 1; + + let inputs = build_inputs( + [( + Basic { + amount: 10_000_000, + mana: 10_000, + address: ed25519_address.clone(), + native_token: None, + sender: None, + sdruc: None, + timelock: None, + expiration: None, + }, + Some(Bip44::new(SHIMMER_COIN_TYPE)), + )], + Some(SLOT_INDEX), + ); + + let outputs = vec![ + BasicOutputBuilder::new_with_amount(1_000_000) + .add_unlock_condition(AddressUnlockCondition::new(ed25519_address.clone())) + .finish_output() + .unwrap(), + ]; + + let selected = TransactionBuilder::new( + inputs.clone(), + outputs.clone(), + [ed25519_address], + SLOT_INDEX, + SLOT_COMMITMENT_ID, + protocol_parameters.clone(), + ) + .with_min_mana_allotment(account_id_1, reference_mana_cost) + .finish() + .unwrap(); + + let unlocks = secret_manager + .transaction_unlocks(&selected, &protocol_parameters) + .await + .unwrap(); + + let signed_transaction_payload = SignedTransactionPayload::new(selected.transaction.clone(), unlocks).unwrap(); + + let basic_block_body = BlockBody::build_basic( + StrongParents::from_vec(vec![BlockId::new([0; 36])]).unwrap(), + (protocol_parameters.work_score_parameters(), reference_mana_cost), + ) + .with_payload(Payload::from(signed_transaction_payload)) + .finish() + .unwrap(); + + assert!(unsorted_eq(&selected.inputs_data, &inputs)); + assert_eq!(selected.transaction.outputs().len(), 2); + assert_eq!(selected.transaction.allotments().len(), 1); + assert_eq!( + selected.transaction.allotments().first().unwrap().mana(), + basic_block_body.max_burned_mana(), + ); +} From db59bfbd3dd5f51e020a0d1c498cc7352bd5b7aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Tue, 26 Mar 2024 17:10:47 +0100 Subject: [PATCH 6/8] Cleanup --- sdk/src/wallet/operations/block.rs | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/sdk/src/wallet/operations/block.rs b/sdk/src/wallet/operations/block.rs index 580fc80ba5..d18294f7eb 100644 --- a/sdk/src/wallet/operations/block.rs +++ b/sdk/src/wallet/operations/block.rs @@ -26,22 +26,23 @@ where allow_negative_bic: bool, ) -> Result { log::debug!("submit_basic_block"); + let protocol_parameters = self.client().get_protocol_parameters().await?; + // If an issuer ID is provided, use it; otherwise, use the first available account or implicit account. let issuer_id = match issuer_id.into() { Some(id) => id, None => { let current_slot = self.client().get_slot_index().await?; - let network_id = self.client().get_network_id().await?; + self.ledger() .await - .first_block_issuer_account_id(current_slot, network_id) + .first_block_issuer_account_id(current_slot, protocol_parameters.network_id()) .ok_or(WalletError::AccountNotFound)? } }; let unsigned_block = self.client().build_basic_block(issuer_id, payload).await?; - let protocol_parameters = self.client().get_protocol_parameters().await?; if !allow_negative_bic { let work_score = protocol_parameters.work_score(unsigned_block.body.as_basic()); let congestion = self.client().get_account_congestion(&issuer_id, work_score).await?; @@ -71,21 +72,6 @@ where .await; log::debug!("submitting block {}", block.id(&protocol_parameters)); - log::debug!( - "submitting block alloted {}/{} max burned mana", - block - .as_basic() - .payload() - .unwrap() - .as_signed_transaction() - .transaction() - .allotments() - .first() - .unwrap() - .mana(), - block.as_basic().max_burned_mana(), - ); - log::debug!("submitting block {:?}", block); for attempt in 1..MAX_POST_BLOCK_ATTEMPTS { if let Ok(block_id) = self.client().post_block(&block).await { log::debug!("submitted block {}", block_id); @@ -94,9 +80,11 @@ where tokio::time::sleep(std::time::Duration::from_secs(attempt)).await; } + log::debug!("submitting block {block:?}"); + let block_id = self.client().post_block(&block).await?; - log::debug!("submitted block {}", block_id); + log::debug!("submitted block {block_id}"); Ok(block_id) } From 1cdb86931352ea28b7d8b6a3011487383801fa01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Tue, 26 Mar 2024 17:12:54 +0100 Subject: [PATCH 7/8] Bring back send_amount_from_block_issuer_account_with_generated_mana test --- .../transaction_builder/account_outputs.rs | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/sdk/tests/client/transaction_builder/account_outputs.rs b/sdk/tests/client/transaction_builder/account_outputs.rs index 26933e50c0..67cc3aa33a 100644 --- a/sdk/tests/client/transaction_builder/account_outputs.rs +++ b/sdk/tests/client/transaction_builder/account_outputs.rs @@ -2421,3 +2421,66 @@ fn account_transition_with_required_context_inputs() { 1 ); } + +#[test] +fn send_amount_from_block_issuer_account_with_generated_mana() { + let protocol_parameters = iota_mainnet_protocol_parameters().clone(); + let account_id_1 = AccountId::from_str(ACCOUNT_ID_1).unwrap(); + let ed25519_address = Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(); + + let inputs = [AccountOutputBuilder::new_with_amount(10_000_000, account_id_1) + .with_mana(20000) + .add_unlock_condition(AddressUnlockCondition::new( + Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap(), + )) + .with_features([BlockIssuerFeature::new( + u32::MAX, + BlockIssuerKeys::from_vec(vec![ + Ed25519PublicKeyHashBlockIssuerKey::new(**ed25519_address.as_ed25519()).into(), + ]) + .unwrap(), + ) + .unwrap()]) + .finish_output() + .unwrap()]; + let inputs = inputs + .into_iter() + .map(|input| InputSigningData { + output: input, + output_metadata: rand_output_metadata_with_id(rand_output_id_with_slot_index(SlotIndex(5))), + chain: None, + }) + .collect::>(); + + let outputs = vec![ + BasicOutputBuilder::new_with_amount(1_000_000) + .add_unlock_condition(AddressUnlockCondition::new(ed25519_address.clone())) + .finish_output() + .unwrap(), + ]; + + let selected = TransactionBuilder::new( + inputs.clone(), + outputs.clone(), + [Address::try_from_bech32(BECH32_ADDRESS_ED25519_0).unwrap()], + SLOT_INDEX, + SLOT_COMMITMENT_ID, + protocol_parameters, + ) + .with_min_mana_allotment(account_id_1, 2) + .with_remainder_address(Address::Account(account_id_1.into())) + .finish() + .unwrap(); + + assert!(unsorted_eq(&selected.inputs_data, &inputs)); + assert_eq!(selected.transaction.outputs().len(), 2); + assert!(selected.transaction.outputs()[1].is_account()); + assert_eq!(selected.transaction.allotments().len(), 1); + // Required context inputs are added when the account is transitioned + assert_eq!(selected.transaction.context_inputs().len(), 2); + assert!(selected.transaction.context_inputs().commitment().is_some()); + assert_eq!( + selected.transaction.context_inputs().block_issuance_credits().count(), + 1 + ); +} From a60ab41e7537423e2c717d60e5482dc11c76e071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thoralf=20M=C3=BCller?= Date: Wed, 27 Mar 2024 21:04:19 +0100 Subject: [PATCH 8/8] Remove the duplicated code --- sdk/src/wallet/operations/block.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/sdk/src/wallet/operations/block.rs b/sdk/src/wallet/operations/block.rs index d18294f7eb..8a3dc576f6 100644 --- a/sdk/src/wallet/operations/block.rs +++ b/sdk/src/wallet/operations/block.rs @@ -72,20 +72,20 @@ where .await; log::debug!("submitting block {}", block.id(&protocol_parameters)); - for attempt in 1..MAX_POST_BLOCK_ATTEMPTS { - if let Ok(block_id) = self.client().post_block(&block).await { - log::debug!("submitted block {}", block_id); - return Ok(block_id); + log::debug!("submitting block {block:?}"); + + let mut attempt = 1; + loop { + match self.client().post_block(&block).await { + Ok(block_id) => break Ok(block_id), + Err(err) => { + if attempt >= MAX_POST_BLOCK_ATTEMPTS { + return Err(err.into()); + } + } } tokio::time::sleep(std::time::Duration::from_secs(attempt)).await; + attempt += 1; } - - log::debug!("submitting block {block:?}"); - - let block_id = self.client().post_block(&block).await?; - - log::debug!("submitted block {block_id}"); - - Ok(block_id) } }