Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make ISA a transaction builder #2102

Merged
merged 37 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
4b651a9
Rename input selection to transaction builder. Move transaction optio…
Feb 29, 2024
871d012
fmt
Feb 29, 2024
5a06ec3
revert flag name
Feb 29, 2024
b403c92
find/replace mistakes
Feb 29, 2024
1987e5d
typo
Feb 29, 2024
1cbff52
error swap
Feb 29, 2024
80dd6b5
review
Mar 1, 2024
eacfe08
Merge branch '2.0' into feat/transaction-builder
Mar 1, 2024
898b332
finish renaming
Mar 1, 2024
e1b396c
Fix reward context inputs and tests
Mar 1, 2024
3d5d7ca
Merge branch '2.0' into feat/transaction-builder
Mar 1, 2024
c4042f7
Merge branch '2.0' into feat/transaction-builder
Mar 1, 2024
67f6efb
remove context inputs from transaction options
Mar 1, 2024
4d4da3f
Merge branch 'feat/transaction-builder' of https://github.com/iotaled…
Mar 1, 2024
9105784
Merge branch '2.0' into feat/transaction-builder
Mar 1, 2024
200c65f
revert merge mistake
Mar 1, 2024
bec9ba2
remove comments
Mar 1, 2024
999a984
usused import
Mar 1, 2024
bad67a0
Remove unused ContextInput
thibault-martinez Mar 4, 2024
88c4b36
review
Mar 4, 2024
d948e8e
more ts instances
Mar 4, 2024
c591188
review
Mar 4, 2024
c09919d
Merge branch '2.0' into feat/transaction-builder
Mar 4, 2024
074bc0f
double copyright
Mar 4, 2024
8d444bc
Merge branch '2.0' into feat/transaction-builder
Mar 4, 2024
2683468
review
Mar 4, 2024
80c1897
fix mana allotment not recalculating after changes are made
Mar 4, 2024
fd12b2b
Merge branch 'feat/transaction-builder' of https://github.com/iotaled…
Mar 4, 2024
2efa235
remove error variant
Mar 4, 2024
985d4cf
Merge branch '2.0' into feat/transaction-builder
Mar 4, 2024
09ceee6
Merge branch '2.0' into feat/transaction-builder
thibault-martinez Mar 5, 2024
9b6efa5
Merge branch '2.0' into feat/transaction-builder
Mar 5, 2024
ada1dbb
Merge branch '2.0' into feat/transaction-builder
Mar 5, 2024
61e94f2
allow providing chains
Mar 5, 2024
70520ec
Merge branch '2.0' into feat/transaction-builder
Mar 5, 2024
d862881
DRY
Mar 5, 2024
2e098c0
Merge branch 'feat/transaction-builder' of https://github.com/iotaled…
Mar 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions bindings/core/src/method/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ use std::path::PathBuf;

use crypto::keys::bip44::Bip44;
use derivative::Derivative;
use iota_sdk::utils::serde::string;
#[cfg(feature = "events")]
use iota_sdk::wallet::events::types::{WalletEvent, WalletEventType};
use iota_sdk::{client::api::options::TransactionOptions, utils::serde::string};
// #[cfg(feature = "participation")]
// use iota_sdk::{
// client::node_manager::node::Node,
Expand All @@ -17,7 +17,7 @@ use iota_sdk::wallet::events::types::{WalletEvent, WalletEventType};
// };
use iota_sdk::{
client::{
api::{input_selection::Burn, PreparedTransactionDataDto, SignedTransactionDataDto},
api::{transaction_builder::Burn, PreparedTransactionDataDto, SignedTransactionDataDto},
node_manager::node::NodeAuth,
secret::GenerateAddressOptions,
},
Expand All @@ -29,7 +29,7 @@ use iota_sdk::{
wallet::{
BeginStakingParams, ClientOptions, ConsolidationParams, CreateAccountParams, CreateDelegationParams,
CreateNativeTokenParams, FilterOptions, MintNftParams, OutputParams, OutputsToClaim, SendManaParams,
SendNativeTokenParams, SendNftParams, SendParams, SyncOptions, TransactionOptions,
SendNativeTokenParams, SendNftParams, SendParams, SyncOptions,
},
U256,
};
Expand Down
4 changes: 2 additions & 2 deletions cli/src/wallet_cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::str::FromStr;
use clap::{CommandFactory, Parser, Subcommand};
use colored::Colorize;
use iota_sdk::{
client::{request_funds_from_faucet, secret::SecretManager},
client::{api::options::TransactionOptions, request_funds_from_faucet, secret::SecretManager},
types::block::{
address::{AccountAddress, Bech32Address, ToBech32Ext},
mana::ManaAllotment,
Expand All @@ -25,7 +25,7 @@ use iota_sdk::{
wallet::{
types::OutputData, BeginStakingParams, ConsolidationParams, CreateDelegationParams, CreateNativeTokenParams,
Error as WalletError, MintNftParams, OutputsToClaim, ReturnStrategy, SendManaParams, SendNativeTokenParams,
SendNftParams, SendParams, SyncOptions, TransactionOptions, Wallet,
SendNftParams, SendParams, SyncOptions, Wallet,
},
U256,
};
Expand Down
4 changes: 2 additions & 2 deletions sdk/examples/how_tos/account_output/send_amount.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
//! `cargo run --release --all-features --example account_output_send_amount`

use iota_sdk::{
client::node_api::indexer::query_parameters::BasicOutputQueryParameters,
client::{api::options::TransactionOptions, node_api::indexer::query_parameters::BasicOutputQueryParameters},
types::block::address::{AccountAddress, ToBech32Ext},
wallet::{AccountSyncOptions, Result, SyncOptions, TransactionOptions},
wallet::{AccountSyncOptions, Result, SyncOptions},
Wallet,
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@
//! cargo run --release --all-features --example send_micro_transaction
//! ```

use iota_sdk::{
wallet::{Result, TransactionOptions},
Wallet,
};
use iota_sdk::{client::api::options::TransactionOptions, wallet::Result, Wallet};

// The base coin micro amount to send
const SEND_MICRO_AMOUNT: u64 = 1;
Expand Down
3 changes: 2 additions & 1 deletion sdk/src/client/api/block_builder/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// Copyright 2021 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

pub mod input_selection;
pub mod options;
pub mod transaction;
pub mod transaction_builder;

pub use self::transaction::verify_semantic;
use crate::{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use alloc::collections::{BTreeMap, BTreeSet};
use serde::{Deserialize, Serialize};

use crate::{
client::api::input_selection::Burn,
client::api::transaction_builder::Burn,
types::block::{
address::Address,
context_input::ContextInput,
Expand All @@ -28,7 +28,7 @@ pub struct TransactionOptions {
pub context_inputs: Vec<ContextInput>,
/// Inputs that must be used for the transaction.
pub required_inputs: BTreeSet<OutputId>,
/// Specifies what needs to be burned during input selection.
/// Specifies what needs to be burned in the transaction.
thibault-martinez marked this conversation as resolved.
Show resolved Hide resolved
pub burn: Option<Burn>,
/// A string attached to the transaction.
pub note: Option<String>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use serde::{Deserialize, Serialize};

use crate::types::block::output::{AccountId, DelegationId, FoundryId, NativeToken, NftId, TokenId};

/// A type to specify what needs to be burned during input selection.
/// A type to specify what needs to be burned in a transaction.
/// Nothing will be burned that has not been explicitly set with this struct.
#[derive(Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

//! Error handling for input selection.
//! Error handling for transaction builder.

use std::fmt::Debug;

Expand All @@ -10,7 +10,7 @@ use primitive_types::U256;
use super::Requirement;
use crate::types::block::output::{ChainId, OutputId, TokenId};

/// Errors related to input selection.
/// Errors related to transaction builder.
#[derive(Debug, Eq, PartialEq, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
Expand Down Expand Up @@ -59,7 +59,7 @@ pub enum Error {
/// No input with matching ed25519 address provided.
#[error("no input with matching ed25519 address provided")]
MissingInputWithEd25519Address,
/// No available inputs were provided to input selection.
/// No available inputs were provided to transaction builder.
#[error("no available inputs provided")]
NoAvailableInputsProvided,
/// Required input is forbidden.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2024 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

//! Input selection for transactions
//! Builder for transactions

pub(crate) mod burn;
pub(crate) mod error;
Expand All @@ -19,11 +19,17 @@ use self::requirement::account::is_account_with_id;
pub use self::{burn::Burn, error::Error, requirement::Requirement};
use crate::{
client::{
api::{PreparedTransactionData, RemainderData},
api::{
options::{RemainderValueStrategy, TransactionOptions},
transaction::validate_transaction_length,
PreparedTransactionData, RemainderData,
},
node_api::indexer::query_parameters::OutputQueryParameters,
secret::types::InputSigningData,
Client,
},
types::block::{
address::{AccountAddress, Address, NftAddress},
address::{AccountAddress, Address, NftAddress, ToBech32Ext},
context_input::ContextInput,
input::{Input, UtxoInput, INPUT_COUNT_RANGE},
mana::ManaAllotment,
Expand All @@ -40,12 +46,133 @@ use crate::{
},
};

/// Working state for the input selection algorithm.
impl Client {
/// Builds a transaction using the given inputs, outputs, addresses, and options.
pub async fn build_transaction(
Thoralf-M marked this conversation as resolved.
Show resolved Hide resolved
&self,
addresses: impl IntoIterator<Item = Address>,
outputs: impl IntoIterator<Item = Output>,
options: TransactionOptions,
) -> crate::client::Result<PreparedTransactionData> {
let outputs = outputs.into_iter().collect::<Vec<_>>();
let addresses = addresses.into_iter().collect::<Vec<_>>();
// Voting output needs to be requested before to prevent a deadlock
Thoralf-M marked this conversation as resolved.
Show resolved Hide resolved
let protocol_parameters = self.get_protocol_parameters().await?;
let creation_slot = self.get_slot_index().await?;
let slot_commitment_id = self.get_issuance().await?.latest_commitment.id();
let reference_mana_cost = if let Some(issuer_id) = options.issuer_id {
Some(self.get_account_congestion(&issuer_id, None).await?.reference_mana_cost)
} else {
None
};
let remainder_address = match options.remainder_value_strategy {
RemainderValueStrategy::ReuseAddress => None,
RemainderValueStrategy::CustomAddress(address) => Some(address),
};

let hrp = self.get_bech32_hrp().await?;
Thoralf-M marked this conversation as resolved.
Show resolved Hide resolved

let mut available_input_ids = HashSet::new();
for address in &addresses {
let mut cursor = None;
loop {
Thoralf-M marked this conversation as resolved.
Show resolved Hide resolved
let mut query = OutputQueryParameters::new().unlockable_by_address(address.clone().to_bech32(hrp));
if let Some(cursor) = cursor {
query = query.cursor(cursor);
}
let res = self.output_ids(query).await?;
cursor = res.cursor;
available_input_ids.extend(res.items);
if cursor.is_none() {
break;
}
}
}
let available_inputs = self
.get_outputs_with_metadata(&available_input_ids.into_iter().collect::<Vec<_>>())
.await?
.into_iter()
.map(|res| InputSigningData {
output: res.output,
output_metadata: res.metadata,
chain: None,
})
.collect::<Vec<_>>();

let mut mana_rewards = HashMap::new();

if let Some(burn) = &options.burn {
for delegation_id in burn.delegations() {
let output_id = self.delegation_output_id(*delegation_id).await?;
mana_rewards.insert(
output_id,
self.get_output_mana_rewards(&output_id, slot_commitment_id.slot_index())
.await?
.rewards,
);
}
}

// Check that no input got already locked
for output_id in &options.required_inputs {
let input = self.get_output(output_id).await?;
if input.output.can_claim_rewards(outputs.iter().find(|o| {
input
.output
.chain_id()
.map(|chain_id| chain_id.or_from_output_id(output_id))
== o.chain_id()
})) {
mana_rewards.insert(
*output_id,
self.get_output_mana_rewards(output_id, slot_commitment_id.slot_index())
.await?
.rewards,
);
}
}

let mut transaction_builder = TransactionBuilder::new(
available_inputs,
outputs,
addresses,
creation_slot,
slot_commitment_id,
protocol_parameters.clone(),
)
.with_required_inputs(options.required_inputs)
.with_context_inputs(options.context_inputs)
.with_mana_rewards(mana_rewards)
.with_payload(options.tagged_data_payload)
.with_mana_allotments(options.mana_allotments)
.with_remainder_address(remainder_address)
.with_burn(options.burn);

if let (Some(account_id), Some(reference_mana_cost)) = (options.issuer_id, reference_mana_cost) {
transaction_builder = transaction_builder.with_min_mana_allotment(account_id, reference_mana_cost);
}

if !options.allow_additional_input_selection {
transaction_builder = transaction_builder.disable_additional_transaction_builder();
}

if let Some(capabilities) = options.capabilities {
transaction_builder = transaction_builder.with_transaction_capabilities(capabilities)
}

let prepared_transaction_data = transaction_builder.select()?;
thibault-martinez marked this conversation as resolved.
Show resolved Hide resolved

validate_transaction_length(&prepared_transaction_data.transaction)?;

Ok(prepared_transaction_data)
}
}

/// Working state for the transaction builder algorithm.
#[derive(Debug)]
pub struct InputSelection {
pub struct TransactionBuilder {
available_inputs: Vec<InputSigningData>,
required_inputs: HashSet<OutputId>,
forbidden_inputs: HashSet<OutputId>,
selected_inputs: Vec<InputSigningData>,
context_inputs: HashSet<ContextInput>,
provided_outputs: Vec<Output>,
Expand Down Expand Up @@ -81,8 +208,8 @@ pub(crate) struct Remainders {
added_mana: u64,
}

impl InputSelection {
/// Creates a new [`InputSelection`].
impl TransactionBuilder {
/// Creates a new [`TransactionBuilder`].
pub fn new(
available_inputs: impl IntoIterator<Item = InputSigningData>,
outputs: impl IntoIterator<Item = Output>,
Expand Down Expand Up @@ -116,7 +243,6 @@ impl InputSelection {
Self {
available_inputs,
required_inputs: HashSet::new(),
forbidden_inputs: HashSet::new(),
selected_inputs: Vec::new(),
context_inputs: HashSet::new(),
provided_outputs: outputs.into_iter().collect(),
Expand Down Expand Up @@ -156,19 +282,10 @@ impl InputSelection {
Requirement::NativeTokens,
]);

// Removes forbidden inputs from available inputs.
self.available_inputs
.retain(|input| !self.forbidden_inputs.contains(input.output_id()));

// This is to avoid a borrow of self since there is a mutable borrow in the loop already.
let required_inputs = std::mem::take(&mut self.required_inputs);

for required_input in required_inputs {
// Checks that required input is not forbidden.
if self.forbidden_inputs.contains(&required_input) {
return Err(Error::RequiredInputIsForbidden(required_input));
thibault-martinez marked this conversation as resolved.
Show resolved Hide resolved
}

// Checks that required input is available.
match self
.available_inputs
Expand Down Expand Up @@ -362,37 +479,31 @@ impl InputSelection {
Ok(added_output)
}

/// Sets the required inputs of an [`InputSelection`].
/// Sets the required inputs of an [`TransactionBuilder`].
pub fn with_required_inputs(mut self, inputs: impl IntoIterator<Item = OutputId>) -> Self {
self.required_inputs = inputs.into_iter().collect();
self
}

/// Sets the forbidden inputs of an [`InputSelection`].
pub fn with_forbidden_inputs(mut self, inputs: impl IntoIterator<Item = OutputId>) -> Self {
self.forbidden_inputs = inputs.into_iter().collect();
self
}

/// Sets the context inputs of an [`InputSelection`].
/// Sets the context inputs of an [`TransactionBuilder`].
pub fn with_context_inputs(mut self, context_inputs: impl IntoIterator<Item = ContextInput>) -> Self {
self.context_inputs = context_inputs.into_iter().collect();
self
}

/// Sets the burn of an [`InputSelection`].
/// Sets the burn of an [`TransactionBuilder`].
pub fn with_burn(mut self, burn: impl Into<Option<Burn>>) -> Self {
self.burn = burn.into();
self
}

/// Sets the remainder address of an [`InputSelection`].
/// Sets the remainder address of an [`TransactionBuilder`].
pub fn with_remainder_address(mut self, address: impl Into<Option<Address>>) -> Self {
self.remainders.address = address.into();
self
}

/// Sets the mana allotments of an [`InputSelection`].
/// Sets the mana allotments of an [`TransactionBuilder`].
pub fn with_mana_allotments(mut self, mana_allotments: impl IntoIterator<Item = (AccountId, u64)>) -> Self {
self.mana_allotments = mana_allotments.into_iter().collect();
self
Expand Down Expand Up @@ -427,7 +538,7 @@ impl InputSelection {
}

/// Disables selecting additional inputs.
pub fn disable_additional_input_selection(mut self) -> Self {
pub fn disable_additional_transaction_builder(mut self) -> Self {
thibault-martinez marked this conversation as resolved.
Show resolved Hide resolved
self.allow_additional_input_selection = false;
self
}
Expand Down
Loading
Loading