1use std::fmt;
2use std::future::Future;
3use std::ops::RangeInclusive;
4use std::sync::Arc;
5
6use bitcoin::key::Keypair;
7use bitcoin::secp256k1;
8use fedimint_core::Amount;
9use fedimint_core::core::{
10 DynInput, DynOutput, IInput, IOutput, IntoDynInstance, ModuleInstanceId,
11};
12use fedimint_core::encoding::{Decodable, Encodable};
13use fedimint_core::module::Amounts;
14use fedimint_core::task::{MaybeSend, MaybeSync};
15use fedimint_core::transaction::{Transaction, TransactionSignature};
16use fedimint_logging::LOG_CLIENT;
17use itertools::multiunzip;
18use rand::{CryptoRng, Rng, RngCore};
19use secp256k1::Secp256k1;
20use tracing::warn;
21
22use crate::error::TransactionSubmitError;
23use crate::module::{IdxRange, OutPointRange, StateGenerator};
24use crate::sm::{self, DynState};
25use crate::{
26 InstancelessDynClientInput, InstancelessDynClientInputBundle, InstancelessDynClientInputSM,
27 InstancelessDynClientOutput, InstancelessDynClientOutputBundle, InstancelessDynClientOutputSM,
28 states_add_instance, states_to_instanceless_dyn,
29};
30
31#[derive(Clone, Debug)]
32pub struct ClientInput<I = DynInput> {
33 pub input: I,
34 pub keys: Vec<Keypair>,
35 pub amounts: Amounts,
36}
37
38#[derive(Clone)]
39pub struct ClientInputSM<S = DynState> {
40 pub state_machines: StateGenerator<S>,
41}
42
43impl<S> fmt::Debug for ClientInputSM<S> {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 f.write_str("ClientInputSM")
46 }
47}
48
49#[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)]
51pub enum NeverClientContext {}
52
53impl sm::Context for NeverClientContext {
54 const KIND: Option<fedimint_core::core::ModuleKind> = None;
55}
56
57#[derive(Debug, Clone, Eq, PartialEq, Hash, Decodable, Encodable)]
62pub enum NeverClientStateMachine {}
63
64impl IntoDynInstance for NeverClientStateMachine {
65 type DynType = DynState;
66
67 fn into_dyn(self, _instance_id: ModuleInstanceId) -> Self::DynType {
68 unreachable!()
69 }
70}
71impl sm::State for NeverClientStateMachine {
72 type ModuleContext = NeverClientContext;
73
74 fn transitions(
75 &self,
76 _context: &Self::ModuleContext,
77 _global_context: &crate::DynGlobalClientContext,
78 ) -> Vec<sm::StateTransition<Self>> {
79 unreachable!()
80 }
81
82 fn operation_id(&self) -> fedimint_core::core::OperationId {
83 unreachable!()
84 }
85}
86
87#[derive(Clone, Debug)]
91pub struct ClientInputBundle<I = DynInput, S = DynState> {
92 pub(crate) inputs: Vec<ClientInput<I>>,
93 pub(crate) sm_gens: Vec<ClientInputSM<S>>,
94}
95
96impl<I> ClientInputBundle<I, NeverClientStateMachine> {
97 pub fn new_no_sm(inputs: Vec<ClientInput<I>>) -> Self {
102 if inputs.is_empty() {
103 warn!(target: LOG_CLIENT, "Empty input bundle will be illegal in the future");
105 }
106 Self {
107 inputs,
108 sm_gens: vec![],
109 }
110 }
111}
112
113impl<I, S> ClientInputBundle<I, S>
114where
115 I: IInput + MaybeSend + MaybeSync + 'static,
116 S: sm::IState + MaybeSend + MaybeSync + 'static,
117{
118 pub fn new(inputs: Vec<ClientInput<I>>, sm_gens: Vec<ClientInputSM<S>>) -> Self {
119 Self { inputs, sm_gens }
120 }
121
122 pub fn sms(&self) -> &[ClientInputSM<S>] {
123 &self.sm_gens
124 }
125
126 pub fn into_instanceless(self) -> InstancelessDynClientInputBundle {
127 InstancelessDynClientInputBundle {
128 inputs: self
129 .inputs
130 .into_iter()
131 .map(|input| InstancelessDynClientInput {
132 input: Box::new(input.input),
133 keys: input.keys,
134 amounts: input.amounts,
135 })
136 .collect(),
137 sm_gens: self
138 .sm_gens
139 .into_iter()
140 .map(|input_sm| InstancelessDynClientInputSM {
141 state_machines: states_to_instanceless_dyn(input_sm.state_machines),
142 })
143 .collect(),
144 }
145 }
146}
147
148impl<I, S> ClientInputBundle<I, S> {
149 pub fn inputs(&self) -> &[ClientInput<I>] {
150 &self.inputs
151 }
152
153 pub fn is_empty(&self) -> bool {
154 self.inputs.is_empty()
156 }
157}
158
159impl<I> IntoDynInstance for ClientInput<I>
160where
161 I: IntoDynInstance<DynType = DynInput> + 'static,
162{
163 type DynType = ClientInput;
164
165 fn into_dyn(self, module_instance_id: ModuleInstanceId) -> ClientInput {
166 ClientInput {
167 input: self.input.into_dyn(module_instance_id),
168 keys: self.keys,
169 amounts: self.amounts,
170 }
171 }
172}
173
174impl<S> IntoDynInstance for ClientInputSM<S>
175where
176 S: IntoDynInstance<DynType = DynState> + 'static,
177{
178 type DynType = ClientInputSM;
179
180 fn into_dyn(self, module_instance_id: ModuleInstanceId) -> ClientInputSM {
181 ClientInputSM {
182 state_machines: state_gen_to_dyn(self.state_machines, module_instance_id),
183 }
184 }
185}
186
187impl<I, S> IntoDynInstance for ClientInputBundle<I, S>
188where
189 I: IntoDynInstance<DynType = DynInput> + 'static,
190 S: IntoDynInstance<DynType = DynState> + 'static,
191{
192 type DynType = ClientInputBundle;
193
194 fn into_dyn(self, module_instance_id: ModuleInstanceId) -> ClientInputBundle {
195 ClientInputBundle {
196 inputs: self
197 .inputs
198 .into_iter()
199 .map(|input| input.into_dyn(module_instance_id))
200 .collect::<Vec<ClientInput>>(),
201
202 sm_gens: self
203 .sm_gens
204 .into_iter()
205 .map(|input_sm| input_sm.into_dyn(module_instance_id))
206 .collect::<Vec<ClientInputSM>>(),
207 }
208 }
209}
210
211impl IntoDynInstance for InstancelessDynClientInputBundle {
212 type DynType = ClientInputBundle;
213
214 fn into_dyn(self, module_instance_id: ModuleInstanceId) -> ClientInputBundle {
215 ClientInputBundle {
216 inputs: self
217 .inputs
218 .into_iter()
219 .map(|input| ClientInput {
220 input: DynInput::from_parts(module_instance_id, input.input),
221 keys: input.keys,
222 amounts: input.amounts,
223 })
224 .collect::<Vec<ClientInput>>(),
225
226 sm_gens: self
227 .sm_gens
228 .into_iter()
229 .map(|input_sm| ClientInputSM {
230 state_machines: states_add_instance(
231 module_instance_id,
232 input_sm.state_machines,
233 ),
234 })
235 .collect::<Vec<ClientInputSM>>(),
236 }
237 }
238}
239
240#[derive(Clone, Debug)]
241pub struct ClientOutputBundle<O = DynOutput, S = DynState> {
242 pub(crate) outputs: Vec<ClientOutput<O>>,
243 pub(crate) sm_gens: Vec<ClientOutputSM<S>>,
244}
245
246#[derive(Clone, Debug)]
247pub struct ClientOutput<O = DynOutput> {
248 pub output: O,
249 pub amounts: Amounts,
250}
251
252#[derive(Clone)]
253pub struct ClientOutputSM<S = DynState> {
254 pub state_machines: StateGenerator<S>,
255}
256
257impl<S> fmt::Debug for ClientOutputSM<S> {
258 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
259 f.write_str("ClientOutputSM")
260 }
261}
262impl<O> ClientOutputBundle<O, NeverClientStateMachine> {
263 pub fn new_no_sm(outputs: Vec<ClientOutput<O>>) -> Self {
268 if outputs.is_empty() {
269 warn!(target: LOG_CLIENT, "Empty output bundle will be illegal in the future");
271 }
272 Self {
273 outputs,
274 sm_gens: vec![],
275 }
276 }
277}
278impl<O, S> ClientOutputBundle<O, S> {
279 pub fn outputs(&self) -> &[ClientOutput<O>] {
280 &self.outputs
281 }
282}
283
284impl<O, S> ClientOutputBundle<O, S>
285where
286 O: IOutput + MaybeSend + MaybeSync + 'static,
287 S: sm::IState + MaybeSend + MaybeSync + 'static,
288{
289 pub fn new(outputs: Vec<ClientOutput<O>>, sm_gens: Vec<ClientOutputSM<S>>) -> Self {
290 Self { outputs, sm_gens }
291 }
292
293 pub fn sms(&self) -> &[ClientOutputSM<S>] {
294 &self.sm_gens
295 }
296
297 pub fn with(mut self, other: Self) -> Self {
298 self.outputs.extend(other.outputs);
299 self.sm_gens.extend(other.sm_gens);
300 self
301 }
302
303 pub fn into_instanceless(self) -> InstancelessDynClientOutputBundle {
304 InstancelessDynClientOutputBundle {
305 outputs: self
306 .outputs
307 .into_iter()
308 .map(|output| InstancelessDynClientOutput {
309 output: Box::new(output.output),
310 amounts: output.amounts,
311 })
312 .collect(),
313 sm_gens: self
314 .sm_gens
315 .into_iter()
316 .map(|output_sm| InstancelessDynClientOutputSM {
317 state_machines: states_to_instanceless_dyn(output_sm.state_machines),
318 })
319 .collect(),
320 }
321 }
322}
323
324impl<O, S> ClientOutputBundle<O, S> {
325 pub fn is_empty(&self) -> bool {
326 self.outputs.is_empty()
328 }
329}
330
331impl<I, S> IntoDynInstance for ClientOutputBundle<I, S>
332where
333 I: IntoDynInstance<DynType = DynOutput> + 'static,
334 S: IntoDynInstance<DynType = DynState> + 'static,
335{
336 type DynType = ClientOutputBundle;
337
338 fn into_dyn(self, module_instance_id: ModuleInstanceId) -> ClientOutputBundle {
339 ClientOutputBundle {
340 outputs: self
341 .outputs
342 .into_iter()
343 .map(|output| output.into_dyn(module_instance_id))
344 .collect::<Vec<ClientOutput>>(),
345
346 sm_gens: self
347 .sm_gens
348 .into_iter()
349 .map(|output_sm| output_sm.into_dyn(module_instance_id))
350 .collect::<Vec<ClientOutputSM>>(),
351 }
352 }
353}
354
355impl IntoDynInstance for InstancelessDynClientOutputBundle {
356 type DynType = ClientOutputBundle;
357
358 fn into_dyn(self, module_instance_id: ModuleInstanceId) -> ClientOutputBundle {
359 ClientOutputBundle {
360 outputs: self
361 .outputs
362 .into_iter()
363 .map(|output| ClientOutput {
364 output: DynOutput::from_parts(module_instance_id, output.output),
365 amounts: output.amounts,
366 })
367 .collect::<Vec<ClientOutput>>(),
368
369 sm_gens: self
370 .sm_gens
371 .into_iter()
372 .map(|output_sm| ClientOutputSM {
373 state_machines: states_add_instance(
374 module_instance_id,
375 output_sm.state_machines,
376 ),
377 })
378 .collect::<Vec<ClientOutputSM>>(),
379 }
380 }
381}
382
383impl<I> IntoDynInstance for ClientOutput<I>
384where
385 I: IntoDynInstance<DynType = DynOutput> + 'static,
386{
387 type DynType = ClientOutput;
388
389 fn into_dyn(self, module_instance_id: ModuleInstanceId) -> ClientOutput {
390 ClientOutput {
391 output: self.output.into_dyn(module_instance_id),
392 amounts: self.amounts,
393 }
394 }
395}
396
397impl<S> IntoDynInstance for ClientOutputSM<S>
398where
399 S: IntoDynInstance<DynType = DynState> + 'static,
400{
401 type DynType = ClientOutputSM;
402
403 fn into_dyn(self, module_instance_id: ModuleInstanceId) -> ClientOutputSM {
404 ClientOutputSM {
405 state_machines: state_gen_to_dyn(self.state_machines, module_instance_id),
406 }
407 }
408}
409
410#[derive(Debug, Clone, PartialEq, Eq)]
429pub struct FeeQuoteRequest {
430 pub input_amount: Amounts,
432 pub output_amount: Amounts,
434 pub input_fee: Amounts,
436 pub output_fee: Amounts,
438}
439
440#[derive(Debug, Clone, PartialEq, Eq)]
454pub struct FeeQuote {
455 pub input: Amounts,
459 pub output: Amounts,
462 pub dust: Amounts,
464}
465
466impl FeeQuote {
467 pub const ZERO: Self = Self {
471 input: Amounts::ZERO,
472 output: Amounts::ZERO,
473 dust: Amounts::ZERO,
474 };
475
476 pub fn total(&self) -> Amounts {
479 self.input
480 .clone()
481 .checked_add(&self.output)
482 .and_then(|sum| sum.checked_add(&self.dust))
483 .expect("aggregate fee components cannot overflow an Amounts")
484 }
485}
486
487pub async fn max_affordable_send_amount<GrossUp, Quote, Fut>(
524 balance: Amount,
525 min_amount: Amount,
526 max_amount: Amount,
527 gross_up: GrossUp,
528 fee_quote: Quote,
529) -> Result<Option<Amount>, TransactionSubmitError>
530where
531 GrossUp: Fn(Amount) -> Amount,
532 Quote: Fn(Amount) -> Fut,
533 Fut: Future<Output = Result<FeeQuote, TransactionSubmitError>>,
534{
535 let hi_bound = max_amount.msats.min(balance.msats);
537 let lo_bound = min_amount.msats;
538
539 if lo_bound > hi_bound {
540 return Ok(None);
541 }
542
543 if gross_up(Amount::from_msats(lo_bound)).msats > balance.msats {
551 return Ok(None);
553 }
554 let fundable_max = {
555 let mut lo = lo_bound;
556 let mut hi = hi_bound;
557 while lo < hi {
558 let mid = lo + (hi - lo).div_ceil(2);
559 if gross_up(Amount::from_msats(mid)).msats <= balance.msats {
560 lo = mid;
561 } else {
562 hi = mid - 1;
563 }
564 }
565 lo
566 };
567
568 let mut lo = lo_bound;
569 let mut hi = fundable_max;
570 let mut lo_affordable = false;
571
572 let funded = gross_up(Amount::from_msats(fundable_max));
580 match fee_quote(funded).await {
581 Ok(quote) => {
582 let total = funded
583 .msats
584 .saturating_add(quote.total().get_bitcoin().msats);
585 if total <= balance.msats {
586 return Ok(Some(Amount::from_msats(fundable_max)));
588 }
589 let overhead = total.saturating_sub(fundable_max);
590 let seed = balance
591 .msats
592 .saturating_sub(overhead)
593 .clamp(lo_bound, fundable_max);
594 if send_amount_affordable(Amount::from_msats(seed), balance, &gross_up, &fee_quote)
595 .await?
596 {
597 lo = seed;
598 lo_affordable = true;
599 }
600 }
601 Err(e) if e.is_insufficient_funds() => {}
602 Err(e) => return Err(e),
603 }
604
605 if !lo_affordable
608 && !send_amount_affordable(Amount::from_msats(lo_bound), balance, &gross_up, &fee_quote)
609 .await?
610 {
611 return Ok(None);
612 }
613
614 while lo < hi {
616 let mid = lo + (hi - lo).div_ceil(2);
618
619 if send_amount_affordable(Amount::from_msats(mid), balance, &gross_up, &fee_quote).await? {
620 lo = mid;
621 } else {
622 hi = mid - 1;
623 }
624 }
625
626 Ok(Some(Amount::from_msats(lo)))
627}
628
629async fn send_amount_affordable<GrossUp, Quote, Fut>(
636 amount: Amount,
637 balance: Amount,
638 gross_up: &GrossUp,
639 fee_quote: &Quote,
640) -> Result<bool, TransactionSubmitError>
641where
642 GrossUp: Fn(Amount) -> Amount,
643 Quote: Fn(Amount) -> Fut,
644 Fut: Future<Output = Result<FeeQuote, TransactionSubmitError>>,
645{
646 let funded_amount = gross_up(amount);
647
648 if funded_amount > balance {
649 return Ok(false);
650 }
651
652 match fee_quote(funded_amount).await {
653 Ok(quote) => Ok(funded_amount + quote.total().get_bitcoin() <= balance),
654 Err(e) if e.is_insufficient_funds() => Ok(false),
655 Err(e) => Err(e),
656 }
657}
658
659#[derive(Default, Clone, Debug)]
660pub struct TransactionBuilder {
661 inputs: Vec<ClientInputBundle>,
662 outputs: Vec<ClientOutputBundle>,
663}
664
665impl TransactionBuilder {
666 pub fn new() -> Self {
667 Self::default()
668 }
669
670 pub fn with_inputs(mut self, inputs: ClientInputBundle) -> Self {
671 self.inputs.push(inputs);
672 self
673 }
674
675 pub fn with_outputs(mut self, outputs: ClientOutputBundle) -> Self {
676 self.outputs.push(outputs);
677 self
678 }
679
680 pub fn build<C, R: RngCore + CryptoRng>(
681 self,
682 secp_ctx: &Secp256k1<C>,
683 mut rng: R,
684 ) -> (Transaction, Vec<DynState>)
685 where
686 C: secp256k1::Signing + secp256k1::Verification,
687 {
688 let (input_idx_to_bundle_idx, inputs, input_keys): (Vec<_>, Vec<_>, Vec<_>) = multiunzip(
693 self.inputs
694 .iter()
695 .enumerate()
696 .flat_map(|(bundle_idx, bundle)| {
697 bundle
698 .inputs
699 .iter()
700 .map(move |input| (bundle_idx, input.input.clone(), input.keys.clone()))
701 }),
702 );
703 let (output_idx_to_bundle_idx, outputs): (Vec<_>, Vec<_>) = multiunzip(
706 self.outputs
707 .iter()
708 .enumerate()
709 .flat_map(|(bundle_idx, bundle)| {
710 bundle
711 .outputs
712 .iter()
713 .map(move |output| (bundle_idx, output.output.clone()))
714 }),
715 );
716 let nonce: [u8; 8] = rng.r#gen();
717
718 let txid = Transaction::tx_hash_from_parts(&inputs, &outputs, nonce);
719 let msg = secp256k1::Message::from_digest_slice(&txid[..]).expect("txid has right length");
720
721 let signatures = input_keys
722 .iter()
723 .flatten()
724 .map(|keypair| secp_ctx.sign_schnorr(&msg, keypair))
725 .collect();
726
727 let transaction = Transaction {
728 inputs,
729 outputs,
730 nonce,
731 signatures: TransactionSignature::NaiveMultisig(signatures),
732 };
733
734 let input_states = self
735 .inputs
736 .into_iter()
737 .enumerate()
738 .filter(|(_, bundle)| !bundle.is_empty())
739 .flat_map(|(bundle_idx, bundle)| {
740 let input_idxs = find_range_of_matching_items(&input_idx_to_bundle_idx, bundle_idx)
741 .expect("Non empty bundles must always have a match");
742 bundle.sm_gens.into_iter().flat_map(move |sm| {
743 (sm.state_machines)(OutPointRange::new(
744 txid,
745 IdxRange::from_inclusive(input_idxs.clone()).expect("can't overflow"),
746 ))
747 })
748 });
749
750 let output_states = self
751 .outputs
752 .into_iter()
753 .enumerate()
754 .filter(|(_, bundle)| !bundle.is_empty())
755 .flat_map(|(bundle_idx, bundle)| {
756 let output_idxs =
757 find_range_of_matching_items(&output_idx_to_bundle_idx, bundle_idx)
758 .expect("Non empty bundles must always have a match");
759 bundle.sm_gens.into_iter().flat_map(move |sm| {
760 (sm.state_machines)(OutPointRange::new(
761 txid,
762 IdxRange::from_inclusive(output_idxs.clone())
763 .expect("can't possibly overflow"),
764 ))
765 })
766 });
767 (transaction, input_states.chain(output_states).collect())
768 }
769
770 pub fn inputs(&self) -> impl Iterator<Item = &ClientInput> {
771 self.inputs.iter().flat_map(|i| i.inputs.iter())
772 }
773
774 pub fn outputs(&self) -> impl Iterator<Item = &ClientOutput> {
775 self.outputs.iter().flat_map(|i| i.outputs.iter())
776 }
777}
778
779fn find_range_of_matching_items(arr: &[usize], item: usize) -> Option<RangeInclusive<u64>> {
782 debug_assert!(arr.windows(2).all(|w| w[0] <= w[1]));
784
785 arr.iter()
786 .enumerate()
787 .filter_map(|(arr_idx, arr_item)| (*arr_item == item).then_some(arr_idx as u64))
788 .fold(None, |cur: Option<(u64, u64)>, idx| {
789 Some(cur.map_or((idx, idx), |cur| (cur.0.min(idx), cur.1.max(idx))))
790 })
791 .map(|(start, end)| start..=end)
792}
793
794#[test]
795fn find_range_of_matching_items_sanity() {
796 assert_eq!(find_range_of_matching_items(&[0, 0], 0), Some(0..=1));
797 assert_eq!(find_range_of_matching_items(&[0, 0, 1], 0), Some(0..=1));
798 assert_eq!(find_range_of_matching_items(&[0, 0, 1], 1), Some(2..=2));
799 assert_eq!(find_range_of_matching_items(&[0, 0, 1], 2), None);
800 assert_eq!(find_range_of_matching_items(&[], 0), None);
801}
802
803fn state_gen_to_dyn<S>(
804 state_gen: StateGenerator<S>,
805 module_instance: ModuleInstanceId,
806) -> StateGenerator<DynState>
807where
808 S: IntoDynInstance<DynType = DynState> + 'static,
809{
810 Arc::new(move |out_point_range| {
811 let states = state_gen(out_point_range);
812 states
813 .into_iter()
814 .map(|state| state.into_dyn(module_instance))
815 .collect()
816 })
817}
818
819#[cfg(test)]
820mod tests;