Skip to content
This repository was archived by the owner on May 22, 2025. It is now read-only.

Commit 0472c8f

Browse files
author
Alex Coats
committed
Use a custom collection type for order
1 parent 8f48a49 commit 0472c8f

File tree

2 files changed

+121
-95
lines changed

2 files changed

+121
-95
lines changed

sdk/src/client/api/block_builder/transaction_builder/mod.rs

Lines changed: 120 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ pub(crate) mod remainder;
99
pub(crate) mod requirement;
1010
pub(crate) mod transition;
1111

12-
use alloc::collections::BTreeMap;
12+
use alloc::collections::{BTreeMap, VecDeque};
1313
use std::collections::{HashMap, HashSet};
1414

1515
use crypto::keys::bip44::Bip44;
@@ -180,7 +180,7 @@ impl Client {
180180
pub struct TransactionBuilder {
181181
available_inputs: Vec<InputSigningData>,
182182
required_inputs: HashSet<OutputId>,
183-
selected_inputs: Vec<InputSigningData>,
183+
selected_inputs: OrderedInputs,
184184
bic_context_inputs: HashSet<BlockIssuanceCreditContextInput>,
185185
commitment_context_input: Option<CommitmentContextInput>,
186186
reward_context_inputs: HashSet<OutputId>,
@@ -252,7 +252,7 @@ impl TransactionBuilder {
252252
Self {
253253
available_inputs,
254254
required_inputs: HashSet::new(),
255-
selected_inputs: Vec::new(),
255+
selected_inputs: Default::default(),
256256
bic_context_inputs: HashSet::new(),
257257
commitment_context_input: None,
258258
reward_context_inputs: HashSet::new(),
@@ -456,7 +456,7 @@ impl TransactionBuilder {
456456

457457
let data = PreparedTransactionData {
458458
transaction,
459-
inputs_data: self.selected_inputs,
459+
inputs_data: self.selected_inputs.into_iter().collect(),
460460
remainders: self.remainders.data,
461461
mana_rewards: self.mana_rewards.into_iter().collect(),
462462
};
@@ -485,7 +485,16 @@ impl TransactionBuilder {
485485
self.requirements.push(requirement);
486486
}
487487

488-
self.insert_input_sorted(input);
488+
let required_address = input
489+
.output
490+
.required_address(
491+
self.latest_slot_commitment_id.slot_index(),
492+
self.protocol_parameters.committable_age_range(),
493+
)
494+
// PANIC: safe to unwrap as non basic/account/foundry/nft/delegation outputs are already filtered out.
495+
.unwrap()
496+
.expect("expiration unlockable outputs already filtered out");
497+
self.selected_inputs.insert(input, required_address);
489498

490499
// New inputs/outputs may need context inputs
491500
if !self.requirements.contains(&Requirement::ContextInputs) {
@@ -671,102 +680,119 @@ impl TransactionBuilder {
671680
}
672681
})
673682
}
683+
}
674684

675-
fn insert_input_sorted(&mut self, input: InputSigningData) {
676-
let input_required_address = input
677-
.output
678-
.required_address(
679-
self.latest_slot_commitment_id.slot_index(),
680-
self.protocol_parameters.committable_age_range(),
681-
)
682-
// PANIC: safe to unwrap as non basic/account/foundry/nft/delegation outputs are already filtered out.
683-
.unwrap()
684-
.expect("expiration unlockable outputs already filtered out");
685-
if input_required_address.is_ed25519() {
686-
if let Some(position) = self
687-
.selected_inputs
688-
.iter()
689-
.position(|i| i.output_id() > input.output_id())
690-
{
691-
self.selected_inputs.insert(position, input);
692-
} else {
693-
self.selected_inputs.push(input);
694-
}
685+
#[derive(Clone, Debug, Default)]
686+
pub(crate) struct OrderedInputs {
687+
ed25519: VecDeque<InputSigningData>,
688+
other: BTreeMap<Address, Vec<InputSigningData>>,
689+
len: usize,
690+
}
691+
692+
impl OrderedInputs {
693+
pub(crate) fn iter(&self) -> <&Self as IntoIterator>::IntoIter {
694+
self.into_iter()
695+
}
696+
697+
pub(crate) fn insert(&mut self, input: InputSigningData, required_address: Address) {
698+
if required_address.is_ed25519() {
699+
self.ed25519.push_back(input);
695700
} else {
696-
match self.selected_inputs.iter().position(|input_signing_data| {
697-
let required_address = input_signing_data
698-
.output
699-
.required_address(
700-
self.latest_slot_commitment_id.slot_index(),
701-
self.protocol_parameters.committable_age_range(),
702-
)
703-
// PANIC: safe to unwrap as non basic/account/foundry/nft/delegation outputs are already filtered
704-
// out.
705-
.unwrap()
706-
.expect("expiration unlockable outputs already filtered out");
707-
match required_address {
708-
Address::Account(unlock_address) => {
709-
if let Output::Account(account_output) = &input_signing_data.output {
710-
*unlock_address.account_id()
711-
== account_output.account_id_non_null(input_signing_data.output_id())
712-
} else {
713-
false
714-
}
715-
}
716-
Address::Nft(unlock_address) => {
717-
if let Output::Nft(nft_output) = &input_signing_data.output {
718-
*unlock_address.nft_id() == nft_output.nft_id_non_null(input_signing_data.output_id())
719-
} else {
720-
false
721-
}
722-
}
723-
_ => false,
701+
self.other.entry(required_address).or_default().push(input);
702+
}
703+
self.len += 1;
704+
}
705+
706+
pub(crate) fn len(&self) -> usize {
707+
self.len
708+
}
709+
710+
pub(crate) fn is_empty(&self) -> bool {
711+
self.len() == 0
712+
}
713+
}
714+
715+
impl<'a> IntoIterator for &'a OrderedInputs {
716+
type Item = &'a InputSigningData;
717+
type IntoIter = alloc::vec::IntoIter<Self::Item>;
718+
719+
fn into_iter(self) -> Self::IntoIter {
720+
let mut other = self
721+
.other
722+
.iter()
723+
.map(|(k, v)| (k, v.iter().collect::<VecDeque<_>>()))
724+
.collect::<BTreeMap<_, _>>();
725+
let mut inputs = Vec::new();
726+
let mut queue = self.ed25519.iter().collect::<VecDeque<_>>();
727+
while let Some(input) = queue.pop_front() {
728+
// Add associated inputs to the front of the queue
729+
match &input.output {
730+
Output::Account(account_output) => {
731+
queue = other
732+
.remove(&Address::Account(AccountAddress::new(
733+
account_output.account_id_non_null(input.output_id()),
734+
)))
735+
.into_iter()
736+
.flatten()
737+
.chain(queue)
738+
.collect()
724739
}
725-
}) {
726-
Some(position) => {
727-
// Insert after the output we need
728-
self.selected_inputs.insert(position + 1, input);
740+
Output::Nft(nft_output) => {
741+
queue = other
742+
.remove(&Address::Nft(NftAddress::new(
743+
nft_output.nft_id_non_null(input.output_id()),
744+
)))
745+
.into_iter()
746+
.flatten()
747+
.chain(queue)
748+
.collect()
729749
}
730-
None => {
731-
// insert before address
732-
let account_or_nft_address = match &input.output {
733-
Output::Account(account_output) => Some(Address::Account(AccountAddress::new(
750+
_ => (),
751+
};
752+
inputs.push(input);
753+
}
754+
inputs.extend(other.into_values().flatten());
755+
inputs.into_iter()
756+
}
757+
}
758+
759+
impl IntoIterator for OrderedInputs {
760+
type Item = InputSigningData;
761+
type IntoIter = alloc::vec::IntoIter<Self::Item>;
762+
763+
fn into_iter(mut self) -> Self::IntoIter {
764+
let mut inputs = Vec::new();
765+
let mut queue = self.ed25519;
766+
while let Some(input) = queue.pop_front() {
767+
// Add associated inputs to the front of the queue
768+
match &input.output {
769+
Output::Account(account_output) => {
770+
queue = self
771+
.other
772+
.remove(&Address::Account(AccountAddress::new(
734773
account_output.account_id_non_null(input.output_id()),
735-
))),
736-
Output::Nft(nft_output) => Some(Address::Nft(NftAddress::new(
774+
)))
775+
.into_iter()
776+
.flatten()
777+
.chain(queue)
778+
.collect()
779+
}
780+
Output::Nft(nft_output) => {
781+
queue = self
782+
.other
783+
.remove(&Address::Nft(NftAddress::new(
737784
nft_output.nft_id_non_null(input.output_id()),
738-
))),
739-
_ => None,
740-
};
741-
742-
if let Some(account_or_nft_address) = account_or_nft_address {
743-
// Check for existing outputs for this address, and insert before
744-
match self.selected_inputs.iter().position(|input_signing_data| {
745-
let required_address = input_signing_data
746-
.output
747-
.required_address(
748-
self.latest_slot_commitment_id.slot_index(),
749-
self.protocol_parameters.committable_age_range(),
750-
)
751-
// PANIC: safe to unwrap as non basic/account/foundry/nft/delegation outputs are already
752-
// filtered out.
753-
.unwrap()
754-
.expect("expiration unlockable outputs already filtered out");
755-
required_address == account_or_nft_address
756-
}) {
757-
Some(position) => {
758-
// Insert before the output with this address required for unlocking
759-
self.selected_inputs.insert(position, input);
760-
}
761-
// just push output
762-
None => self.selected_inputs.push(input),
763-
}
764-
} else {
765-
// just push basic or foundry output
766-
self.selected_inputs.push(input);
767-
}
785+
)))
786+
.into_iter()
787+
.flatten()
788+
.chain(queue)
789+
.collect()
768790
}
769-
}
791+
_ => (),
792+
};
793+
inputs.push(input);
770794
}
795+
inputs.extend(self.other.into_values().flatten());
796+
inputs.into_iter()
771797
}
772798
}

sdk/src/client/api/block_builder/transaction_builder/remainder.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ impl TransactionBuilder {
3030
Ok(())
3131
}
3232

33-
/// Gets the remainder address from configuration of finds one from the inputs.
33+
/// Gets the remainder address from configuration or finds one from the inputs.
3434
pub(crate) fn get_remainder_address(&self) -> Result<Option<(Address, Option<Bip44>)>, TransactionBuilderError> {
3535
if let Some(remainder_address) = &self.remainders.address {
3636
// Search in inputs for the Bip44 chain for the remainder address, so the ledger can regenerate it

0 commit comments

Comments
 (0)