From 571a598442710bec36fe8f843cc4204699d4d248 Mon Sep 17 00:00:00 2001 From: Thibault Martinez Date: Thu, 28 Mar 2024 13:22:03 +0100 Subject: [PATCH 1/2] Remove remaining mentions of milestones (#2086) * Remove remaining mentions of milestones * force_syncing * filter options * Remove TransactionWithMetadata::timestamp * Remove outdated code * fix * fix cli * CLI formatted creation_time * Add duration to example * Reverse conditions * Happy Thoralf noises --------- Co-authored-by: DaughterOfMars Co-authored-by: /alex/ --- .../nodejs/lib/types/wallet/transaction.ts | 2 - bindings/nodejs/lib/types/wallet/wallet.ts | 16 ++++---- .../python/iota_sdk/types/filter_options.py | 6 +-- .../types/transaction_with_metadata.py | 2 - .../python/iota_sdk/wallet/sync_options.py | 7 ++-- cli/src/wallet_cli/mod.rs | 28 ++++++++++--- .../block/01_block_confirmation_time.rs | 40 +++++++++---------- sdk/src/client/constants.rs | 2 +- .../block/output/unlock_condition/error.rs | 4 +- sdk/src/wallet/core/mod.rs | 23 +++++------ sdk/src/wallet/mod.rs | 15 +++---- sdk/src/wallet/operations/syncing/options.rs | 6 +-- sdk/src/wallet/operations/transaction/mod.rs | 1 - sdk/src/wallet/types/mod.rs | 9 ----- 14 files changed, 78 insertions(+), 83 deletions(-) diff --git a/bindings/nodejs/lib/types/wallet/transaction.ts b/bindings/nodejs/lib/types/wallet/transaction.ts index 60eb35692e..5aa0c75fc7 100644 --- a/bindings/nodejs/lib/types/wallet/transaction.ts +++ b/bindings/nodejs/lib/types/wallet/transaction.ts @@ -31,8 +31,6 @@ export class TransactionWithMetadata { blockId?: BlockId; /** The inclusion state of the transaction */ inclusionState!: InclusionState; - /** The creation time */ - timestamp!: string; /** The transaction id */ transactionId!: TransactionId; /** The network id in which the transaction was sent */ diff --git a/bindings/nodejs/lib/types/wallet/wallet.ts b/bindings/nodejs/lib/types/wallet/wallet.ts index 88e53d3b6e..5edc9d066d 100644 --- a/bindings/nodejs/lib/types/wallet/wallet.ts +++ b/bindings/nodejs/lib/types/wallet/wallet.ts @@ -11,7 +11,7 @@ import { import { DecayedMana, HexEncodedString, u256, u64 } from '../utils'; import { ClientOptions } from '../client'; import { Bip44, SecretManagerType } from '../secret_manager/secret-manager'; -import { Bech32Address } from '../block'; +import { Bech32Address, SlotIndex } from '../block'; /** Options for the Wallet builder. */ export interface WalletOptions { @@ -99,9 +99,9 @@ export interface NativeTokenBalance { /** Sync options for a wallet */ export interface SyncOptions { /** - * Usually syncing is skipped if it's called in between 200ms, because there can only be new changes every - * milestone and calling it twice "at the same time" will not return new data - * When this to true, we will sync anyways, even if it's called 0ms after the las sync finished. Default: false. + * Syncing is usually skipped if it's called repeatedly in a short amount of time as there can only be new changes every + * slot and calling it twice "at the same time" will not return new data. + * When this to true, we sync anyways, even if it's called 0ms after the last sync finished. Default: false. */ forceSyncing?: boolean; /// Try to sync transactions from incoming outputs with their inputs. Some data may not be obtained if it has been @@ -163,10 +163,10 @@ export interface NftSyncOptions { /** Options to filter outputs */ export interface FilterOptions { - /** Filter all outputs where the booked milestone index is below the specified timestamp */ - lowerBoundBookedTimestamp?: number; - /** Filter all outputs where the booked milestone index is above the specified timestamp */ - upperBoundBookedTimestamp?: number; + /** Include all outputs where the included slot is below the specified slot */ + includedBelowSlot?: SlotIndex; + /** Include all outputs where the included slot is above the specified slot */ + includedAboveSlot?: SlotIndex; /** Filter all outputs for the provided types (Basic = 3, Account = 4, Foundry = 5, NFT = 6) */ outputTypes?: number[]; /** Return all account outputs matching these IDs. */ diff --git a/bindings/python/iota_sdk/types/filter_options.py b/bindings/python/iota_sdk/types/filter_options.py index ae6ec2cf48..42f726cc1b 100644 --- a/bindings/python/iota_sdk/types/filter_options.py +++ b/bindings/python/iota_sdk/types/filter_options.py @@ -4,7 +4,7 @@ from __future__ import annotations from typing import List, Optional from dataclasses import dataclass -from iota_sdk.types.common import json +from iota_sdk.types.common import json, SlotIndex @json @@ -13,8 +13,8 @@ class FilterOptions: """Options to filter outputs. """ - lowerBoundBookedTimestamp: Optional[int] = None - upperBoundBookedTimestamp: Optional[int] = None + includedBelowSlot: Optional[SlotIndex] = None + includedAboveSlot: Optional[SlotIndex] = None outputTypes: Optional[List[int]] = None accountIds: Optional[List[str]] = None foundryIds: Optional[List[str]] = None diff --git a/bindings/python/iota_sdk/types/transaction_with_metadata.py b/bindings/python/iota_sdk/types/transaction_with_metadata.py index 68df84391d..66c8640ec8 100644 --- a/bindings/python/iota_sdk/types/transaction_with_metadata.py +++ b/bindings/python/iota_sdk/types/transaction_with_metadata.py @@ -39,7 +39,6 @@ class TransactionWithMetadata: Attributes: payload: The transaction payload. inclusion_state: The inclusion state of the transaction. - timestamp: The timestamp of the transaction. transaction_id: The ID of the corresponding transaction. network_id: The ID of the network this transaction was issued in. incoming: Indicates whether the transaction was created by the wallet or whether it was sent by someone else and is incoming. @@ -49,7 +48,6 @@ class TransactionWithMetadata: """ payload: SignedTransactionPayload inclusion_state: InclusionState - timestamp: int transaction_id: TransactionId network_id: int incoming: bool diff --git a/bindings/python/iota_sdk/wallet/sync_options.py b/bindings/python/iota_sdk/wallet/sync_options.py index 7a2b702c1b..f32f5acf9a 100644 --- a/bindings/python/iota_sdk/wallet/sync_options.py +++ b/bindings/python/iota_sdk/wallet/sync_options.py @@ -69,10 +69,9 @@ class SyncOptions: **Attributes** force_syncing : - Usually syncing is skipped if it's called in between 200ms, because there can only be new - changes every milestone and calling it twice "at the same time" will not return new data. - When this is set to true, we will sync anyways, even if it's called 0ms after the last sync - finished. + Syncing is usually skipped if it's called repeatedly in a short amount of time as there can only be new changes every + slot and calling it twice "at the same time" will not return new data. + When this to true, we sync anyways, even if it's called 0ms after the last sync finished. sync_incoming_transactions : Try to sync transactions from incoming outputs with their inputs. Some data may not be obtained if it has been pruned. diff --git a/cli/src/wallet_cli/mod.rs b/cli/src/wallet_cli/mod.rs index 9cff41dc0f..5b81f1363b 100644 --- a/cli/src/wallet_cli/mod.rs +++ b/cli/src/wallet_cli/mod.rs @@ -1159,7 +1159,12 @@ pub async fn transaction_command(wallet: &Wallet, selector: TransactionSelector) TransactionSelector::Id(id) => wallet_ledger.get_transaction(&id), TransactionSelector::Index(index) => { let mut transactions = wallet_ledger.transactions().values().collect::>(); - transactions.sort_unstable_by(|a, b| b.timestamp.cmp(&a.timestamp)); + transactions.sort_unstable_by(|a, b| { + b.payload + .transaction() + .creation_slot() + .cmp(&a.payload.transaction().creation_slot()) + }); transactions.into_iter().nth(index) } }; @@ -1177,7 +1182,12 @@ pub async fn transaction_command(wallet: &Wallet, selector: TransactionSelector) pub async fn transactions_command(wallet: &Wallet, show_details: bool) -> Result<(), Error> { let wallet_ledger = wallet.ledger().await; let mut transactions = wallet_ledger.transactions().values().collect::>(); - transactions.sort_unstable_by(|a, b| b.timestamp.cmp(&a.timestamp)); + transactions.sort_unstable_by(|a, b| { + b.payload + .transaction() + .creation_slot() + .cmp(&a.payload.transaction().creation_slot()) + }); if transactions.is_empty() { println_log_info!("No transactions found"); @@ -1186,10 +1196,16 @@ pub async fn transactions_command(wallet: &Wallet, show_details: bool) -> Result if show_details { println_log_info!("{:#?}", tx); } else { - let transaction_time = to_utc_date_time(tx.timestamp)?; - let formatted_time = transaction_time.format("%Y-%m-%d %H:%M:%S UTC").to_string(); - - println_log_info!("{:<5}{}\t{}", i, tx.transaction_id, formatted_time); + let protocol_parameters = wallet.client().get_protocol_parameters().await?; + let creation_slot = tx.payload.transaction().creation_slot(); + let creation_time = to_utc_date_time(creation_slot.to_timestamp( + protocol_parameters.genesis_unix_timestamp(), + protocol_parameters.slot_duration_in_seconds(), + ) as u128)? + .format("%Y-%m-%d %H:%M:%S UTC") + .to_string(); + + println_log_info!("{:<5}{}\t{}\t{}", i, tx.transaction_id, creation_slot, creation_time); } } } diff --git a/sdk/examples/client/block/01_block_confirmation_time.rs b/sdk/examples/client/block/01_block_confirmation_time.rs index e11bb9f84b..4748953ffb 100644 --- a/sdk/examples/client/block/01_block_confirmation_time.rs +++ b/sdk/examples/client/block/01_block_confirmation_time.rs @@ -8,6 +8,8 @@ //! cargo run --release --example block_confirmation_time //! ``` +use std::time::Instant; + use crypto::keys::bip44::Bip44; use iota_sdk::{ client::{ @@ -45,34 +47,32 @@ async fn main() -> Result<(), Box> { println!("{block:#?}"); + let mut block_state = BlockState::Pending; + let start = Instant::now(); + // Wait for the block to get included for _ in 0..30 { tokio::time::sleep(std::time::Duration::from_secs(1)).await; let metadata = client.get_block_metadata(&block_id).await?; - if let BlockState::Confirmed | BlockState::Finalized = metadata.block_state { + block_state = metadata.block_state; + + if let BlockState::Confirmed | BlockState::Finalized = block_state { break; } } - println!( - "Block with no payload included: {}/block/{}", - std::env::var("EXPLORER_URL").unwrap(), - block_id - ); - - // TODO uncomment when we have a new confirmation logic - // Get the block metadata. - // let metadata = client.get_block_metadata(&block_id).await?; - - // if let Some(ms_index) = metadata.referenced_by_milestone_index { - // let ms = client.get_milestone_by_index(ms_index).await?; - // println!( - // "Block {block_id} got confirmed by milestone {ms_index} at timestamp {}.", - // ms.essence().timestamp() - // ); - // } else { - // println!("Block {block_id} is not confirmed.") - // } + if let BlockState::Confirmed | BlockState::Finalized = block_state { + let duration = start.elapsed(); + + println!( + "Block with no payload included after {} seconds: {}/block/{}", + duration.as_secs(), + std::env::var("EXPLORER_URL").unwrap(), + block_id + ); + } else { + println!("Block with no payload was not included"); + } Ok(()) } diff --git a/sdk/src/client/constants.rs b/sdk/src/client/constants.rs index b4d9b0252f..ba82f47aa8 100644 --- a/sdk/src/client/constants.rs +++ b/sdk/src/client/constants.rs @@ -16,7 +16,7 @@ pub(crate) const DEFAULT_QUORUM_THRESHOLD: usize = 66; pub(crate) const DEFAULT_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")); #[cfg(not(target_family = "wasm"))] pub(crate) const MAX_PARALLEL_API_REQUESTS: usize = 100; -/// Max allowed difference between the local time and latest milestone time, 5 minutes in seconds +/// Max allowed difference between local time and tangle time, 5 minutes in seconds pub(crate) const FIVE_MINUTES_IN_NANOSECONDS: u64 = 300_000_000_000; /// Delay for caching a node info response in WASM runtime #[cfg(target_family = "wasm")] diff --git a/sdk/src/types/block/output/unlock_condition/error.rs b/sdk/src/types/block/output/unlock_condition/error.rs index 48f0bd8861..d6c0f8ee8f 100644 --- a/sdk/src/types/block/output/unlock_condition/error.rs +++ b/sdk/src/types/block/output/unlock_condition/error.rs @@ -12,9 +12,9 @@ pub enum UnlockConditionError { Kind(u8), #[display(fmt = "invalid unlock condition count: {_0}")] Count(>::Error), - #[display(fmt = "expiration unlock condition with milestone index and timestamp set to 0")] + #[display(fmt = "expiration unlock condition with slot index set to 0")] ExpirationZero, - #[display(fmt = "timelock unlock condition with milestone index and timestamp set to 0")] + #[display(fmt = "timelock unlock condition with slot index set to 0")] TimelockZero, #[display(fmt = "unlock conditions are not unique and/or sorted")] NotUniqueSorted, diff --git a/sdk/src/wallet/core/mod.rs b/sdk/src/wallet/core/mod.rs index 30fa56f3e7..e8290e1718 100644 --- a/sdk/src/wallet/core/mod.rs +++ b/sdk/src/wallet/core/mod.rs @@ -188,17 +188,17 @@ impl WalletLedger { _ => {} } - // TODO filter based on slot index - // if let Some(lower_bound_booked_timestamp) = filter.lower_bound_booked_timestamp { - // if output.metadata.milestone_timestamp_booked() < lower_bound_booked_timestamp { - // continue; - // } - // } - // if let Some(upper_bound_booked_timestamp) = filter.upper_bound_booked_timestamp { - // if output.metadata.milestone_timestamp_booked() > upper_bound_booked_timestamp { - // continue; - // } - // } + if let Some(included_below_slot) = filter.included_below_slot { + if output.metadata.included().slot() > included_below_slot { + return false; + } + } + + if let Some(included_above_slot) = filter.included_above_slot { + if output.metadata.included().slot() < included_above_slot { + return false; + } + } if let Some(output_types) = &filter.output_types { if !output_types.contains(&output.output.kind()) { @@ -655,7 +655,6 @@ mod test { payload: tx_payload, block_id: None, network_id: 0, - timestamp: 0, inclusion_state: InclusionState::Pending, incoming: false, note: None, diff --git a/sdk/src/wallet/mod.rs b/sdk/src/wallet/mod.rs index 590c1f24ee..8b704305ab 100644 --- a/sdk/src/wallet/mod.rs +++ b/sdk/src/wallet/mod.rs @@ -78,6 +78,7 @@ use crate::{ block::{ output::{AccountId, AnchorId, DelegationId, FoundryId, NftId, OutputWithMetadata}, payload::signed_transaction::{SignedTransactionPayload, TransactionId}, + slot::SlotIndex, }, }, wallet::types::InclusionState, @@ -87,10 +88,10 @@ use crate::{ #[derive(Debug, Default, Clone, Serialize, Deserialize, Eq, PartialEq)] #[serde(rename_all = "camelCase")] pub struct FilterOptions { - /// Filter all outputs where the booked milestone index is below the specified timestamp - pub lower_bound_booked_timestamp: Option, - /// Filter all outputs where the booked milestone index is above the specified timestamp - pub upper_bound_booked_timestamp: Option, + /// Include all outputs where the included slot is below the specified slot. + pub included_below_slot: Option, + /// Include all outputs where the included slot is above the specified slot. + pub included_above_slot: Option, /// Filter all outputs for the provided types (Basic = 3, Account = 4, Foundry = 5, NFT = 6). pub output_types: Option>, /// Return all account outputs matching these IDs. @@ -114,12 +115,6 @@ pub(crate) fn build_transaction_from_payload_and_inputs( payload: tx_payload.clone(), block_id: inputs.first().map(|i| *i.metadata.block_id()), inclusion_state: InclusionState::Confirmed, - timestamp: 0, - // TODO use slot index since milestone_timestamp_spent is gone - // inputs - // .first() - // .and_then(|i| i.metadata.milestone_timestamp_spent.map(|t| t as u128 * 1000)) - // .unwrap_or_else(|| crate::utils::unix_timestamp_now().as_millis()), transaction_id: tx_id, network_id: tx_payload.transaction().network_id(), incoming: true, diff --git a/sdk/src/wallet/operations/syncing/options.rs b/sdk/src/wallet/operations/syncing/options.rs index f5a1d09816..f2626acf09 100644 --- a/sdk/src/wallet/operations/syncing/options.rs +++ b/sdk/src/wallet/operations/syncing/options.rs @@ -14,9 +14,9 @@ const DEFAULT_SYNC_IMPLICIT_ACCOUNTS: bool = false; #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SyncOptions { - /// Usually syncing is skipped if it's called in between 200ms, because there can only be new changes every - /// milestone and calling it twice "at the same time" will not return new data - /// When this to true, we will sync anyways, even if it's called 0ms after the las sync finished. + /// Syncing is usually skipped if it's called repeatedly in a short amount of time as there can only be new changes + /// every slot and calling it twice "at the same time" will not return new data. + /// When this to true, we sync anyways, even if it's called 0ms after the last sync finished. #[serde(default)] pub force_syncing: bool, /// Try to sync transactions from incoming outputs with their inputs. Some data may not be obtained if it has been diff --git a/sdk/src/wallet/operations/transaction/mod.rs b/sdk/src/wallet/operations/transaction/mod.rs index 063180d48d..a4e7027c83 100644 --- a/sdk/src/wallet/operations/transaction/mod.rs +++ b/sdk/src/wallet/operations/transaction/mod.rs @@ -119,7 +119,6 @@ where payload: signed_transaction_data.payload, block_id, network_id, - timestamp: crate::client::unix_timestamp_now().as_millis(), inclusion_state: InclusionState::Pending, incoming: false, note: options.and_then(|o| o.note), diff --git a/sdk/src/wallet/types/mod.rs b/sdk/src/wallet/types/mod.rs index 543b49055c..e2dac2034d 100644 --- a/sdk/src/wallet/types/mod.rs +++ b/sdk/src/wallet/types/mod.rs @@ -97,8 +97,6 @@ pub struct TransactionWithMetadata { pub payload: SignedTransactionPayload, pub block_id: Option, pub inclusion_state: InclusionState, - // Transaction creation time - pub timestamp: u128, pub transaction_id: TransactionId, // network id to ignore outputs when set_client_options is used to switch to another network pub network_id: u64, @@ -122,8 +120,6 @@ pub struct TransactionWithMetadataDto { pub block_id: Option, /// Inclusion state of the transaction pub inclusion_state: InclusionState, - /// Timestamp - pub timestamp: String, pub transaction_id: TransactionId, /// Network id to ignore outputs when set_client_options is used to switch to another network pub network_id: String, @@ -140,7 +136,6 @@ impl From<&TransactionWithMetadata> for TransactionWithMetadataDto { payload: SignedTransactionPayloadDto::from(&value.payload), block_id: value.block_id, inclusion_state: value.inclusion_state, - timestamp: value.timestamp.to_string(), transaction_id: value.transaction_id, network_id: value.network_id.to_string(), incoming: value.incoming, @@ -161,10 +156,6 @@ impl TryFromDto for TransactionWithMetadata { payload: SignedTransactionPayload::try_from_dto_with_params_inner(dto.payload, params)?, block_id: dto.block_id, inclusion_state: dto.inclusion_state, - timestamp: dto - .timestamp - .parse::() - .map_err(|e| PayloadError::Timestamp(e.to_string()))?, transaction_id: dto.transaction_id, network_id: dto .network_id From 001588e87b313ccca666764ab77e97105b9a841a Mon Sep 17 00:00:00 2001 From: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> Date: Thu, 28 Mar 2024 14:55:32 +0100 Subject: [PATCH 2/2] Handle failed transactions/blocks (#2120) * Handle BlockState::Failed * Correctly set the block id, try multiple times to post a block * Filter issuer accounts by network id, add block id debug log * Add debug logs and test * Move test * Cleanup * Bring back send_amount_from_block_issuer_account_with_generated_mana test * Remove the duplicated code --------- Co-authored-by: Thibault Martinez --- sdk/src/wallet/core/mod.rs | 5 +- sdk/src/wallet/operations/block.rs | 27 ++++-- .../wallet/operations/syncing/transactions.rs | 73 ++++++++------- sdk/src/wallet/operations/transaction/mod.rs | 12 +-- .../transaction/submit_transaction.rs | 26 ------ .../transaction_builder/account_outputs.rs | 24 +++-- .../transaction_builder/basic_outputs.rs | 90 ++++++++++++++++++- 7 files changed, 183 insertions(+), 74 deletions(-) delete mode 100644 sdk/src/wallet/operations/transaction/submit_transaction.rs diff --git a/sdk/src/wallet/core/mod.rs b/sdk/src/wallet/core/mod.rs index e8290e1718..29657d4f99 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 a4e7027c83..c4e86cc220 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(), + ); +}