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 c6f7e56c91..8a3dc576f6 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, @@ -24,14 +26,17 @@ 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?; + self.ledger() .await - .first_block_issuer_account_id(current_slot) + .first_block_issuer_account_id(current_slot, protocol_parameters.network_id()) .ok_or(WalletError::AccountNotFound)? } }; @@ -39,7 +44,6 @@ where let unsigned_block = self.client().build_basic_block(issuer_id, payload).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 { @@ -67,10 +71,21 @@ where self.emit(WalletEvent::TransactionProgress(TransactionProgressEvent::Broadcasting)) .await; - let block_id = self.client().post_block(&block).await?; + log::debug!("submitting block {}", block.id(&protocol_parameters)); + log::debug!("submitting block {block:?}"); - log::debug!("submitted block {}", block_id); - - Ok(block_id) + 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; + } } } diff --git a/sdk/src/wallet/operations/syncing/transactions.rs b/sdk/src/wallet/operations/syncing/transactions.rs index 0e88e68ac4..8cbce452de 100644 --- a/sdk/src/wallet/operations/syncing/transactions.rs +++ b/sdk/src/wallet/operations/syncing/transactions.rs @@ -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 - } -} diff --git a/sdk/tests/client/transaction_builder/account_outputs.rs b/sdk/tests/client/transaction_builder/account_outputs.rs index 86533dd7c5..67cc3aa33a 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}; 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(), + ); +}