1use 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
28const MAX_ALLOWED_DUST_OUTPUTS: i64 = 100;
30const DUST_DIVISOR: i64 = 100_000;
31const DUST_THRESHOLD: u64 = 1_000_000;
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct PreparedTransactionData {
36 pub essence: Essence,
38 pub address_index_recorders: Vec<AddressIndexRecorder>,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct AddressIndexRecorder {
45 pub account_index: usize,
47 pub input: Input,
49 pub output: OutputResponse,
51 pub address_index: usize,
53 pub chain: Chain,
55 pub internal: bool,
57 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
70pub 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 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 pub fn with_seed(mut self, seed: &'a Seed) -> Self {
103 self.seed.replace(seed);
104 self
105 }
106
107 pub fn with_account_index(mut self, account_index: usize) -> Self {
109 self.account_index.replace(account_index);
110 self
111 }
112
113 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 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 pub fn with_input_range(mut self, range: Range<usize>) -> Self {
133 self.input_range = range;
134 self
135 }
136
137 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 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 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 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 pub fn with_data(mut self, data: Vec<u8>) -> Self {
171 self.data.replace(data);
172 self
173 }
174
175 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 pub async fn finish(self) -> Result<Message> {
186 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 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 self.finish_indexation().await
204 } else {
205 self.finish_message(None).await
207 }
208 }
209
210 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 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 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 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 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 if total_already_spent > total_to_spend {
315 let remaining_balance = total_already_spent - total_to_spend;
316 remainder_address_balance = (Some(output_address), remaining_balance);
320 }
321 }
322 }
323 }
324 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 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 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 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 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 outputs.sort_by(|l, r| r.amount.cmp(&l.amount));
395
396 let mut iterator: Vec<&OutputWrapper> = outputs.iter().collect();
400 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 .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 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 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 inputs_for_essence.clear();
444 outputs_for_essence.clear();
445 address_index_recorders.clear();
446 total_already_spent = 0;
447 }
448 }
449
450 if address_outputs.is_empty() {
457 empty_address_count += 1;
459 } else {
460 empty_address_count = 0;
462 }
463
464 if index % 2 == 1 {
467 address_index += 1;
468 }
469 }
470 gap_index += super::ADDRESS_GAP_RANGE;
471 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 pub async fn prepare_transaction(&self) -> Result<PreparedTransactionData> {
496 let mut dust_and_allowance_recorders = Vec::new();
498
499 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 let (mut inputs_for_essence, mut outputs_for_essence, address_index_recorders) = match &self.inputs {
519 Some(inputs) => {
520 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 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 for output in self.outputs.clone() {
549 outputs_for_essence.push(output);
550 }
551
552 let mut essence = RegularEssence::builder();
553 inputs_for_essence.sort_unstable_by_key(|a| a.pack_new());
555 essence = essence.with_inputs(inputs_for_essence);
556
557 outputs_for_essence.sort_unstable_by_key(|a| a.pack_new());
559 essence = essence.with_outputs(outputs_for_essence);
560
561 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 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_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 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 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 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 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 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 self.finish_message(Some(payload)).await
659 }
660
661 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 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 match self.client.get_local_pow().await {
707 true => Ok(final_message),
708 false => {
709 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
728async fn is_dust_allowed(client: &Client, address: Address, outputs: Vec<(u64, Address, bool)>) -> Result<()> {
732 let mut dust_allowance_balance: i64 = 0;
734 let mut dust_outputs_amount: i64 = 0;
736
737 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 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 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 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#[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#[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
848pub 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#[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#[cfg(feature = "wasm")]
887const LN_3: f64 = 1.098_612_288_668_109;
888#[cfg(feature = "wasm")]
889const POW_ROUNDS_BEFORE_INTERVAL_CHECK: usize = 3000;
891#[cfg(feature = "wasm")]
892pub 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 parent_messages = match parent_messages {
902 Some(parents) => parents,
903 None => client.get_tips().await?,
904 };
905
906 if !client.get_local_pow().await {
908 let mut message_bytes: Vec<u8> = Vec::new();
909 network_id.pack(&mut message_bytes).unwrap();
910 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 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 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 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}