Skip to content

Commit

Permalink
Merge branch '2.0' into 2.0.0-alpha.2
Browse files Browse the repository at this point in the history
  • Loading branch information
thibault-martinez authored Mar 28, 2024
2 parents fbdec45 + 001588e commit 9b85ae1
Show file tree
Hide file tree
Showing 19 changed files with 261 additions and 157 deletions.
2 changes: 0 additions & 2 deletions bindings/nodejs/lib/types/wallet/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
16 changes: 8 additions & 8 deletions bindings/nodejs/lib/types/wallet/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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. */
Expand Down
6 changes: 3 additions & 3 deletions bindings/python/iota_sdk/types/filter_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 0 additions & 2 deletions bindings/python/iota_sdk/types/transaction_with_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -49,7 +48,6 @@ class TransactionWithMetadata:
"""
payload: SignedTransactionPayload
inclusion_state: InclusionState
timestamp: int
transaction_id: TransactionId
network_id: int
incoming: bool
Expand Down
7 changes: 3 additions & 4 deletions bindings/python/iota_sdk/wallet/sync_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
28 changes: 22 additions & 6 deletions cli/src/wallet_cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<Vec<_>>();
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)
}
};
Expand All @@ -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::<Vec<_>>();
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");
Expand All @@ -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);
}
}
}
Expand Down
40 changes: 20 additions & 20 deletions sdk/examples/client/block/01_block_confirmation_time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
//! cargo run --release --example block_confirmation_time
//! ```

use std::time::Instant;

use crypto::keys::bip44::Bip44;
use iota_sdk::{
client::{
Expand Down Expand Up @@ -45,34 +47,32 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {

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(())
}
2 changes: 1 addition & 1 deletion sdk/src/client/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down
4 changes: 2 additions & 2 deletions sdk/src/types/block/output/unlock_condition/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ pub enum UnlockConditionError {
Kind(u8),
#[display(fmt = "invalid unlock condition count: {_0}")]
Count(<UnlockConditionCount as TryFrom<usize>>::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,
Expand Down
28 changes: 15 additions & 13 deletions sdk/src/wallet/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down Expand Up @@ -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<AccountId> {
pub fn first_block_issuer_account_id(&self, current_slot: SlotIndex, network_id: u64) -> Option<AccountId> {
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 {
Expand Down Expand Up @@ -655,7 +658,6 @@ mod test {
payload: tx_payload,
block_id: None,
network_id: 0,
timestamp: 0,
inclusion_state: InclusionState::Pending,
incoming: false,
note: None,
Expand Down
15 changes: 5 additions & 10 deletions sdk/src/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ use crate::{
block::{
output::{AccountId, AnchorId, DelegationId, FoundryId, NftId, OutputWithMetadata},
payload::signed_transaction::{SignedTransactionPayload, TransactionId},
slot::SlotIndex,
},
},
wallet::types::InclusionState,
Expand All @@ -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<u32>,
/// Filter all outputs where the booked milestone index is above the specified timestamp
pub upper_bound_booked_timestamp: Option<u32>,
/// Include all outputs where the included slot is below the specified slot.
pub included_below_slot: Option<SlotIndex>,
/// Include all outputs where the included slot is above the specified slot.
pub included_above_slot: Option<SlotIndex>,
/// Filter all outputs for the provided types (Basic = 3, Account = 4, Foundry = 5, NFT = 6).
pub output_types: Option<Vec<u8>>,
/// Return all account outputs matching these IDs.
Expand All @@ -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,
Expand Down
27 changes: 21 additions & 6 deletions sdk/src/wallet/operations/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ use crate::{
wallet::{Wallet, WalletError},
};

const MAX_POST_BLOCK_ATTEMPTS: u64 = 3;

impl<S: 'static + SecretManage> Wallet<S>
where
WalletError: From<S::Error>,
Expand All @@ -24,22 +26,24 @@ where
allow_negative_bic: bool,
) -> Result<BlockId, WalletError> {
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)?
}
};

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 {
Expand Down Expand Up @@ -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;
}
}
}
6 changes: 3 additions & 3 deletions sdk/src/wallet/operations/syncing/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 9b85ae1

Please sign in to comment.