iota_client/api/
message_builder.rs

1// Copyright 2021 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::{api::address::search_address, Client, ClientMiner, Error, Result};
5
6use bee_common::packable::Packable;
7use bee_message::{constants::INPUT_OUTPUT_COUNT_MAX, prelude::*};
8#[cfg(not(feature = "wasm"))]
9use bee_pow::providers::{miner::MinerCancel, NonceProviderBuilder};
10use bee_rest_api::types::{
11    dtos::{AddressDto, OutputDto},
12    responses::OutputResponse,
13};
14use crypto::keys::slip10::{Chain, Curve, Seed};
15#[cfg(feature = "wasm")]
16use gloo_timers::future::TimeoutFuture;
17#[cfg(not(feature = "wasm"))]
18use tokio::time::sleep;
19
20#[cfg(not(feature = "wasm"))]
21use std::time::Duration;
22use std::{
23    collections::{HashMap, HashSet},
24    ops::Range,
25    str::FromStr,
26};
27
28// https://github.com/GalRogozinski/protocol-rfcs/blob/dust/text/0032-dust-protection/0032-dust-protection.md
29const MAX_ALLOWED_DUST_OUTPUTS: i64 = 100;
30const DUST_DIVISOR: i64 = 100_000;
31const DUST_THRESHOLD: u64 = 1_000_000;
32
33/// Helper struct for offline signing
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct PreparedTransactionData {
36    /// Transaction essence
37    pub essence: Essence,
38    /// Required address information for signing
39    pub address_index_recorders: Vec<AddressIndexRecorder>,
40}
41
42/// Structure for sorting of UnlockBlocks
43#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct AddressIndexRecorder {
45    /// Index of the account
46    pub account_index: usize,
47    /// The input used
48    pub input: Input,
49    /// The output information
50    pub output: OutputResponse,
51    /// index of this address on the seed
52    pub address_index: usize,
53    /// The chain derived from seed
54    pub chain: Chain,
55    /// Whether this is an internal address
56    pub internal: bool,
57    /// The address
58    pub bech32_address: String,
59}
60
61#[derive(Debug, Clone)]
62struct OutputWrapper {
63    output: OutputResponse,
64    address_index: usize,
65    internal: bool,
66    amount: u64,
67    address: String,
68}
69
70/// Builder of the message API
71pub struct ClientMessageBuilder<'a> {
72    client: &'a Client,
73    seed: Option<&'a Seed>,
74    account_index: Option<usize>,
75    initial_address_index: Option<usize>,
76    inputs: Option<Vec<UtxoInput>>,
77    input_range: Range<usize>,
78    outputs: Vec<Output>,
79    index: Option<Box<[u8]>>,
80    data: Option<Vec<u8>>,
81    parents: Option<Vec<MessageId>>,
82}
83
84impl<'a> ClientMessageBuilder<'a> {
85    /// Create message builder
86    pub fn new(client: &'a Client) -> Self {
87        Self {
88            client,
89            seed: None,
90            account_index: None,
91            initial_address_index: None,
92            inputs: None,
93            input_range: 0..100,
94            outputs: Vec::new(),
95            index: None,
96            data: None,
97            parents: None,
98        }
99    }
100
101    /// Sets the seed.
102    pub fn with_seed(mut self, seed: &'a Seed) -> Self {
103        self.seed.replace(seed);
104        self
105    }
106
107    /// Sets the account index.
108    pub fn with_account_index(mut self, account_index: usize) -> Self {
109        self.account_index.replace(account_index);
110        self
111    }
112
113    /// Sets the index of the address to start looking for balance.
114    pub fn with_initial_address_index(mut self, initial_address_index: usize) -> Self {
115        self.initial_address_index.replace(initial_address_index);
116        self
117    }
118
119    /// Set a custom input(transaction output)
120    pub fn with_input(mut self, input: UtxoInput) -> Self {
121        self.inputs = match self.inputs {
122            Some(mut inputs) => {
123                inputs.push(input);
124                Some(inputs)
125            }
126            None => Some(vec![input]),
127        };
128        self
129    }
130
131    /// Set a custom range in which to search for addresses for custom provided inputs. Default: 0..100
132    pub fn with_input_range(mut self, range: Range<usize>) -> Self {
133        self.input_range = range;
134        self
135    }
136
137    /// Set a transfer to the builder
138    pub fn with_output(mut self, address: &str, amount: u64) -> Result<Self> {
139        let output = SignatureLockedSingleOutput::new(Address::from_str(address)?, amount)?.into();
140        self.outputs.push(output);
141        Ok(self)
142    }
143
144    /// Set a dust allowance transfer to the builder, address needs to be Bech32 encoded
145    pub fn with_dust_allowance_output(mut self, address: &str, amount: u64) -> Result<Self> {
146        if amount < DUST_THRESHOLD {
147            return Err(Error::DustError(
148                "Amount for SignatureLockedDustAllowanceOutput needs to be >= 1_000_000".into(),
149            ));
150        }
151        let output = SignatureLockedDustAllowanceOutput::new(Address::from_str(address)?, amount)?.into();
152        self.outputs.push(output);
153        Ok(self)
154    }
155
156    /// Set a transfer to the builder, address needs to be hex encoded
157    pub fn with_output_hex(mut self, address: &str, amount: u64) -> Result<Self> {
158        let output = SignatureLockedSingleOutput::new(address.parse::<Ed25519Address>()?.into(), amount)?.into();
159        self.outputs.push(output);
160        Ok(self)
161    }
162
163    /// Set indexation to the builder
164    pub fn with_index<I: AsRef<[u8]>>(mut self, index: I) -> Self {
165        self.index.replace(index.as_ref().into());
166        self
167    }
168
169    /// Set data to the builder
170    pub fn with_data(mut self, data: Vec<u8>) -> Self {
171        self.data.replace(data);
172        self
173    }
174
175    /// Set 1-8 custom parent message ids
176    pub fn with_parents(mut self, parent_ids: Vec<MessageId>) -> Result<Self> {
177        if !(1..=8).contains(&parent_ids.len()) {
178            return Err(Error::InvalidParentsAmount(parent_ids.len()));
179        }
180        self.parents.replace(parent_ids);
181        Ok(self)
182    }
183
184    /// Consume the builder and get the API result
185    pub async fn finish(self) -> Result<Message> {
186        // Indexation payload requires an indexation tag
187        if self.data.is_some() && self.index.is_none() {
188            return Err(Error::MissingParameter("index"));
189        }
190        if self.inputs.is_some() && self.outputs.is_empty() {
191            return Err(Error::MissingParameter("output"));
192        }
193        if !self.outputs.is_empty() {
194            if self.seed.is_none() && self.inputs.is_none() {
195                return Err(Error::MissingParameter("Seed"));
196            }
197            // Send message with transaction
198            let prepared_transaction_data = self.prepare_transaction().await?;
199            let tx_payload = self.sign_transaction(prepared_transaction_data, None, None).await?;
200            self.finish_message(Some(tx_payload)).await
201        } else if self.index.is_some() {
202            // Send message with indexation payload
203            self.finish_indexation().await
204        } else {
205            // Send message without payload
206            self.finish_message(None).await
207        }
208    }
209
210    // Used to store the address data for an input so we can later sign it
211    fn create_address_index_recorder(
212        account_index: usize,
213        address_index: usize,
214        internal: bool,
215        output: &OutputResponse,
216        bech32_address: String,
217    ) -> Result<AddressIndexRecorder> {
218        // Note that we need to sign the original address, i.e., `path/index`,
219        // instead of `path/index/_offset` or `path/_offset`.
220
221        // 44 is for BIP 44 (HD wallets) and 4218 is the registered index for IOTA https://github.com/satoshilabs/slips/blob/master/slip-0044.md
222        let chain = Chain::from_u32_hardened(vec![
223            44,
224            4218,
225            account_index as u32,
226            internal as u32,
227            address_index as u32,
228        ]);
229        let input = Input::Utxo(
230            UtxoInput::new(TransactionId::from_str(&output.transaction_id)?, output.output_index)
231                .map_err(|_| Error::TransactionError)?,
232        );
233
234        Ok(AddressIndexRecorder {
235            account_index,
236            input,
237            output: output.clone(),
238            address_index,
239            chain,
240            internal,
241            bech32_address,
242        })
243    }
244
245    /// Get output amount and address from an OutputDto (bool true == SignatureLockedSingle, false ==
246    /// SignatureLockedDustAllowance)
247    pub fn get_output_amount_and_address(output: &OutputDto) -> Result<(u64, Address, bool)> {
248        match output {
249            OutputDto::Treasury(_) => Err(Error::OutputError("Treasury output is no supported")),
250            OutputDto::SignatureLockedSingle(ref r) => match &r.address {
251                AddressDto::Ed25519(addr) => {
252                    let output_address = Address::from(Ed25519Address::from_str(&addr.address)?);
253                    Ok((r.amount, output_address, true))
254                }
255            },
256            OutputDto::SignatureLockedDustAllowance(ref r) => match &r.address {
257                AddressDto::Ed25519(addr) => {
258                    let output_address = Address::from(Ed25519Address::from_str(&addr.address)?);
259                    Ok((r.amount, output_address, false))
260                }
261            },
262        }
263    }
264
265    // If custom inputs are provided we check if they are unspent, get the balance and search the address for it
266    async fn get_custom_inputs(
267        &self,
268        inputs: &[UtxoInput],
269        total_to_spend: u64,
270        dust_and_allowance_recorders: &mut Vec<(u64, Address, bool)>,
271    ) -> Result<(Vec<Input>, Vec<Output>, Vec<AddressIndexRecorder>)> {
272        let mut inputs_for_essence = Vec::new();
273        let mut outputs_for_essence = Vec::new();
274        let mut address_index_recorders = Vec::new();
275        let mut remainder_address_balance: (Option<Address>, u64) = (None, 0);
276        let mut total_already_spent = 0;
277        let account_index = self.account_index.unwrap_or(0);
278        for input in inputs {
279            // Only add unspent outputs
280            if let Ok(output) = self.client.get_output(input).await {
281                if !output.is_spent {
282                    let (output_amount, output_address, check_treshold) =
283                        ClientMessageBuilder::get_output_amount_and_address(&output.output)?;
284                    if !check_treshold || output_amount < DUST_THRESHOLD {
285                        dust_and_allowance_recorders.push((output_amount, output_address, false));
286                    }
287
288                    total_already_spent += output_amount;
289                    let bech32_hrp = self.client.get_bech32_hrp().await?;
290                    let (address_index, internal) = match self.seed {
291                        Some(seed) => {
292                            search_address(
293                                seed,
294                                &bech32_hrp,
295                                account_index,
296                                self.input_range.clone(),
297                                &output_address,
298                            )
299                            .await?
300                        }
301                        None => (0, false),
302                    };
303
304                    let address_index_record = ClientMessageBuilder::create_address_index_recorder(
305                        account_index,
306                        address_index,
307                        internal,
308                        &output,
309                        output_address.to_bech32(&bech32_hrp),
310                    )?;
311                    inputs_for_essence.push(address_index_record.input.clone());
312                    address_index_recorders.push(address_index_record);
313                    // Output the remaining tokens back to the original address
314                    if total_already_spent > total_to_spend {
315                        let remaining_balance = total_already_spent - total_to_spend;
316                        // Keep track of remaining balance, we don't add an output here, because we could have
317                        // multiple inputs from the same address, which would create multiple outputs with the
318                        // same address, which is not allowed
319                        remainder_address_balance = (Some(output_address), remaining_balance);
320                    }
321                }
322            }
323        }
324        // Add output from remaining balance of custom inputs if necessary
325        if let Some(address) = remainder_address_balance.0 {
326            if remainder_address_balance.1 < DUST_THRESHOLD {
327                dust_and_allowance_recorders.push((remainder_address_balance.1, address, true));
328            }
329            outputs_for_essence.push(SignatureLockedSingleOutput::new(address, remainder_address_balance.1)?.into());
330        }
331
332        if total_already_spent < total_to_spend {
333            return Err(Error::NotEnoughBalance(total_already_spent, total_to_spend));
334        }
335
336        Ok((inputs_for_essence, outputs_for_essence, address_index_recorders))
337    }
338
339    // Searches inputs for an amount which a user wants to spend, also checks that it doesn't create dust
340    async fn get_inputs(
341        &self,
342        total_to_spend: u64,
343        _dust_and_allowance_recorders: &mut [(u64, Address, bool)],
344    ) -> Result<(Vec<Input>, Vec<Output>, Vec<AddressIndexRecorder>)> {
345        let mut outputs = Vec::new();
346        let mut dust_allowance_outputs = Vec::new();
347        let mut inputs_for_essence = Vec::new();
348        let mut outputs_for_essence = Vec::new();
349        let mut address_index_recorders = Vec::new();
350        let mut total_already_spent = 0;
351        let account_index = self.account_index.unwrap_or(0);
352        let mut gap_index = self.initial_address_index.unwrap_or(0);
353        let mut empty_address_count: u64 = 0;
354        'input_selection: loop {
355            // Get the addresses in the BIP path/index ~ path/index+20
356            let addresses = self
357                .client
358                .get_addresses(self.seed.ok_or(crate::Error::MissingParameter("seed"))?)
359                .with_account_index(account_index)
360                .with_range(gap_index..gap_index + super::ADDRESS_GAP_RANGE)
361                .get_all()
362                .await?;
363            // For each address, get the address outputs
364            let mut address_index = gap_index;
365            for (index, (str_address, internal)) in addresses.iter().enumerate() {
366                let address_outputs = self
367                    .client
368                    .get_address()
369                    .outputs(str_address, Default::default())
370                    .await?;
371
372                // We store output responses locally in outputs and dust_allowance_outputs and after each output we sort
373                // them and try to get enough inputs for the transaction, so we don't request more
374                // outputs than we need
375                for (output_index, output_id) in address_outputs.iter().enumerate() {
376                    let output = self.client.get_output(output_id).await?;
377                    if !output.is_spent {
378                        let (amount, _, _) = ClientMessageBuilder::get_output_amount_and_address(&output.output)?;
379
380                        let output_wrapper = OutputWrapper {
381                            output,
382                            address_index,
383                            internal: *internal,
384                            amount,
385                            address: str_address.clone(),
386                        };
387                        match output_wrapper.output.output {
388                            OutputDto::SignatureLockedSingle(_) => outputs.push(output_wrapper),
389                            OutputDto::SignatureLockedDustAllowance(_) => dust_allowance_outputs.push(output_wrapper),
390                            OutputDto::Treasury(_) => {}
391                        };
392
393                        // Order outputs descending, so that as few inputs as necessary are used
394                        outputs.sort_by(|l, r| r.amount.cmp(&l.amount));
395
396                        // We start using the signature locked outputs, so we don't move dust_allowance_outputs first
397                        // which could result in a unconfirmable transaction if we still have
398                        // dust on that address
399                        let mut iterator: Vec<&OutputWrapper> = outputs.iter().collect();
400                        // We only need dust_allowance_outputs in the last iterator, because otherwise we could use
401                        // a dust allowance output as input while still having dust on the address
402                        if output_index == address_outputs.len() - 1 {
403                            dust_allowance_outputs.sort_by(|l, r| r.amount.cmp(&l.amount));
404                            iterator = iterator.into_iter().chain(dust_allowance_outputs.iter()).collect();
405                        }
406
407                        for (_offset, output_wrapper) in iterator
408                            .iter()
409                            // Max inputs is 127
410                            .take(INPUT_OUTPUT_COUNT_MAX)
411                            .enumerate()
412                        {
413                            total_already_spent += output_wrapper.amount;
414                            let address_index_record = ClientMessageBuilder::create_address_index_recorder(
415                                account_index,
416                                output_wrapper.address_index,
417                                output_wrapper.internal,
418                                &output_wrapper.output,
419                                str_address.to_owned(),
420                            )?;
421                            inputs_for_essence.push(address_index_record.input.clone());
422                            address_index_recorders.push(address_index_record);
423                            // Break if we have enough funds and don't create dust for the remainder
424                            if total_already_spent == total_to_spend
425                                || total_already_spent >= total_to_spend + DUST_THRESHOLD
426                            {
427                                let remaining_balance = total_already_spent - total_to_spend;
428                                // Output possible remaining tokens back to the original address
429                                if remaining_balance != 0 {
430                                    outputs_for_essence.push(
431                                        SignatureLockedSingleOutput::new(
432                                            Address::try_from_bech32(&output_wrapper.address)?,
433                                            remaining_balance,
434                                        )?
435                                        .into(),
436                                    );
437                                }
438                                break 'input_selection;
439                            }
440                        }
441                        // We need to cleare all gathered records if we haven't reached the total amount we need in this
442                        // iteration.
443                        inputs_for_essence.clear();
444                        outputs_for_essence.clear();
445                        address_index_recorders.clear();
446                        total_already_spent = 0;
447                    }
448                }
449
450                // If there are more than 20 (ADDRESS_GAP_RANGE) consecutive empty addresses, then we stop
451                // looking up the addresses belonging to the seed. Note that we don't
452                // really count the exact 20 consecutive empty addresses, which is
453                // unnecessary. We just need to check the address range,
454                // (index * ADDRESS_GAP_RANGE, index * ADDRESS_GAP_RANGE + ADDRESS_GAP_RANGE), where index is
455                // natural number, and to see if the outputs are all empty.
456                if address_outputs.is_empty() {
457                    // Accumulate the empty_address_count for each run of output address searching
458                    empty_address_count += 1;
459                } else {
460                    // Reset counter if there is an output
461                    empty_address_count = 0;
462                }
463
464                // if we just processed an even index, increase the address index
465                // (because the list has public and internal addresses)
466                if index % 2 == 1 {
467                    address_index += 1;
468                }
469            }
470            gap_index += super::ADDRESS_GAP_RANGE;
471            // The gap limit is 20 and use reference 40 here because there's public and internal addresses
472            if empty_address_count >= (super::ADDRESS_GAP_RANGE * 2) as u64 {
473                let inputs_balance = outputs
474                    .iter()
475                    .chain(dust_allowance_outputs.iter())
476                    .fold(0, |acc, output| acc + output.amount);
477                let inputs_amount = outputs.len() + dust_allowance_outputs.len();
478                if inputs_balance >= total_to_spend && inputs_amount > INPUT_OUTPUT_COUNT_MAX {
479                    return Err(Error::ConsolidationRequired(inputs_amount));
480                } else if inputs_balance > total_to_spend {
481                    return Err(Error::DustError(format!(
482                        "Transaction would create a dust output with {}i",
483                        inputs_balance - total_to_spend
484                    )));
485                } else {
486                    return Err(Error::NotEnoughBalance(inputs_balance, total_to_spend));
487                }
488            }
489        }
490
491        Ok((inputs_for_essence, outputs_for_essence, address_index_recorders))
492    }
493
494    /// Prepare a transaction
495    pub async fn prepare_transaction(&self) -> Result<PreparedTransactionData> {
496        // store (amount, address, new_created) to check later if dust is allowed
497        let mut dust_and_allowance_recorders = Vec::new();
498
499        // Calculate the total tokens to spend
500        let mut total_to_spend = 0;
501        for output in &self.outputs {
502            match output {
503                Output::SignatureLockedSingle(x) => {
504                    total_to_spend += x.amount();
505                    if x.amount() < DUST_THRESHOLD {
506                        dust_and_allowance_recorders.push((x.amount(), *x.address(), true));
507                    }
508                }
509                Output::SignatureLockedDustAllowance(x) => {
510                    total_to_spend += x.amount();
511                    dust_and_allowance_recorders.push((x.amount(), *x.address(), true));
512                }
513                _ => {}
514            }
515        }
516
517        // Inputselection
518        let (mut inputs_for_essence, mut outputs_for_essence, address_index_recorders) = match &self.inputs {
519            Some(inputs) => {
520                // 127 is the maximum input amount
521                if inputs.len() > INPUT_OUTPUT_COUNT_MAX {
522                    return Err(Error::ConsolidationRequired(inputs.len()));
523                }
524                self.get_custom_inputs(inputs, total_to_spend, dust_and_allowance_recorders.as_mut())
525                    .await?
526            }
527            None => {
528                self.get_inputs(total_to_spend, dust_and_allowance_recorders.as_mut())
529                    .await?
530            }
531        };
532
533        // Check if we would let dust on an address behind or send new dust, which would make the tx unconfirmable
534        let mut single_addresses = HashSet::new();
535        for dust_or_allowance in &dust_and_allowance_recorders {
536            single_addresses.insert(dust_or_allowance.1);
537        }
538        for address in single_addresses {
539            let created_or_consumed_outputs: Vec<(u64, Address, bool)> = dust_and_allowance_recorders
540                .iter()
541                .cloned()
542                .filter(|d| d.1 == address)
543                .collect();
544            is_dust_allowed(self.client, address, created_or_consumed_outputs).await?;
545        }
546
547        // Build signed transaction payload
548        for output in self.outputs.clone() {
549            outputs_for_essence.push(output);
550        }
551
552        let mut essence = RegularEssence::builder();
553        // Order inputs and add them to the essence
554        inputs_for_essence.sort_unstable_by_key(|a| a.pack_new());
555        essence = essence.with_inputs(inputs_for_essence);
556
557        // Order outputs and add them to the essence
558        outputs_for_essence.sort_unstable_by_key(|a| a.pack_new());
559        essence = essence.with_outputs(outputs_for_essence);
560
561        // Add indexation_payload if index set
562        if let Some(index) = self.index.clone() {
563            let indexation_payload = IndexationPayload::new(&index, &self.data.clone().unwrap_or_default())?;
564            essence = essence.with_payload(Payload::Indexation(Box::new(indexation_payload)))
565        }
566        let regular_essence = essence.finish()?;
567        let essence = Essence::Regular(regular_essence);
568
569        Ok(PreparedTransactionData {
570            essence,
571            address_index_recorders,
572        })
573    }
574
575    /// Sign the transaction
576    pub async fn sign_transaction(
577        &self,
578        prepared_transaction_data: PreparedTransactionData,
579        seed: Option<&'a Seed>,
580        inputs_range: Option<Range<usize>>,
581    ) -> Result<Payload> {
582        let essence = prepared_transaction_data.essence;
583        let mut address_index_recorders = prepared_transaction_data.address_index_recorders;
584        let hashed_essence = essence.hash();
585        let mut unlock_blocks = Vec::new();
586        let mut signature_indexes = HashMap::<String, usize>::new();
587        address_index_recorders.sort_by(|a, b| a.input.cmp(&b.input));
588
589        for (current_block_index, mut recorder) in address_index_recorders.into_iter().enumerate() {
590            // If seed is provided we assume an essence that got prepared without seed and need to find the correct
591            // address indexes and public/internal
592            if seed.is_some() {
593                let (address_index, internal) = search_address(
594                    seed.or(self.seed).ok_or(crate::Error::MissingParameter("Seed"))?,
595                    &recorder.bech32_address[0..4],
596                    recorder.account_index,
597                    inputs_range.clone().unwrap_or_else(|| self.input_range.clone()),
598                    &Address::try_from_bech32(&recorder.bech32_address)?,
599                )
600                .await?;
601                recorder = ClientMessageBuilder::create_address_index_recorder(
602                    recorder.account_index,
603                    address_index,
604                    internal,
605                    &recorder.output,
606                    recorder.bech32_address,
607                )?;
608            }
609
610            // Check if current path is same as previous path
611            // If so, add a reference unlock block
612            // Format to differentiate between public and internal addresses
613            let index = format!("{}{}", recorder.address_index, recorder.internal);
614            if let Some(block_index) = signature_indexes.get(&index) {
615                unlock_blocks.push(UnlockBlock::Reference(ReferenceUnlock::new(*block_index as u16)?));
616            } else {
617                // If not, we need to create a signature unlock block
618                let private_key = seed
619                    .or(self.seed)
620                    .ok_or(crate::Error::MissingParameter("Seed"))?
621                    .derive(Curve::Ed25519, &recorder.chain)?
622                    .secret_key();
623                let public_key = private_key.public_key().to_bytes();
624                // The signature unlock block needs to sign the hash of the entire transaction essence of the
625                // transaction payload
626                let signature = Box::new(private_key.sign(&hashed_essence).to_bytes());
627                unlock_blocks.push(UnlockBlock::Signature(SignatureUnlock::Ed25519(Ed25519Signature::new(
628                    public_key, *signature,
629                ))));
630                signature_indexes.insert(index, current_block_index);
631            }
632        }
633
634        let unlock_blocks = UnlockBlocks::new(unlock_blocks)?;
635        let payload = TransactionPayloadBuilder::new()
636            .with_essence(essence)
637            .with_unlock_blocks(unlock_blocks)
638            .finish()
639            .map_err(|_| Error::TransactionError)?;
640        Ok(Payload::Transaction(Box::new(payload)))
641    }
642
643    /// Consume the builder and get the API result
644    pub async fn finish_indexation(self) -> Result<Message> {
645        let payload: Payload;
646        {
647            let index = &self.index.as_ref();
648            let empty_slice = &vec![];
649            let data = &self.data.as_ref().unwrap_or(empty_slice);
650
651            // build indexation
652            let index = IndexationPayload::new(index.expect("No indexation tag"), data)
653                .map_err(|e| Error::IndexationError(e.to_string()))?;
654            payload = Payload::Indexation(Box::new(index));
655        }
656
657        // building message
658        self.finish_message(Some(payload)).await
659    }
660
661    /// Builds the final message and posts it to the node
662    pub async fn finish_message(self, payload: Option<Payload>) -> Result<Message> {
663        #[cfg(feature = "wasm")]
664        let final_message = {
665            let parent_message_ids = match self.parents {
666                Some(parents) => parents,
667                _ => self.client.get_tips().await?,
668            };
669            let min_pow_score = self.client.get_min_pow_score().await?;
670            let network_id = self.client.get_network_id().await?;
671            finish_single_thread_pow(
672                self.client,
673                network_id,
674                Some(parent_message_ids),
675                payload,
676                min_pow_score,
677            )
678            .await?
679        };
680        #[cfg(not(feature = "wasm"))]
681        let final_message = match self.parents {
682            Some(mut parents) => {
683                // Sort parents
684                parents.sort_unstable_by_key(|a| a.pack_new());
685                parents.dedup();
686
687                let min_pow_score = self.client.get_min_pow_score().await?;
688                let network_id = self.client.get_network_id().await?;
689                do_pow(
690                    crate::client::ClientMinerBuilder::new()
691                        .with_local_pow(self.client.get_local_pow().await)
692                        .finish(),
693                    min_pow_score,
694                    network_id,
695                    payload,
696                    parents,
697                )?
698                .1
699                .ok_or_else(|| Error::Pow("final message pow failed.".to_string()))?
700            }
701            None => finish_pow(self.client, payload).await?,
702        };
703
704        let msg_id = self.client.post_message_json(&final_message).await?;
705        // Get message if we use remote PoW, because the node will change parents and nonce
706        match self.client.get_local_pow().await {
707            true => Ok(final_message),
708            false => {
709                // Request message multiple times because the node maybe didn't process it completely in this time
710                // or a node balancer could be used which forwards the request to different node than we published
711                for time in 1..3 {
712                    if let Ok(message) = self.client.get_message().data(&msg_id).await {
713                        return Ok(message);
714                    }
715                    #[cfg(not(feature = "wasm"))]
716                    sleep(Duration::from_millis(time * 50)).await;
717                    #[cfg(feature = "wasm")]
718                    {
719                        TimeoutFuture::new((time * 50).try_into().unwrap()).await;
720                    }
721                }
722                self.client.get_message().data(&msg_id).await
723            }
724        }
725    }
726}
727
728// Calculate the outputs on this address after this transaction gets confirmed so we know if we can send dust or
729// dust allowance outputs (as input), the bool in the outputs defines if we consume this output (false) or create a new
730// one (true)
731async fn is_dust_allowed(client: &Client, address: Address, outputs: Vec<(u64, Address, bool)>) -> Result<()> {
732    // balance of all dust allowance outputs
733    let mut dust_allowance_balance: i64 = 0;
734    // Amount of dust outputs
735    let mut dust_outputs_amount: i64 = 0;
736
737    // Add outputs from this transaction
738    for (amount, _, add_outputs) in outputs {
739        let sign = if add_outputs { 1 } else { -1 };
740        if amount >= DUST_THRESHOLD {
741            dust_allowance_balance += sign * amount as i64;
742        } else {
743            dust_outputs_amount += sign;
744        }
745    }
746
747    let bech32_hrp = client.get_bech32_hrp().await?;
748
749    let address_data = client.get_address().balance(&address.to_bech32(&bech32_hrp)).await?;
750    // If we create a dust output and a dust allowance output we don't need to check more outputs if the balance/100_000
751    // is < 100 because then we are sure that we didn't reach the max dust outputs
752    if address_data.dust_allowed
753        && dust_outputs_amount == 1
754        && dust_allowance_balance >= 0
755        && address_data.balance as i64 / DUST_DIVISOR < MAX_ALLOWED_DUST_OUTPUTS
756    {
757        return Ok(());
758    } else if !address_data.dust_allowed && dust_outputs_amount == 1 && dust_allowance_balance <= 0 {
759        return Err(Error::DustError(format!(
760            "No dust output allowed on address {}",
761            address.to_bech32(&bech32_hrp)
762        )));
763    }
764
765    // Check all outputs of the address because we want to consume a dust allowance output and don't know if we are
766    // allowed to do that
767    let address_outputs_metadata = client.find_outputs(&[], &[address.to_bech32(&bech32_hrp)]).await?;
768    for output_metadata in address_outputs_metadata {
769        match output_metadata.output {
770            OutputDto::Treasury(_) => {}
771            OutputDto::SignatureLockedDustAllowance(d_a_o) => {
772                dust_allowance_balance += d_a_o.amount as i64;
773            }
774            OutputDto::SignatureLockedSingle(s_o) => {
775                if s_o.amount < DUST_THRESHOLD {
776                    dust_outputs_amount += 1;
777                }
778            }
779        }
780    }
781
782    // Here dust_allowance_balance and dust_outputs_amount should be as if this transaction gets confirmed
783    // Max allowed dust outputs is 100
784    let allowed_dust_amount = std::cmp::min(dust_allowance_balance / DUST_DIVISOR, MAX_ALLOWED_DUST_OUTPUTS);
785    if dust_outputs_amount > allowed_dust_amount {
786        return Err(Error::DustError(format!(
787            "No dust output allowed on address {}",
788            address.to_bech32(&bech32_hrp)
789        )));
790    }
791    Ok(())
792}
793
794/// Does PoW with always new tips
795#[cfg(not(feature = "wasm"))]
796pub async fn finish_pow(client: &Client, payload: Option<Payload>) -> Result<Message> {
797    let local_pow = client.get_local_pow().await;
798    let min_pow_score = client.get_min_pow_score().await?;
799    let tips_interval = client.get_tips_interval().await;
800    let network_id = client.get_network_id().await?;
801    loop {
802        let cancel = MinerCancel::new();
803        let cancel_2 = cancel.clone();
804        let payload_ = payload.clone();
805        let mut parent_messages = client.get_tips().await?;
806        parent_messages.sort_unstable_by_key(|a| a.pack_new());
807        parent_messages.dedup();
808        let time_thread = std::thread::spawn(move || Ok(pow_timeout(tips_interval, cancel)));
809        let pow_thread = std::thread::spawn(move || {
810            do_pow(
811                crate::client::ClientMinerBuilder::new()
812                    .with_local_pow(local_pow)
813                    .with_cancel(cancel_2)
814                    .finish(),
815                min_pow_score,
816                network_id,
817                payload_,
818                parent_messages,
819            )
820        });
821
822        let threads = vec![pow_thread, time_thread];
823        for t in threads {
824            match t.join().expect("Failed to join threads.") {
825                Ok(res) => {
826                    if res.0 != 0 || !local_pow {
827                        if let Some(message) = res.1 {
828                            return Ok(message);
829                        }
830                    }
831                }
832                Err(err) => {
833                    return Err(err);
834                }
835            }
836        }
837    }
838}
839
840// PoW timeout, if we reach this we will restart the PoW with new tips, so the final message will never be lazy
841#[cfg(not(feature = "wasm"))]
842fn pow_timeout(after_seconds: u64, cancel: MinerCancel) -> (u64, Option<Message>) {
843    std::thread::sleep(std::time::Duration::from_secs(after_seconds));
844    cancel.trigger();
845    (0, None)
846}
847
848/// Does PoW
849pub fn do_pow(
850    client_miner: ClientMiner,
851    min_pow_score: f64,
852    network_id: u64,
853    payload: Option<Payload>,
854    parent_messages: Vec<MessageId>,
855) -> Result<(u64, Option<Message>)> {
856    let mut message = MessageBuilder::<ClientMiner>::new();
857    message = message.with_network_id(network_id);
858    if let Some(p) = payload {
859        message = message.with_payload(p);
860    }
861    let message = message
862        .with_parents(Parents::new(parent_messages)?)
863        .with_nonce_provider(client_miner, min_pow_score)
864        .finish()
865        .map_err(Error::MessageError)?;
866    Ok((message.nonce(), Some(message)))
867}
868
869// Single threaded PoW for wasm
870#[cfg(feature = "wasm")]
871use bee_message::payload::option_payload_pack;
872#[cfg(feature = "wasm")]
873use bee_ternary::{b1t6, Btrit, T1B1Buf, TritBuf};
874#[cfg(feature = "wasm")]
875use bytes::Buf;
876#[cfg(feature = "wasm")]
877use crypto::hashes::ternary::{
878    curl_p::{CurlPBatchHasher, BATCH_SIZE},
879    HASH_LENGTH,
880};
881#[cfg(feature = "wasm")]
882use crypto::hashes::{blake2b::Blake2b256, Digest};
883
884// Precomputed natural logarithm of 3 for performance reasons.
885// See https://oeis.org/A002391.
886#[cfg(feature = "wasm")]
887const LN_3: f64 = 1.098_612_288_668_109;
888#[cfg(feature = "wasm")]
889// should take around one second to reach on an average CPU, so shouldn't cause a noticeable delay on tips_interval
890const POW_ROUNDS_BEFORE_INTERVAL_CHECK: usize = 3000;
891#[cfg(feature = "wasm")]
892/// Single threaded PoW function for wasm
893pub async fn finish_single_thread_pow(
894    client: &Client,
895    network_id: u64,
896    parent_messages: Option<Vec<MessageId>>,
897    payload: Option<bee_message::payload::Payload>,
898    target_score: f64,
899) -> crate::Result<Message> {
900    // let mut message_bytes: Vec<u8> = bytes.clone().into();
901    let mut parent_messages = match parent_messages {
902        Some(parents) => parents,
903        None => client.get_tips().await?,
904    };
905
906    // return with 0 as nonce if remote PoW should be used
907    if !client.get_local_pow().await {
908        let mut message_bytes: Vec<u8> = Vec::new();
909        network_id.pack(&mut message_bytes).unwrap();
910        // sort parent messages
911        parent_messages.sort_unstable_by_key(|a| a.pack_new());
912        parent_messages.dedup();
913        Parents::new(parent_messages.clone())?.pack(&mut message_bytes).unwrap();
914        option_payload_pack(&mut message_bytes, payload.clone().as_ref())?;
915        (0_u64).pack(&mut message_bytes).unwrap();
916        return Ok(Message::unpack(&mut message_bytes.reader())?);
917    }
918
919    let tips_interval = client.get_tips_interval().await;
920
921    loop {
922        let mut message_bytes: Vec<u8> = Vec::new();
923        network_id.pack(&mut message_bytes).unwrap();
924        // sort parent messages
925        parent_messages.sort_unstable_by_key(|a| a.pack_new());
926        parent_messages.dedup();
927        Parents::new(parent_messages.clone())?.pack(&mut message_bytes).unwrap();
928        option_payload_pack(&mut message_bytes, payload.clone().as_ref())?;
929
930        let mut pow_digest = TritBuf::<T1B1Buf>::new();
931        let target_zeros =
932            (((message_bytes.len() + std::mem::size_of::<u64>()) as f64 * target_score).ln() / LN_3).ceil() as usize;
933
934        if target_zeros > HASH_LENGTH {
935            return Err(bee_pow::providers::miner::Error::InvalidPowScore(target_score, target_zeros).into());
936        }
937
938        let hash = Blake2b256::digest(&message_bytes);
939
940        b1t6::encode::<T1B1Buf>(&hash).iter().for_each(|t| pow_digest.push(t));
941
942        let mut nonce = 0;
943        let mut hasher = CurlPBatchHasher::<T1B1Buf>::new(HASH_LENGTH);
944        let mut buffers = Vec::<TritBuf<T1B1Buf>>::with_capacity(BATCH_SIZE);
945        for _ in 0..BATCH_SIZE {
946            let mut buffer = TritBuf::<T1B1Buf>::zeros(HASH_LENGTH);
947            buffer[..pow_digest.len()].copy_from(&pow_digest);
948            buffers.push(buffer);
949        }
950        let mining_start = instant::Instant::now();
951        // counter to reduce amount of mining_start.elapsed() calls
952        let mut counter = 0;
953        loop {
954            if counter % POW_ROUNDS_BEFORE_INTERVAL_CHECK == 0
955                && mining_start.elapsed() > std::time::Duration::from_secs(tips_interval)
956            {
957                // update parents
958                parent_messages = client.get_tips().await?;
959                break;
960            }
961            for (i, buffer) in buffers.iter_mut().enumerate() {
962                let nonce_trits = b1t6::encode::<T1B1Buf>(&(nonce + i as u64).to_le_bytes());
963                buffer[pow_digest.len()..pow_digest.len() + nonce_trits.len()].copy_from(&nonce_trits);
964                hasher.add(buffer.clone());
965            }
966            for (i, hash) in hasher.hash().enumerate() {
967                let trailing_zeros = hash.iter().rev().take_while(|t| *t == Btrit::Zero).count();
968                if trailing_zeros >= target_zeros {
969                    (nonce + i as u64).pack(&mut message_bytes).unwrap();
970                    return Ok(Message::unpack(&mut message_bytes.reader())?);
971                }
972            }
973            nonce += BATCH_SIZE as u64;
974            counter += 1;
975        }
976    }
977}