mina_tree/staged_ledger/
resources.rs

1use std::collections::BTreeMap;
2
3use mina_signer::CompressedPubKey;
4use openmina_core::constants::ConstraintConstants;
5
6use crate::{
7    scan_state::{
8        currency::{Amount, Fee, Magnitude},
9        scan_state::transaction_snark::work,
10        transaction_logic::{valid, CoinbaseFeeTransfer, GenericCommand},
11    },
12    MyCow,
13};
14
15use super::{
16    diff::AtMostTwo,
17    pre_diff_info::{fee_transfers_map, sum_fees},
18    staged_ledger::StagedLedger,
19};
20
21#[derive(Clone)]
22pub struct Discarded {
23    pub commands_rev: Vec<valid::UserCommand>,
24    pub completed_work: Vec<work::Checked>,
25}
26
27impl std::fmt::Debug for Discarded {
28    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29        let Self {
30            commands_rev,
31            completed_work,
32        } = self;
33
34        f.debug_struct("Discarded")
35            .field("commands_rev", &commands_rev.len())
36            .field("completed_work", &completed_work.len())
37            .finish()
38    }
39}
40
41impl Discarded {
42    fn add_user_command(&mut self, cmd: valid::UserCommand) {
43        self.commands_rev.push(cmd);
44    }
45
46    fn add_completed_work(&mut self, work: work::Checked) {
47        self.completed_work.push(work);
48    }
49}
50
51pub enum IncreaseBy {
52    One,
53    Two,
54}
55
56#[derive(Clone)]
57pub struct Resources {
58    max_space: u64,
59    max_jobs: u64,
60    pub commands_rev: Vec<valid::UserCommand>,
61    pub completed_work_rev: Vec<work::Checked>, // TODO: Use another container (VecDeque ?)
62    fee_transfers: BTreeMap<CompressedPubKey, Fee>,
63    add_coinbase: bool,
64    pub coinbase: AtMostTwo<CoinbaseFeeTransfer>,
65    supercharge_coinbase: bool,
66    receiver_pk: CompressedPubKey,
67    budget: Result<Fee, String>,
68    pub discarded: Discarded,
69    is_coinbase_receiver_new: bool,
70    _logger: (),
71}
72
73impl std::fmt::Debug for Resources {
74    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75        let Self {
76            max_space,
77            max_jobs,
78            commands_rev,
79            completed_work_rev,
80            fee_transfers,
81            add_coinbase,
82            coinbase,
83            supercharge_coinbase,
84            receiver_pk,
85            budget,
86            discarded,
87            is_coinbase_receiver_new,
88            _logger,
89        } = self;
90
91        f.debug_struct("Resources")
92            .field("max_space", max_space)
93            .field("max_jobs", max_jobs)
94            .field("commands_rev", &commands_rev.len())
95            .field("completed_work_rev", &completed_work_rev.len())
96            .field("fee_transfers", fee_transfers)
97            .field("add_coinbase", add_coinbase)
98            .field("coinbase", coinbase)
99            .field("supercharge_coinbase", supercharge_coinbase)
100            .field("receiver_pk", receiver_pk)
101            .field("budget", budget)
102            .field("discarded", discarded)
103            .field("is_coinbase_receiver_new", is_coinbase_receiver_new)
104            .field("_logger", _logger)
105            .finish()
106    }
107}
108
109impl Resources {
110    /// <https://github.com/MinaProtocol/mina/blob/05c2f73d0f6e4f1341286843814ce02dcb3919e0/src/lib/staged_ledger/staged_ledger.ml#L1149>
111    fn coinbase_ft(work: work::Unchecked) -> Option<CoinbaseFeeTransfer> {
112        // Here we could not add the fee transfer if the prover=receiver_pk but
113        // retaining it to preserve that information in the
114        // staged_ledger_diff. It will be checked in apply_diff before adding*)
115
116        if !work.fee.is_zero() {
117            Some(CoinbaseFeeTransfer::create(work.prover, work.fee))
118        } else {
119            None
120        }
121    }
122
123    /// <https://github.com/MinaProtocol/mina/blob/05c2f73d0f6e4f1341286843814ce02dcb3919e0/src/lib/staged_ledger/staged_ledger.ml#L1157>
124    fn cheapest_two_work(works: &[work::Checked]) -> (Option<work::Work>, Option<work::Work>) {
125        let (w1, w2) = works
126            .iter()
127            .fold((None, None), |(w1, w2), w| match (w1, w2) {
128                (None, _) => (Some(w), None),
129                (Some(x), None) => {
130                    if w.fee < x.fee {
131                        (Some(w), w1)
132                    } else {
133                        (w1, Some(w))
134                    }
135                }
136                (Some(x), Some(y)) => {
137                    if w.fee < x.fee {
138                        (Some(w), w1)
139                    } else if w.fee < y.fee {
140                        (w1, Some(w))
141                    } else {
142                        (w1, w2)
143                    }
144                }
145            });
146
147        (w1.cloned(), w2.cloned())
148    }
149
150    /// <https://github.com/MinaProtocol/mina/blob/05c2f73d0f6e4f1341286843814ce02dcb3919e0/src/lib/staged_ledger/staged_ledger.ml#L1171>
151    fn coinbase_work(
152        constraint_constants: &ConstraintConstants,
153        is_two: Option<bool>,
154        works: &[work::Checked],
155        is_coinbase_receiver_new: bool,
156        supercharge_coinbase: bool,
157    ) -> Option<(AtMostTwo<CoinbaseFeeTransfer>, Vec<work::Work>)> {
158        let is_two = is_two.unwrap_or(false);
159
160        let (min1, min2) = Self::cheapest_two_work(works);
161
162        let diff = |ws: &[work::Unchecked], ws2: &[work::Statement]| -> Vec<work::Unchecked> {
163            ws.iter()
164                .filter(|w| {
165                    let wstatement = w.statement();
166                    !ws2.iter().any(|w2| &wstatement == w2)
167                })
168                .cloned()
169                .collect()
170        };
171
172        let coinbase_amount =
173            StagedLedger::coinbase_amount(supercharge_coinbase, constraint_constants)?;
174
175        // if the coinbase receiver is new then the account creation fee will
176        // be deducted from the reward
177        let budget = if is_coinbase_receiver_new {
178            coinbase_amount
179                .checked_sub(&Amount::from_u64(constraint_constants.account_creation_fee))?
180        } else {
181            coinbase_amount
182        };
183
184        if is_two {
185            match (min1, min2) {
186                (None, _) => None,
187                (Some(w), None) => {
188                    if Amount::of_fee(&w.fee) <= budget {
189                        let stmt = w.statement();
190                        let cb = AtMostTwo::Two(Self::coinbase_ft(w).map(|ft| (ft, None)));
191                        Some((cb, diff(works, &[stmt])))
192                    } else {
193                        let cb = AtMostTwo::Two(None);
194                        Some((cb, works.to_vec()))
195                    }
196                }
197                (Some(w1), Some(w2)) => {
198                    let sum = w1.fee.checked_add(&w2.fee)?;
199
200                    if Amount::of_fee(&sum) < budget {
201                        let stmt1 = w1.statement();
202                        let stmt2 = w2.statement();
203                        let cb = AtMostTwo::Two(
204                            Self::coinbase_ft(w1).map(|ft| (ft, Self::coinbase_ft(w2))),
205                        );
206
207                        // Why add work without checking if work constraints are
208                        // satisfied? If we reach here then it means that we are trying to
209                        // fill the last two slots of the tree with coinbase trnasactions
210                        // and if there's any work in [works] then that has to be included,
211                        // either in the coinbase or as fee transfers that gets paid by
212                        // the transaction fees. So having it as coinbase ft will at least
213                        // reduce the slots occupied by fee transfers*)
214
215                        Some((cb, diff(works, &[stmt1, stmt2])))
216                    } else if Amount::of_fee(&w1.fee) <= coinbase_amount {
217                        let stmt = w1.statement();
218                        let cb = AtMostTwo::Two(Self::coinbase_ft(w1).map(|ft| (ft, None)));
219                        Some((cb, diff(works, &[stmt])))
220                    } else {
221                        let cb = AtMostTwo::Two(None);
222                        Some((cb, works.to_vec()))
223                    }
224                }
225            }
226        } else {
227            min1.map(|w| {
228                if Amount::of_fee(&w.fee) <= budget {
229                    let stmt = w.statement();
230                    let cb = AtMostTwo::One(Self::coinbase_ft(w));
231                    (cb, diff(works, &[stmt]))
232                } else {
233                    let cb = AtMostTwo::One(None);
234                    (cb, works.to_vec())
235                }
236            })
237        }
238    }
239
240    /// <https://github.com/MinaProtocol/mina/blob/05c2f73d0f6e4f1341286843814ce02dcb3919e0/src/lib/staged_ledger/staged_ledger.ml#L1244>
241    fn init_coinbase_and_fee_transfers(
242        constraint_constants: &ConstraintConstants,
243        cw_seq: &[work::Unchecked],
244        add_coinbase: bool,
245        job_count: u64,
246        slots: u64,
247        is_coinbase_receiver_new: bool,
248        supercharge_coinbase: bool,
249    ) -> (AtMostTwo<CoinbaseFeeTransfer>, Vec<(CompressedPubKey, Fee)>) {
250        let cw_unchecked =
251            |works: Vec<work::Unchecked>| works.into_iter().map(|w| w.forget()).collect::<Vec<_>>();
252
253        let (coinbase, rem_cw) = match (
254            add_coinbase,
255            Self::coinbase_work(
256                constraint_constants,
257                None,
258                cw_seq,
259                is_coinbase_receiver_new,
260                supercharge_coinbase,
261            ),
262        ) {
263            (true, Some((ft, rem_cw))) => (ft, rem_cw),
264            (true, None) => {
265                // Coinbase could not be added because work-fees > coinbase-amount
266                if job_count == 0 || slots - job_count >= 1 {
267                    // Either no jobs are required or there is a free slot that can be filled
268                    // without having to include any work
269                    (AtMostTwo::One(None), cw_seq.to_vec())
270                } else {
271                    (AtMostTwo::Zero, cw_seq.to_vec())
272                }
273            }
274            _ => (AtMostTwo::Zero, cw_seq.to_vec()),
275        };
276
277        let rem_cw = cw_unchecked(rem_cw);
278        let singles = rem_cw
279            .into_iter()
280            .filter_map(|work::Unchecked { fee, prover, .. }| {
281                if fee.is_zero() {
282                    None
283                } else {
284                    Some((prover, fee))
285                }
286            })
287            .rev()
288            .collect();
289
290        (coinbase, singles)
291    }
292
293    /// <https://github.com/MinaProtocol/mina/blob/05c2f73d0f6e4f1341286843814ce02dcb3919e0/src/lib/staged_ledger/staged_ledger.ml#L1276>
294    pub fn init(
295        constraint_constants: &ConstraintConstants,
296        uc_seq: Vec<valid::UserCommand>,
297        mut cw_seq: Vec<work::Checked>,
298        (slots, job_count): (u64, u64),
299        receiver_pk: CompressedPubKey,
300        add_coinbase: bool,
301        supercharge_coinbase: bool,
302        _logger: (),
303        is_coinbase_receiver_new: bool,
304    ) -> Self {
305        let (coinbase, singles) = Self::init_coinbase_and_fee_transfers(
306            constraint_constants,
307            &cw_seq,
308            add_coinbase,
309            job_count,
310            slots,
311            is_coinbase_receiver_new,
312            supercharge_coinbase,
313        );
314
315        let fee_transfers = fee_transfers_map(singles.clone()).expect("OCaml throw here");
316
317        let budget1 = sum_fees(&uc_seq, |c| c.fee());
318        let budget2 = sum_fees(singles.iter().filter(|(k, _)| k != &receiver_pk), |c| c.1);
319
320        let budget = match (budget1, budget2) {
321            (Ok(r), Ok(c)) => r
322                .checked_sub(&c)
323                .ok_or_else(|| "budget did not suffice".to_string()),
324            (_, Err(e)) | (Err(e), _) => Err(e),
325        };
326
327        let discarded = Discarded {
328            commands_rev: Vec::with_capacity(256),
329            completed_work: Vec::with_capacity(256),
330        };
331
332        Self {
333            max_space: slots,
334            max_jobs: job_count,
335            commands_rev: uc_seq,
336            completed_work_rev: {
337                // Completed work in reverse order for faster removal of proofs if budget doesn't suffice
338                cw_seq.reverse();
339                cw_seq
340            },
341            fee_transfers,
342            add_coinbase,
343            coinbase,
344            supercharge_coinbase,
345            receiver_pk,
346            budget,
347            discarded,
348            is_coinbase_receiver_new,
349            _logger,
350        }
351    }
352
353    /// <https://github.com/MinaProtocol/mina/blob/05c2f73d0f6e4f1341286843814ce02dcb3919e0/src/lib/staged_ledger/staged_ledger.ml#L1335>
354    fn reselect_coinbase_work(&mut self, constraint_constants: &ConstraintConstants) {
355        let cw_unchecked = |works: &[work::Unchecked]| {
356            works.iter().map(|w| w.clone().forget()).collect::<Vec<_>>()
357        };
358
359        let (coinbase, rem_cw) = match &self.coinbase {
360            AtMostTwo::Zero => (None, MyCow::Borrow(&self.completed_work_rev)),
361            AtMostTwo::One(_) => {
362                match Self::coinbase_work(
363                    constraint_constants,
364                    None,
365                    &self.completed_work_rev,
366                    self.is_coinbase_receiver_new,
367                    self.supercharge_coinbase,
368                ) {
369                    None => (
370                        Some(AtMostTwo::One(None)),
371                        MyCow::Borrow(&self.completed_work_rev),
372                    ),
373                    Some((ft, rem_cw)) => (Some(ft), MyCow::Own(rem_cw)),
374                }
375            }
376            AtMostTwo::Two(_) => {
377                match Self::coinbase_work(
378                    constraint_constants,
379                    Some(true),
380                    &self.completed_work_rev,
381                    self.is_coinbase_receiver_new,
382                    self.supercharge_coinbase,
383                ) {
384                    None => (
385                        Some(AtMostTwo::Two(None)),
386                        MyCow::Borrow(&self.completed_work_rev),
387                    ),
388                    Some((fts, rem_cw)) => (Some(fts), MyCow::Own(rem_cw)),
389                }
390            }
391        };
392
393        let rem_cw = cw_unchecked(rem_cw.as_ref());
394
395        let singles = rem_cw
396            .into_iter()
397            .filter_map(|work::Unchecked { fee, prover, .. }| {
398                if fee.is_zero() {
399                    None
400                } else {
401                    Some((prover, fee))
402                }
403            })
404            .rev();
405
406        let fee_transfers = fee_transfers_map(singles).expect("OCaml throw here");
407
408        if let Some(coinbase) = coinbase {
409            self.coinbase = coinbase;
410        };
411        self.fee_transfers = fee_transfers;
412    }
413
414    /// <https://github.com/MinaProtocol/mina/blob/05c2f73d0f6e4f1341286843814ce02dcb3919e0/src/lib/staged_ledger/staged_ledger.ml#L1379>
415    fn rebudget(&self) -> Result<Fee, String> {
416        // get the correct coinbase and calculate the fee transfers
417        let payment_fees = sum_fees(&self.commands_rev, |c| c.fee());
418
419        let prover_fee_others =
420            self.fee_transfers
421                .iter()
422                .try_fold(Fee::zero(), |accum, (key, fee)| {
423                    if &self.receiver_pk == key {
424                        Ok(accum)
425                    } else {
426                        accum
427                            .checked_add(fee)
428                            .ok_or_else(|| "Fee overflow".to_string())
429                    }
430                });
431
432        let revenue = payment_fees;
433        let cost = prover_fee_others;
434
435        match (revenue, cost) {
436            (Ok(r), Ok(c)) => r
437                .checked_sub(&c)
438                .ok_or_else(|| "budget did not suffice".to_string()),
439            (Err(e), _) | (_, Err(e)) => Err(e),
440        }
441    }
442
443    /// <https://github.com/MinaProtocol/mina/blob/05c2f73d0f6e4f1341286843814ce02dcb3919e0/src/lib/staged_ledger/staged_ledger.ml#L1399>
444    pub fn budget_sufficient(&self) -> bool {
445        self.budget.is_ok()
446    }
447
448    /// <https://github.com/MinaProtocol/mina/blob/05c2f73d0f6e4f1341286843814ce02dcb3919e0/src/lib/staged_ledger/staged_ledger.ml#L1402>
449    pub fn coinbase_added(&self) -> u64 {
450        match &self.coinbase {
451            AtMostTwo::Zero => 0,
452            AtMostTwo::One(_) => 1,
453            AtMostTwo::Two(_) => 2,
454        }
455    }
456
457    /// <https://github.com/MinaProtocol/mina/blob/05c2f73d0f6e4f1341286843814ce02dcb3919e0/src/lib/staged_ledger/staged_ledger.ml#L1411>
458    #[allow(clippy::bool_to_int_with_if)]
459    pub fn slots_occupied(&self) -> u64 {
460        let fee_for_self = match &self.budget {
461            Err(_) => 0,
462            Ok(b) => {
463                if b.is_zero() {
464                    0
465                } else {
466                    1
467                }
468            }
469        };
470
471        let other_provers = self
472            .fee_transfers
473            .iter()
474            .filter(|&(pk, _)| pk != &self.receiver_pk)
475            .count() as u64;
476
477        let total_fee_transfer_pks = other_provers + fee_for_self;
478
479        self.commands_rev.len() as u64 + ((total_fee_transfer_pks + 1) / 2) + self.coinbase_added()
480    }
481
482    #[allow(clippy::bool_to_int_with_if)]
483    pub fn slots_occupied_dbg(&self) -> u64 {
484        let fee_for_self = match &self.budget {
485            Err(_) => 0,
486            Ok(b) => {
487                if b.is_zero() {
488                    0
489                } else {
490                    1
491                }
492            }
493        };
494
495        let other_provers = self
496            .fee_transfers
497            .iter()
498            .filter(|&(pk, _)| pk != &self.receiver_pk)
499            .count() as u64;
500
501        let total_fee_transfer_pks = other_provers + fee_for_self;
502
503        self.commands_rev.len() as u64 + ((total_fee_transfer_pks + 1) / 2) + self.coinbase_added()
504    }
505
506    /// <https://github.com/MinaProtocol/mina/blob/05c2f73d0f6e4f1341286843814ce02dcb3919e0/src/lib/staged_ledger/staged_ledger.ml#L1430>
507    pub fn space_available(&self) -> bool {
508        let slots = self.slots_occupied();
509        self.max_space > slots
510    }
511
512    /// <https://github.com/MinaProtocol/mina/blob/05c2f73d0f6e4f1341286843814ce02dcb3919e0/src/lib/staged_ledger/staged_ledger.ml#L1434>
513    fn work_done(&self) -> bool {
514        let no_of_proof_bundles = self.completed_work_rev.len() as u64;
515        let slots = self.slots_occupied();
516
517        // If more jobs were added in the previous diff then ( t.max_space-t.max_jobs)
518        // slots can go for free in this diff
519        no_of_proof_bundles == self.max_jobs || slots <= self.max_space - self.max_jobs
520    }
521
522    /// <https://github.com/MinaProtocol/mina/blob/05c2f73d0f6e4f1341286843814ce02dcb3919e0/src/lib/staged_ledger/staged_ledger.ml#L1440>
523    pub fn space_constraint_satisfied(&self) -> bool {
524        let occupied = self.slots_occupied();
525        occupied <= self.max_space
526    }
527
528    /// <https://github.com/MinaProtocol/mina/blob/05c2f73d0f6e4f1341286843814ce02dcb3919e0/src/lib/staged_ledger/staged_ledger.ml#L1444>
529    pub fn work_constraint_satisfied(&self) -> bool {
530        // Are we doing all the work available
531        let all_proofs = self.work_done();
532        // enough work
533        let slots = self.slots_occupied();
534        let cw_count = self.completed_work_rev.len() as u64;
535        let enough_work = cw_count >= slots;
536        // if there are no transactions then don't need any proofs
537        all_proofs || slots == 0 || enough_work
538    }
539
540    /// <https://github.com/MinaProtocol/mina/blob/05c2f73d0f6e4f1341286843814ce02dcb3919e0/src/lib/staged_ledger/staged_ledger.ml#L1454>
541    pub fn available_space(&self) -> u64 {
542        self.max_space - self.slots_occupied()
543    }
544
545    pub fn available_space_dbg(&self) -> u64 {
546        self.max_space - self.slots_occupied_dbg()
547    }
548
549    /// <https://github.com/MinaProtocol/mina/blob/05c2f73d0f6e4f1341286843814ce02dcb3919e0/src/lib/staged_ledger/staged_ledger.ml#L1456>
550    pub fn discard_last_work(
551        &mut self,
552        constraint_constants: &ConstraintConstants,
553    ) -> Option<work::Work> {
554        if self.completed_work_rev.is_empty() {
555            return None;
556        }
557
558        let w = self.completed_work_rev.remove(0);
559
560        let to_be_discarded_fee = w.fee;
561        self.discarded.add_completed_work(w.clone());
562
563        let current_budget = self.budget.clone();
564
565        self.reselect_coinbase_work(constraint_constants);
566
567        let budget = match current_budget {
568            Ok(b) => b
569                .checked_add(&to_be_discarded_fee)
570                .ok_or_else(|| "Currency overflow".to_string()),
571            Err(_) => self.rebudget(),
572        };
573
574        self.budget = budget;
575
576        Some(w)
577    }
578
579    /// <https://github.com/MinaProtocol/mina/blob/05c2f73d0f6e4f1341286843814ce02dcb3919e0/src/lib/staged_ledger/staged_ledger.ml#L1476>
580    pub fn discard_user_command(&mut self) -> Option<valid::UserCommand> {
581        if self.commands_rev.is_empty() {
582            let update_fee_transfers =
583                |this: &mut Self,
584                 ft: CoinbaseFeeTransfer,
585                 coinbase: AtMostTwo<CoinbaseFeeTransfer>| {
586                    this.fee_transfers.insert(ft.receiver_pk, ft.fee);
587                    this.coinbase = coinbase;
588                    this.budget = this.rebudget();
589                };
590
591            match &self.coinbase {
592                AtMostTwo::Zero => {}
593                AtMostTwo::One(None) => {
594                    self.coinbase = AtMostTwo::Zero;
595                }
596                AtMostTwo::Two(None) => {
597                    self.coinbase = AtMostTwo::One(None);
598                }
599                AtMostTwo::Two(Some((ft, None))) => {
600                    self.coinbase = AtMostTwo::One(Some(ft.clone()));
601                }
602                AtMostTwo::One(Some(ft)) => {
603                    update_fee_transfers(self, ft.clone(), AtMostTwo::Zero);
604                }
605                AtMostTwo::Two(Some((ft1, Some(ft2)))) => {
606                    update_fee_transfers(self, ft2.clone(), AtMostTwo::One(Some(ft1.clone())));
607                }
608            }
609
610            None
611        } else {
612            let uc = self.commands_rev.remove(0);
613
614            let current_budget = self.budget.clone();
615
616            let to_be_discarded_fee = uc.fee();
617            self.discarded.add_user_command(uc.clone());
618
619            let budget = match current_budget {
620                Ok(b) => b
621                    .checked_sub(&to_be_discarded_fee)
622                    .ok_or_else(|| "Fee insufficient".to_string()),
623                Err(_) => self.rebudget(),
624            };
625
626            self.budget = budget;
627
628            Some(uc)
629        }
630    }
631
632    /// <https://github.com/MinaProtocol/mina/blob/05c2f73d0f6e4f1341286843814ce02dcb3919e0/src/lib/staged_ledger/staged_ledger.ml#L1521>
633    pub fn worked_more(&self, constraint_constants: &ConstraintConstants) -> bool {
634        // Is the work constraint satisfied even after discarding a work bundle?
635        // We reach here after having more than enough work
636
637        let mut this = self.clone(); // TODO: Do this without cloning
638        this.discard_last_work(constraint_constants);
639
640        let slots = this.slots_occupied();
641        let cw_count = this.completed_work_rev.len() as u64;
642        cw_count > 0 && cw_count >= slots
643    }
644
645    /// <https://github.com/MinaProtocol/mina/blob/05c2f73d0f6e4f1341286843814ce02dcb3919e0/src/lib/staged_ledger/staged_ledger.ml#L1532>
646    pub fn incr_coinbase_part_by(
647        &mut self,
648        constraint_constants: &ConstraintConstants,
649        count: IncreaseBy,
650    ) {
651        let incr = |cb: &AtMostTwo<CoinbaseFeeTransfer>| {
652            Ok(match cb {
653                AtMostTwo::Zero => AtMostTwo::One(None),
654                AtMostTwo::One(None) => {
655                    eprintln!("Coinbase one(none) to two");
656                    AtMostTwo::Two(None)
657                }
658                AtMostTwo::One(Some(ft)) => {
659                    eprintln!("Coinbase one(some) to two {:?}", ft);
660                    AtMostTwo::Two(Some((ft.clone(), None)))
661                }
662                AtMostTwo::Two(_) => {
663                    return Err("Coinbase count cannot be more than two".to_string())
664                }
665            })
666        };
667
668        let by_one = |this: &mut Self| -> Result<(), String> {
669            if !this.discarded.completed_work.is_empty() {
670                let coinbase = incr(&this.coinbase)?;
671                let w = this.discarded.completed_work.remove(0);
672
673                // TODO: Not sure if it should push at the end here
674                this.completed_work_rev.insert(0, w);
675                this.coinbase = coinbase;
676
677                this.reselect_coinbase_work(constraint_constants);
678            } else {
679                let coinbase = incr(&this.coinbase)?;
680                this.coinbase = coinbase;
681
682                if !this.work_done() {
683                    return Err("Could not increment coinbase transaction count because of \
684                         insufficient work"
685                        .to_string());
686                }
687            }
688
689            Ok(())
690        };
691
692        let apply = |this: &mut Self| match count {
693            IncreaseBy::One => by_one(this),
694            IncreaseBy::Two => {
695                by_one(this)?;
696                by_one(this)
697            }
698        };
699
700        if let Err(e) = apply(self) {
701            eprintln!("Error when increasing coinbase: {:?}", e);
702        };
703    }
704}